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

Last change on this file was 18311, checked in by westram, 5 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 95.3 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 expect_used_in_genome_db(GBL_command_arguments *args) {
571    if (GEN_is_genome_db(args->get_gb_main(), -1)) return NULp;
572    return GBS_global_string("ACI command '%s' can only be used in genome databases.", args->get_cmdName());
573}
574
575static GB_ERROR apply_to_origin(GBL_command_arguments *args, bool organism) {
576    EXPECT_PARAMS(args, 1, "\"ACI command\"");
577    EXPECT_ITEM_REFERENCED(args);
578
579    GB_ERROR error = expect_used_in_genome_db(args);
580    if (!error) {
581        if (!GEN_is_pseudo_gene_species(args->get_item_ref())) {
582            error = GBS_global_string("'%s' applies to gene-species only", args->get_cmdName());
583        }
584        else {
585            GBDATA *gb_origin = NULp;
586            if (organism) {
587                gb_origin = GEN_find_origin_organism(args->get_item_ref(), NULp);
588            }
589            else {
590                gb_origin = GEN_find_origin_gene(args->get_item_ref(), NULp);
591            }
592
593            if (!error && !gb_origin) error = GB_await_error();
594
595            if (!error) {
596                char         *command = unEscapeString(args->get_param(0));
597                GBL_call_env  callEnv(gb_origin, args->get_env()); // refer to gb_origin for subcommands
598                // Note: if calling env has a FieldTracker, field access from 'command' is not tracked.
599                //       That access applies to different item.
600
601                for (int i=0; i<args->input.size() && !error; i++) {
602                    char *result       = callEnv.interpret_subcommand(args->input.get(i), command);
603                    if (!result) error = GB_await_error();
604                    else         PASS_2_OUT(args, result);
605                }
606
607                free(command);
608            }
609        }
610    }
611    return error;
612}
613
614static GB_ERROR gbl_origin_gene(GBL_command_arguments *args) { return apply_to_origin(args, false); }
615static GB_ERROR gbl_origin_organism(GBL_command_arguments *args) { return apply_to_origin(args, true); }
616
617
618static GB_ERROR applyToItemFoundByKey(GBL_command_arguments *args, const char *itemname, GBDATA *gb_item_data, const char *key) {
619    GB_ERROR  error        = NULp;
620    char     *command      = unEscapeString(args->get_param(0));
621
622    for (int i=0; i<args->input.size() && !error; i++) {
623        const char *in = args->input.get(i);
624        if (in[0]) { // silently ignore empty input streams
625            GBDATA *gb_item = NULp;
626            {
627                GBDATA *gb_field = GB_find_string(gb_item_data, key, in, GB_IGNORE_CASE, SEARCH_GRANDCHILD);
628                if (gb_field) {
629                    gb_item = GB_get_father(gb_field);
630                }
631                else {
632                    error = GBS_global_string("No %s with %s '%s' found.", itemname, key, in);
633                }
634            }
635            if (gb_item) {
636                GBL_call_env callEnv(gb_item, args->get_env()); // refer to gb_item for subcommands
637                // Note: if calling env has a FieldTracker, field access from 'command' is not tracked.
638                //       That access applies to different item.
639
640                char *result       = callEnv.interpret_subcommand("", command);
641                if (!result) error = GB_await_error();
642                else  PASS_2_OUT(args, result);
643            }
644            else {
645                if (!error) error = GB_await_error();
646            }
647        }
648    }
649
650    free(command);
651    return error;
652}
653static GB_ERROR gbl_findspec(GBL_command_arguments *args) {
654    EXPECT_PARAMS(args, 1, "\"ACI command\"");
655    return applyToItemFoundByKey(args, "species", GBT_get_species_data(args->get_gb_main()), "name");
656}
657static GB_ERROR gbl_findacc(GBL_command_arguments *args) {
658    EXPECT_PARAMS(args, 1, "\"ACI command\"");
659    return applyToItemFoundByKey(args, "species", GBT_get_species_data(args->get_gb_main()), "acc");
660}
661
662static GB_ERROR gbl_findgene(GBL_command_arguments *args) {
663    EXPECT_PARAMS(args, 1, "\"ACI command\"");
664    EXPECT_ITEM_REFERENCED(args);
665
666    GB_ERROR error = expect_used_in_genome_db(args);
667    if (!error) {
668        GBDATA *gb_item = args->get_item_ref();
669        if (GEN_is_organism(gb_item)) {
670            error = applyToItemFoundByKey(args, "gene", GEN_find_gene_data(gb_item), "name");
671        }
672        else if (strcmp(GB_read_key_pntr(gb_item), "gene") == 0) {
673            // if applied to gene -> find "brother" of gene
674            GBDATA *gb_organism = GB_get_grandfather(gb_item);
675            if (GEN_is_organism(gb_organism)) {
676                error = applyToItemFoundByKey(args, "gene", GEN_find_gene_data(gb_organism), "name");
677            }
678            else {
679                error = "'findgene' cannot be used here (was applied to 'gene', but could not find gene-owning organism)";
680            }
681        }
682        else {
683            error = GBS_global_string("'findgene' cannot be applied to '%s' (need an organism)",
684                                      GBT_get_name_or_description(gb_item));
685        }
686    }
687    return error;
688}
689
690class Tab {
691    bool tab[256];
692public:
693    Tab(bool take, const char *invert) {
694        bool init = !take;
695        for (int i = 0; i<256; ++i) tab[i] = init;
696        for (int i = 0; invert[i]; ++i) tab[safeCharIndex(invert[i])] = take;
697    }
698    bool operator[](int i) const { return tab[i]; }
699};
700
701inline GB_ERROR count_by_tab(GBL_command_arguments *args, const Tab& tab) {
702    for (int i=0; i<args->input.size(); ++i) {
703        long        sum = 0;            // count frequencies
704        const char *p   = args->input.get(i);
705
706        while (*p) sum += tab[safeCharIndex(*(p++))];
707        FORMAT_2_OUT(args, "%li", sum);
708    }
709    return NULp;
710}
711inline GB_ERROR remove_by_tab(GBL_command_arguments *args, const Tab& tab) {
712    for (int i=0; i<args->input.size(); ++i) {
713        GBS_strstruct *strstruct = GBS_stropen(1000);
714        for (const char *p = args->input.get(i); *p; p++) {
715            if (!tab[(unsigned int)*p]) {
716                GBS_chrcat(strstruct, *p);
717            }
718        }
719        PASS_2_OUT(args, GBS_strclose(strstruct));
720    }
721    return NULp;
722}
723
724static GB_ERROR gbl_count(GBL_command_arguments *args) {
725    EXPECT_PARAMS(args, 1, "\"characters to count\"");
726    return count_by_tab(args, Tab(true, args->get_param(0)));
727}
728static GB_ERROR gbl_len(GBL_command_arguments *args) {
729    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "\"characters not to count\"");
730    const char *exclude = args->get_optional_param(0, "");
731    return count_by_tab(args, Tab(false, exclude));
732}
733static GB_ERROR gbl_remove(GBL_command_arguments *args) {
734    EXPECT_PARAMS(args, 1, "\"characters to remove\"");
735    return remove_by_tab(args, Tab(true, args->get_param(0)));
736}
737static GB_ERROR gbl_keep(GBL_command_arguments *args) {
738    EXPECT_PARAMS(args, 1, "\"characters to keep\"");
739    return remove_by_tab(args, Tab(false, args->get_param(0)));
740}
741
742
743static char *binop_compare(const char *arg1, const char *arg2, bool case_sensitive) {
744    int result;
745
746    if (case_sensitive) result = strcmp(arg1, arg2);
747    else result                = gbl_stricmp(arg1, arg2);
748
749    return GBS_global_string_copy("%i", result<0 ? -1 : (result>0 ? 1 : 0));
750}
751static char *binop_equals(const char *arg1, const char *arg2, bool case_sensitive) {
752    int result;
753
754    if (case_sensitive) result = strcmp(arg1, arg2);
755    else result                = gbl_stricmp(arg1, arg2);
756
757    return GBS_global_string_copy("%i", result == 0 ? 1 : 0);
758}
759static char *binop_contains(const char *arg1, const char *arg2, bool case_sensitive) {
760    const char *found = NULp;
761
762    if (!arg2[0]) return strdup("0"); // do not report matches of empty string
763
764    if (case_sensitive) found = strstr(arg1, arg2);
765    else found                = gbl_stristr(arg1, arg2);
766
767    return GBS_global_string_copy("%ti", found ? (found-arg1)+1 : 0);
768}
769static char *binop_partof(const char *arg1, const char *arg2, bool case_sensitive) {
770    return binop_contains(arg2, arg1, case_sensitive);
771}
772
773static GB_ERROR gbl_compare  (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_compare,  true);  }
774static GB_ERROR gbl_icompare (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_compare,  false); }
775static GB_ERROR gbl_equals   (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_equals,   true);  }
776static GB_ERROR gbl_iequals  (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_equals,   false); }
777static GB_ERROR gbl_contains (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_contains, true);  }
778static GB_ERROR gbl_icontains(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_contains, false); }
779static GB_ERROR gbl_partof   (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_partof,   true);  }
780static GB_ERROR gbl_ipartof  (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, binop_partof,   false); }
781
782static GB_ERROR gbl_isEmpty(GBL_command_arguments *args) {
783    EXPECT_NO_PARAM(args);
784    for (int i=0; i<args->input.size(); i++) {
785        const char *str = args->input.get(i);
786        FORMAT_2_OUT(args, "%i", str[0] == 0);
787    }
788    return NULp;
789}
790static GB_ERROR gbl_inRange(GBL_command_arguments *args) {
791    EXPECT_PARAMS(args, 2, "low,high");
792
793    double low  = strtod(args->get_param(0), NULp);
794    double high = strtod(args->get_param(1), NULp);
795
796    for (int i=0; i<args->input.size(); i++) {
797        double val     = strtod(args->input.get(i), NULp);
798        bool   inRange = low<=val && val<=high;
799        FORMAT_2_OUT(args, "%i", inRange);
800    }
801    return NULp;
802}
803
804
805static GB_ERROR gbl_translate(GBL_command_arguments *args) {
806    EXPECT_OPTIONAL_PARAMS(args, 2, "old,new", 1, "other");
807
808    const char *other = args->get_optional_param(2, NULp);
809    if (other && (other[0] == 0 || other[1] != 0)) {
810        return "third parameter of translate has to be one character (i.e. \"-\")";
811    }
812    const char replace_other = other ? other[0] : 0;
813
814    // build translation table :
815    unsigned char tab[256];
816    {
817        const unsigned char *o = (const unsigned char *)args->get_param(0);
818        const unsigned char *n = (const unsigned char *)args->get_param(1);
819        char        used[256];
820
821        if (strlen((const char *)o) != strlen((const char *)n)) {
822            return "arguments 1 and 2 of translate should be strings with identical length";
823        }
824
825        for (int i = 0; i<256; ++i) { // IRRELEVANT_LOOP
826            tab[i]  = replace_other ? replace_other : i; // replace unused or identity translation
827            used[i] = 0;
828        }
829
830        for (int i = 0; o[i]; ++i) {
831            if (used[o[i]]) return GBS_global_string("character '%c' used twice in argument 1 of translate", o[i]);
832            used[o[i]] = 1;
833            tab[o[i]]  = n[i]; // real translation
834        }
835    }
836
837    for (int i=0; i<args->input.size(); i++) {
838        GBS_strstruct *strstruct = GBS_stropen(1000);
839        for (const char *p = args->input.get(i); *p; p++) {
840            GBS_chrcat(strstruct, tab[(unsigned char)*p]);
841        }
842        PASS_2_OUT(args, GBS_strclose(strstruct));
843    }
844    return NULp;
845}
846
847
848static GB_ERROR gbl_echo(GBL_command_arguments *args) {
849    ACCEPT_ANY_PARAMS(args);
850    COMMAND_DROPS_INPUT_STREAMS(args);
851    for (int i=0; i<args->param_count(); i++) PARAM_2_OUT(args, i);
852    return NULp;
853}
854
855static GB_ERROR gbl_dd(GBL_command_arguments *args) {
856    EXPECT_NO_PARAM(args);
857    return gbl_mid_streams(args->input, args->output, 0, -1); // copy all streams
858}
859
860enum Case { UPPER, LOWER, CAPS };
861
862static GB_ERROR convert_case(GBL_command_arguments *args, Case convTo) {
863    EXPECT_NO_PARAM(args);
864
865    for (int i=0; i<args->input.size(); i++) {
866        char *p              = ARB_strdup(args->input.get(i));
867        bool  last_was_alnum = false;
868
869        for (char *pp = p; pp[0]; ++pp) {
870            switch (convTo) {
871                case LOWER:  pp[0] = tolower(pp[0]); break;
872                case UPPER:  pp[0] = toupper(pp[0]); break;
873                case CAPS: {
874                    bool alnum = isalnum(pp[0]);
875                    if (alnum) pp[0] = (last_was_alnum ? tolower : toupper)(pp[0]);
876                    last_was_alnum = alnum;
877                    break;
878                }
879                default: gb_assert(0); break;
880            }
881        }
882
883        PASS_2_OUT(args, p);
884    }
885
886    return NULp;
887}
888
889static GB_ERROR gbl_caps (GBL_command_arguments *args) { return convert_case(args, CAPS); }
890static GB_ERROR gbl_upper(GBL_command_arguments *args) { return convert_case(args, UPPER); }
891static GB_ERROR gbl_lower(GBL_command_arguments *args) { return convert_case(args, LOWER); }
892
893static GB_ERROR gbl_head(GBL_command_arguments *args) {
894    EXPECT_PARAMS(args, 1, "length_of_head");
895    int start = atoi(args->get_param(0));
896    if (start <= 0) return gbl_mid_streams(args->input, args->output, 1, 0); // empty all streams
897    return gbl_mid_streams(args->input, args->output, 0, start-1);
898}
899static GB_ERROR gbl_tail(GBL_command_arguments *args) {
900    EXPECT_PARAMS(args, 1, "length_of_tail");
901    int end = atoi(args->get_param(0));
902    if (end <= 0) return gbl_mid_streams(args->input, args->output, 1, 0); // empty all streams
903    return gbl_mid_streams(args->input, args->output, -end, -1);
904}
905
906inline GB_ERROR mid(GBL_command_arguments *args, int start_index) {
907    EXPECT_PARAMS(args, 2, "start,end");
908    return gbl_mid_streams(args->input, args->output, atoi(args->get_param(0))-start_index, atoi(args->get_param(1))-start_index);
909}
910static GB_ERROR gbl_mid0(GBL_command_arguments *args) { return mid(args, 0); }
911static GB_ERROR gbl_mid (GBL_command_arguments *args) { return mid(args, 1); }
912
913static GB_ERROR tab(GBL_command_arguments *args, bool pretab) {
914    EXPECT_PARAMS(args, 1, "tabstop");
915
916    int tab = atoi(args->get_param(0));
917    for (int i=0; i<args->input.size(); i++) {
918        int len = strlen(args->input.get(i));
919        if (len >= tab) IN_2_OUT(args, i);
920        else {
921            char *p = ARB_alloc<char>(tab+1);
922            if (pretab) {
923                int spaces = tab-len;
924                for (int j = 0; j<spaces; ++j) p[j] = ' ';
925                strcpy(p+spaces, args->input.get(i));
926            }
927            else {
928                strcpy(p, args->input.get(i));
929                for (int j=len; j<tab; j++) p[j] = ' ';
930                p[tab] = 0;
931            }
932            PASS_2_OUT(args, p);
933        }
934    }
935    return NULp;
936}
937static GB_ERROR gbl_tab   (GBL_command_arguments *args) { return tab(args, false); }
938static GB_ERROR gbl_pretab(GBL_command_arguments *args) { return tab(args, true); }
939
940static GB_ERROR gbl_crop(GBL_command_arguments *args) {
941    EXPECT_PARAMS(args, 1, "\"chars_to_crop\"");
942
943    const char *chars_to_crop = args->get_param(0);
944    for (int i=0; i<args->input.size(); i++) {
945        const char *s = args->input.get(i);
946        while (s[0] && strchr(chars_to_crop, s[0])) s++; // crop at beg of line
947
948        int   len = strlen(s);
949        char *p   = ARB_alloc<char>(len+1);
950        strcpy(p, s);
951
952        {
953            char *pe = p+len-1;
954
955            while (pe >= p && strchr(chars_to_crop, pe[0])) { // crop at end of line
956                --pe;
957            }
958            gb_assert(pe >= (p-1));
959            pe[1] = 0;
960        }
961        PASS_2_OUT(args, p);
962    }
963    return NULp;
964}
965
966
967
968static GB_ERROR gbl_cut(GBL_command_arguments *args) {
969    EXPECT_PARAMS_PASSED(args, "streamnumber[,streamnumber]+");
970
971    for (int i=0; i<args->param_count(); i++) {
972        int stream = atoi(args->get_param(i));
973        EXPECT_LEGAL_STREAM_INDEX(args, stream);
974        IN_2_OUT(args, bio2info(stream));
975    }
976    return NULp;
977}
978static GB_ERROR gbl_drop(GBL_command_arguments *args) {
979    EXPECT_PARAMS_PASSED(args, "streamnumber[,streamnumber]+");
980
981    GB_ERROR  error   = NULp;
982    bool     *dropped = ARB_alloc<bool>(args->input.size());
983
984    for (int i=0; i<args->input.size(); ++i) dropped[i] = false;
985
986    for (int i=0; i<args->param_count() && !error; ++i) {
987        int stream = atoi(args->get_param(i));
988        error = check_valid_stream_index(args, stream);
989        if (!error) dropped[bio2info(stream)] = true;
990    }
991
992    if (!error) {
993        for (int i=0; i<args->input.size(); ++i) {
994            if (!dropped[i]) IN_2_OUT(args, i);
995        }
996    }
997    free(dropped);
998
999    return error;
1000}
1001
1002static GB_ERROR gbl_dropempty(GBL_command_arguments *args) {
1003    EXPECT_NO_PARAM(args);
1004
1005    for (int i=0; i<args->input.size(); ++i) {
1006        if (args->input.get(i)[0]) { // if non-empty
1007            IN_2_OUT(args, i);
1008        }
1009    }
1010    return NULp;
1011}
1012
1013static GB_ERROR gbl_dropzero(GBL_command_arguments *args) {
1014    EXPECT_NO_PARAM(args);
1015
1016    for (int i=0; i<args->input.size(); ++i) {
1017        if (atoi(args->input.get(i))) { // if non-zero
1018            IN_2_OUT(args, i);
1019        }
1020    }
1021    return NULp;
1022}
1023
1024static GB_ERROR gbl_swap(GBL_command_arguments *args) {
1025    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 2, "streamnumber,streamnumber");
1026
1027    if (args->input.size()<2) return "need at least two input streams";
1028
1029    int swap1;
1030    int swap2;
1031    if (args->param_count() == 0) {
1032        swap1 = args->input.size()-1;
1033        swap2 = args->input.size()-2;
1034    }
1035    else {
1036        gb_assert(args->param_count() == 2);
1037
1038        swap1 = atoi(args->get_param(0));
1039        swap2 = atoi(args->get_param(1));
1040
1041        EXPECT_LEGAL_STREAM_INDEX(args, swap1);
1042        EXPECT_LEGAL_STREAM_INDEX(args, swap2);
1043
1044        swap1 = bio2info(swap1);
1045        swap2 = bio2info(swap2);
1046    }
1047
1048    for (int i = 0; i<args->input.size(); ++i) {
1049        int j = i == swap1 ? swap2 : (i == swap2 ? swap1 : i);
1050        IN_2_OUT(args, j);
1051    }
1052
1053    return NULp;
1054}
1055
1056static GB_ERROR backfront_stream(GBL_command_arguments *args, int toback) {
1057    EXPECT_PARAMS(args, 1, "streamnumber");
1058    if (args->input.size()<1) return "need at least one input stream";
1059
1060    int stream_to_move = atoi(args->get_param(0));
1061    EXPECT_LEGAL_STREAM_INDEX(args, stream_to_move);
1062    stream_to_move = bio2info(stream_to_move);
1063
1064    if (!toback) IN_2_OUT(args, stream_to_move);
1065    for (int i = 0; i<args->input.size(); ++i) {
1066        if (i != stream_to_move) IN_2_OUT(args, i);
1067    }
1068    if (toback) IN_2_OUT(args, stream_to_move);
1069
1070    return NULp;
1071}
1072static GB_ERROR gbl_toback (GBL_command_arguments *args) { return backfront_stream(args, 1); }
1073static GB_ERROR gbl_tofront(GBL_command_arguments *args) { return backfront_stream(args, 0); }
1074
1075static GB_ERROR gbl_merge(GBL_command_arguments *args) {
1076    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "\"separator\"");
1077    const char *separator = args->get_optional_param(0, NULp);
1078
1079    if (args->input.size()) {
1080        GBS_strstruct *str = GBS_stropen(1000);
1081        GBS_strcat(str, args->input.get(0));
1082
1083        for (int i = 1; i<args->input.size(); ++i) {
1084            if (separator) GBS_strcat(str, separator);
1085            GBS_strcat(str, args->input.get(i));
1086        }
1087
1088        PASS_2_OUT(args, GBS_strclose(str));
1089    }
1090    return NULp;
1091}
1092
1093static GB_ERROR gbl_split(GBL_command_arguments *args) {
1094    EXPECT_OPTIONAL_PARAMS_CUSTOM(args, 0, NULp, 2, "\"separator\"[,mode]", true, false);
1095
1096    const char *separator = args->get_optional_param(0, "\n");
1097    int split_mode        = atoi(args->get_optional_param(1, "0")); // 0: remove separator, 1: split before separator, 2: split behind separator
1098
1099    if (!separator[0]) {
1100        // e.g. happens if trying to specify character ';' or ','
1101        return "Invalid separator (cannot be empty; please try to quote the parameter)";
1102    }
1103
1104    if (split_mode<0 || split_mode>2) return GBS_global_string("Illegal split mode '%i' (valid: 0..2)", split_mode);
1105
1106    {
1107        size_t sepLen = strlen(separator);
1108
1109        for (int i = 0; i<args->input.size(); ++i) {
1110            const char *in   = args->input.get(i);
1111            const char *from = in; // search from here
1112
1113            while (in) {
1114                const char *splitAt = strstr(from, separator);
1115                if (splitAt) {
1116                    size_t  len;
1117                    char   *copy;
1118
1119                    if (split_mode == 2) splitAt += sepLen; // split behind separator
1120
1121                    len  = splitAt-in;
1122                    copy = ARB_strndup(in, len);
1123
1124                    PASS_2_OUT(args, copy);
1125
1126                    in   = splitAt + (split_mode == 0 ? sepLen : 0);
1127                    from = in+(split_mode == 1 ? sepLen : 0);
1128                }
1129                else {
1130                    COPY_2_OUT(args, in); // last part
1131                    in = NULp;
1132                }
1133            }
1134        }
1135    }
1136
1137    return NULp;
1138}
1139
1140static GB_ERROR gbl_colsplit(GBL_command_arguments *args) {
1141    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "width");
1142
1143    int width = atoi(args->get_optional_param(0, "1"));
1144    if (width<1) return "Invalid width";
1145
1146    for (int i = 0; i<args->input.size(); ++i) {
1147        const char *in  = args->input.get(i);
1148        int         len = strlen(in);
1149
1150        while (len>0) {
1151            char *part = ARB_strpartdup(in, in+width-1);
1152            PASS_2_OUT(args, part);
1153
1154            in  += width;
1155            len -= width;
1156        }
1157    }
1158
1159    return NULp;
1160}
1161// ----------------------------------
1162//      Extended string functions
1163
1164static char *do_extract_words(const char *source, const char *chars, float minlen, bool sort_output) {
1165    /* extract all words in a text that:
1166     * if minlen < 1.0 -> contain more than minlen*len_of_text characters that also exists in chars
1167     * if minlen > 1.0 -> contain more than minlen characters that also exists in chars
1168     */
1169
1170    char           *s         = ARB_strdup(source);
1171    char          **ps        = ARB_calloc<char*>((strlen(source)>>1) + 1);
1172    GBS_strstruct  *strstruct = GBS_stropen(1000);
1173    char           *f         = s;
1174    int             count     = 0;
1175    char           *p; // @@@ fix locals
1176    char           *h;
1177    int             cnt;
1178    int             len;
1179    int             iminlen   = (int) (minlen+.5);
1180
1181    while ((p = strtok(f, " \t,;:|"))) {
1182        f = NULp;
1183        cnt = 0;
1184        len = strlen(p);
1185        for (h=p; *h; h++) {
1186            if (strchr(chars, *h)) cnt++;
1187        }
1188
1189        if (minlen == 1.0) {
1190            if (cnt != len) continue;
1191        }
1192        else if (minlen > 1.0) {
1193            if (cnt < iminlen) continue;
1194        }
1195        else {
1196            if (len < 3 || cnt < minlen*len) continue;
1197        }
1198        ps[count] = p;
1199        count ++;
1200    }
1201    if (sort_output) {
1202        GB_sort((void **)ps, 0, count, GB_string_comparator, NULp);
1203    }
1204    for (cnt = 0; cnt<count; cnt++) {
1205        if (cnt) {
1206            GBS_chrcat(strstruct, ' ');
1207        }
1208        GBS_strcat(strstruct, ps[cnt]);
1209    }
1210
1211    free(ps);
1212    free(s);
1213    return GBS_strclose(strstruct);
1214}
1215
1216static GB_ERROR gbl_extract_words(GBL_command_arguments *args) {
1217    EXPECT_PARAMS(args, 2, "\"chars\", minchars");
1218
1219    float len = atof(args->get_param(1));
1220    for (int i=0; i<args->input.size(); i++) {
1221        char *res = do_extract_words(args->input.get(i), args->get_param(0), len, 1);
1222        gb_assert(res);
1223        PASS_2_OUT(args, res);
1224    }
1225    return NULp;
1226}
1227
1228static GB_ERROR gbl_extract_sequence(GBL_command_arguments *args) {
1229    EXPECT_PARAMS(args, 2, "\"chars\",minFrequency");
1230
1231    const char *chars   = args->get_param(0);
1232    float       minFreq = atof(args->get_param(1));
1233
1234    if (minFreq <0.0 || minFreq > 1.0) return GBS_global_string("Illegal minFrequency=%f (allowed: ]0.0 .. 1.0[)", minFreq);
1235
1236    for (int i=0; i<args->input.size(); i++) {
1237        char *res = do_extract_words(args->input.get(i), chars, minFreq, 0);
1238        gb_assert(res);
1239        PASS_2_OUT(args, res);
1240    }
1241    return NULp;
1242}
1243
1244static GB_ERROR gbl_checksum(GBL_command_arguments *args) {
1245    GBL_BEGIN_PARAMS;
1246    GBL_PARAM_STRING(exclude, "exclude=", "", "Remove given characters before calculating");
1247    GBL_PARAM_BIT   (upper,   "toupper",  0,  "Convert all characters to uppercase before calculating");
1248    GBL_TRACE_PARAMS(args);
1249    GBL_END_PARAMS;
1250
1251    for (int i=0; i<args->input.size(); i++) {
1252        long id = GBS_checksum(args->input.get(i), upper, exclude);
1253        FORMAT_2_OUT(args, "%lX", id);
1254    }
1255    return NULp;
1256}
1257
1258static GB_ERROR gbl_gcgchecksum(GBL_command_arguments *args) {
1259    EXPECT_NO_PARAM(args);
1260
1261    for (int i=0; i<args->input.size(); i++) {
1262        long id = GBS_gcgchecksum(args->input.get(i));
1263        FORMAT_2_OUT(args, "%li", id);
1264    }
1265    return NULp;
1266}
1267
1268// ------------
1269//      SRT
1270
1271static GB_ERROR gbl_srt(GBL_command_arguments *args) {
1272    EXPECT_PARAMS_PASSED(args, "expr[,expr]+");
1273
1274    GB_ERROR error = NULp;
1275    for (int i=0; i<args->input.size() && !error; i++) {
1276        char *modsource = NULp;
1277
1278        for (int j=0; j<args->param_count() && !error; j++) {
1279            char *hs = GBS_string_eval_in_env(modsource ? modsource : args->input.get(i), args->get_param(j), args->get_callEnv());
1280
1281            if (hs) freeset(modsource, hs);
1282            else {
1283                error = GB_await_error();
1284                free(modsource);
1285            }
1286        }
1287
1288        if (!error) {
1289            if (modsource) PASS_2_OUT(args, modsource);
1290            else           IN_2_OUT(args, i);
1291        }
1292    }
1293    return error;
1294}
1295
1296// -----------------------------
1297//      Calculator Functions
1298
1299struct binop_pair {
1300    int    (*INT)   (int, int);
1301    double (*DOUBLE)(double, double);
1302    binop_pair(int (*INT_)(int, int), double (*DOUBLE_)(double, double)) : INT(INT_), DOUBLE(DOUBLE_) {}
1303};
1304
1305static char *apply_numeric_binop(const char *arg1, const char *arg2, int (*num_bin_op)(int,int)) {
1306    int v1     = atoi(arg1);
1307    int v2     = atoi(arg2);
1308    int result = num_bin_op(v1, v2);
1309
1310    return GBS_global_string_copy("%i", result);
1311}
1312
1313static char *apply_double_binop(const char *arg1, const char *arg2, double (*num_bin_op)(double,double)) {
1314    double v1     = strtod(arg1, NULp);
1315    double v2     = strtod(arg2, NULp);
1316    double result = num_bin_op(v1, v2);
1317
1318    return GBS_global_string_copy("%g", result);
1319}
1320
1321static char *apply_auto_numeric_binop(const char *arg1, const char *arg2, binop_pair multiop) {
1322    // argument type detection (int vs double)
1323    int    i1 = atoi(arg1);
1324    int    i2 = atoi(arg2);
1325    double d1 = strtod(arg1, NULp);
1326    double d2 = strtod(arg2, NULp);
1327
1328    if (double(i1) == d1 || double(i2) == d2) {
1329        int result = multiop.INT(i1, i2);
1330        return GBS_global_string_copy("%i", result);
1331    }
1332    else {
1333        double result = multiop.DOUBLE(d1, d2);
1334        return GBS_global_string_copy("%g", result);
1335    }
1336}
1337
1338
1339
1340template <typename T> static T binop_plus    (T v1, T v2) { return v1+v2; }
1341template <typename T> static T binop_minus   (T v1, T v2) { return v1-v2; }
1342template <typename T> static T binop_mult    (T v1, T v2) { return v1*v2; }
1343template <typename T> static T binop_div     (T v1, T v2) { return v2 ? v1/v2 : 0; }
1344template <typename T> static T binop_per_cent(T v1, T v2) { return v2 ? (v1*100)/v2 : 0; }
1345
1346static int binop_rest(int i1, int i2) { return i2 ? i1%i2 : 0; }
1347
1348
1349static GB_ERROR gbl_plus     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_plus<int>);        }
1350static GB_ERROR gbl_fplus    (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_plus<double>);     }
1351static GB_ERROR gbl_minus    (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_minus<int>);       }
1352static GB_ERROR gbl_fminus   (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_minus<double>);    }
1353static GB_ERROR gbl_mult     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_mult<int>);        }
1354static GB_ERROR gbl_fmult    (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_mult<double>);     }
1355static GB_ERROR gbl_div      (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_div<int>);         }
1356static GB_ERROR gbl_fdiv     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_div<double>);      }
1357static GB_ERROR gbl_rest     (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_rest);             }
1358static GB_ERROR gbl_per_cent (GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_numeric_binop, binop_per_cent<int>);    }
1359static GB_ERROR gbl_fper_cent(GBL_command_arguments *args) { return gbl_apply_binary_operator(args, apply_double_binop,  binop_per_cent<double>); }
1360
1361template <typename T> static T binop_isAbove(T i1, T i2) { return i1>i2; }
1362template <typename T> static T binop_isBelow(T i1, T i2) { return i1<i2; }
1363template <typename T> static T binop_isEqual(T i1, T i2) { return i1 == i2; }
1364
1365static 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>)); }
1366static 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>)); }
1367static 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>)); }
1368
1369inline double float_shift_factor(int digits) {
1370    if (digits<0) {
1371        return 1.0/float_shift_factor(-digits);
1372    }
1373    int factor = 1;
1374    while (digits>0) { // IRRELEVANT_LOOP (gcc 9.x refuses to optimize)
1375        factor *= 10;
1376        --digits;
1377    }
1378    return factor;
1379}
1380
1381static GB_ERROR gbl_round(GBL_command_arguments *args) {
1382    EXPECT_PARAMS(args, 1, "digits");
1383    int digits = atoi(args->get_param(0));
1384
1385    double factor = float_shift_factor(digits);
1386    for (int i=0; i<args->input.size(); ++i) {
1387        double val = strtod(args->input.get(i), NULp);
1388        val = round(val*factor)/factor;
1389        FORMAT_2_OUT(args, "%g", val);
1390    }
1391    return NULp;
1392}
1393
1394
1395
1396// boolean operators
1397
1398static GB_ERROR gbl_not(GBL_command_arguments *args) {
1399    EXPECT_NO_PARAM(args);
1400
1401    for (int i=0; i<args->input.size(); ++i) {
1402        const char *s   = args->input.get(i);
1403        int         val = atoi(s);
1404        FORMAT_2_OUT(args, "%i", !val);
1405    }
1406    return NULp;
1407}
1408
1409static GB_ERROR gbl_and(GBL_command_arguments *args) {
1410    EXPECT_NO_PARAM(args);
1411    bool conjunction = true;
1412    for (int i=0; conjunction && i<args->input.size(); ++i) {
1413        const char *s = args->input.get(i);
1414        conjunction = conjunction && atoi(s);
1415    }
1416    FORMAT_2_OUT(args, "%i", conjunction);
1417    return NULp;
1418}
1419static GB_ERROR gbl_or(GBL_command_arguments *args) {
1420    EXPECT_NO_PARAM(args);
1421    bool disjunction = false;
1422    for (int i=0; !disjunction && i<args->input.size(); ++i) {
1423        const char *s = args->input.get(i);
1424        disjunction = disjunction || atoi(s);
1425    }
1426    FORMAT_2_OUT(args, "%i", disjunction);
1427    return NULp;
1428}
1429
1430static GB_ERROR gbl_select(GBL_command_arguments *args) {
1431    ACCEPT_ANY_PARAMS(args);
1432
1433    GB_ERROR error = NULp;
1434    for (int i=0; i<args->input.size() && !error; i++) {
1435        int paraidx = atoi(args->input.get(i));
1436        error       = check_valid_param_index(args, paraidx);
1437        if (!error) {
1438            char *result = args->get_callEnv().interpret_subcommand("", args->get_param(paraidx)); // @@@ EVALUATED_PARAM (#768)
1439            if (!result) error = GB_await_error();
1440            else PASS_2_OUT(args, result);
1441        }
1442    }
1443    return error;
1444}
1445
1446static GB_ERROR gbl_readdb(GBL_command_arguments *args) {
1447    EXPECT_PARAMS_PASSED(args, "fieldname[,fieldname]+");
1448    EXPECT_ITEM_REFERENCED(args);
1449    COMMAND_DROPS_INPUT_STREAMS(args);
1450
1451    GBS_strstruct *strstr = GBS_stropen(1024);
1452    for (int i=0; i<args->param_count(); i++) {
1453        char *val = GBT_read_as_string(args->get_item_ref(),
1454                                       args->track_field_access(args->get_param(i)));
1455        if (val) {
1456            GBS_strcat(strstr, val);
1457            free(val);
1458        }
1459    }
1460    PASS_2_OUT(args, GBS_strclose(strstr));
1461    return NULp;
1462}
1463
1464
1465enum GBT_ITEM_TYPE {
1466    GBT_ITEM_UNKNOWN,
1467    GBT_ITEM_SPECIES,
1468    GBT_ITEM_GENE
1469};
1470
1471static GBT_ITEM_TYPE identify_gb_item(GBDATA *gb_item) {
1472    /* returns: GBT_ITEM_UNKNOWN    -> unknown database_item
1473     *          GBT_ITEM_SPECIES    -> /species_data/species
1474     *          GBT_ITEM_GENE       -> /species_data/species/gene_data/gene */
1475
1476    GBT_ITEM_TYPE res = GBT_ITEM_UNKNOWN;
1477    if (gb_item) {
1478        GBDATA *gb_father = GB_get_father(gb_item);
1479        if (gb_father) {
1480            const char *key = GB_KEY(gb_item);
1481
1482            if (strcmp(key, "species")                    == 0 &&
1483                strcmp(GB_KEY(gb_father), "species_data") == 0) {
1484                res = GBT_ITEM_SPECIES;
1485            }
1486            else if (strcmp(key, "gene")                   == 0 &&
1487                strcmp(GB_KEY(gb_father), "gene_data")     == 0 &&
1488                identify_gb_item(GB_get_father(gb_father)) == GBT_ITEM_SPECIES) {
1489                res = GBT_ITEM_GENE;
1490            }
1491        }
1492    }
1493    return res;
1494}
1495
1496// --------------------------------------------------------------------------------
1497// taxonomy caching
1498
1499#if defined(DEBUG)
1500// #define DUMP_TAXONOMY_CACHING
1501#endif
1502
1503
1504#define GROUP_COUNT_CHARS 6                         // characters in taxonomy-key reserved for group-counter (hex number)
1505#define BITS_PER_HEXCHAR  4
1506#define MAX_GROUPS        (1 << (GROUP_COUNT_CHARS*BITS_PER_HEXCHAR)) // resulting number of groups
1507
1508struct cached_taxonomy {
1509    char    *tree_name;         // tree for which taxonomy is cached here
1510    int      groups;            // number of named groups in tree (at time of caching)
1511    GB_HASH *taxonomy; /* keys: "!species", ">XXXXgroup" and "<root>".
1512                        * Species and groups contain their first parent (i.e. '>XXXXgroup' or '<root>').
1513                        * Species not in hash are not members of tree.
1514                        * The 'XXXX' in groupname is simply a counter to avoid multiple groups with same name.
1515                        * The group-db-entries are stored in hash as pointers ('>>%p') and
1516                        * point to their own group entry ('>XXXXgroup')
1517                        *
1518                        * Note: the number of 'X's in 'XXXX' above is defined by GROUP_COUNT_CHARS!
1519                        */
1520};
1521
1522static void free_cached_taxonomy(cached_taxonomy *ct) {
1523    free(ct->tree_name);
1524    GBS_free_hash(ct->taxonomy);
1525    free(ct);
1526}
1527
1528static void build_taxonomy_rek(TreeNode *node, GB_HASH *tax_hash, const char *parent_group, int *group_counter) {
1529    if (node->is_leaf()) {
1530        GBDATA *gb_species = node->gb_node;
1531        if (gb_species) { // not zombie
1532            const char *name = GBT_get_name(gb_species);
1533            if (name) GBS_write_hash(tax_hash, GBS_global_string("!%s", name), (long)ARB_strdup(parent_group));
1534        }
1535    }
1536    else {
1537        if (node->has_group_info()) { // node with name
1538            char       *hash_entry;
1539            const char *hash_binary_entry;
1540            (*group_counter)++;
1541
1542            gb_assert((*group_counter)<MAX_GROUPS); // overflow - increase GROUP_COUNT_CHARS
1543
1544            TreeNode *keelTarget = node->keelTarget();
1545
1546            hash_entry = GBS_global_string_copy(">%0*x%s%s",
1547                                                GROUP_COUNT_CHARS, *group_counter,
1548                                                keelTarget ? "!" : "",
1549                                                node->name);
1550            GBS_write_hash(tax_hash, hash_entry, (long)ARB_strdup(parent_group));
1551
1552            hash_binary_entry = GBS_global_string(">>%p", node->gb_node);
1553            GBS_write_hash(tax_hash, hash_binary_entry, (long)ARB_strdup(hash_entry));
1554
1555            if (keelTarget) { // keeled group (projected to son)
1556                if (keelTarget->is_leftson()) {
1557                    build_taxonomy_rek(node->get_leftson(),  tax_hash, hash_entry,   group_counter); // pass down hash_entry only to keelTarget
1558                    build_taxonomy_rek(node->get_rightson(), tax_hash, parent_group, group_counter);
1559                }
1560                else {
1561                    build_taxonomy_rek(node->get_leftson(),  tax_hash, parent_group, group_counter);
1562                    build_taxonomy_rek(node->get_rightson(), tax_hash, hash_entry,   group_counter);
1563                }
1564            }
1565            else { // normal group
1566                build_taxonomy_rek(node->get_leftson(),  tax_hash, hash_entry, group_counter); // pass down hash_entry to both sons
1567                build_taxonomy_rek(node->get_rightson(), tax_hash, hash_entry, group_counter);
1568            }
1569
1570            free(hash_entry);
1571        }
1572        else {
1573            build_taxonomy_rek(node->get_leftson(),  tax_hash, parent_group, group_counter);
1574            build_taxonomy_rek(node->get_rightson(), tax_hash, parent_group, group_counter);
1575        }
1576    }
1577}
1578
1579static GB_HASH *cached_taxonomies = NULp;
1580
1581static bool is_cached_taxonomy(const char */*key*/, long val, void *cl_ct) {
1582    cached_taxonomy *ct1 = (cached_taxonomy *)val;
1583    cached_taxonomy *ct2 = (cached_taxonomy *)cl_ct;
1584
1585    return ct1 == ct2;
1586}
1587
1588static const char *tree_of_cached_taxonomy(cached_taxonomy *ct) {
1589    /* search the hash to find the correct cached taxonomy.
1590     * searching for tree name does not work, because the tree possibly already was deleted
1591     */
1592    const char *tree = GBS_hash_next_element_that(cached_taxonomies, NULp, is_cached_taxonomy, ct);
1593#ifdef DUMP_TAXONOMY_CACHING
1594    if (tree) printf("tree_of_cached_taxonomy: tree='%s' ct->tree_name='%s'\n", tree, ct->tree_name);
1595#endif // DUMP_TAXONOMY_CACHING
1596    return tree;
1597}
1598
1599static void flush_taxonomy_cb(GBDATA *gbd, cached_taxonomy *ct) {
1600    /* this cb is bound all tree db members below "/tree_data/tree_xxx" which
1601     * may have an effect on the displayed taxonomy
1602     * it invalidates cached taxonomies for that tree (when changed or deleted)
1603     */
1604
1605    GB_ERROR    error = NULp;
1606    const char *found = tree_of_cached_taxonomy(ct);
1607
1608    if (found) {
1609#ifdef DUMP_TAXONOMY_CACHING
1610        fprintf(stderr, "Deleting cached taxonomy ct=%p (tree='%s')\n", ct, found);
1611#endif // DUMP_TAXONOMY_CACHING
1612        GBS_write_hash(cached_taxonomies, found, 0); // delete cached taxonomy from hash
1613        free_cached_taxonomy(ct);
1614    }
1615#ifdef DUMP_TAXONOMY_CACHING
1616    else {
1617        fprintf(stderr, "No tree found for cached_taxonomies ct=%p (already deleted?)\n", ct);
1618    }
1619#endif // DUMP_TAXONOMY_CACHING
1620
1621    if (!GB_inside_callback(gbd, GB_CB_DELETE)) {
1622        GB_remove_all_callbacks_to(gbd, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1623    }
1624
1625    if (found && !error) {
1626        GBDATA *gb_main = GB_get_gb_main_during_cb();
1627        if (gb_main) {
1628            GBDATA *gb_tree_refresh = GB_search(gb_main, AWAR_TREE_REFRESH, GB_INT);
1629            if (!gb_tree_refresh) {
1630                error = GBS_global_string("%s (while trying to force refresh)", GB_await_error());
1631            }
1632            else {
1633                GB_touch(gb_tree_refresh); // Note : force tree update
1634            }
1635        }
1636    }
1637
1638    if (error) {
1639        fprintf(stderr, "Error in flush_taxonomy_cb: %s\n", error);
1640    }
1641}
1642
1643static void flush_taxonomy_if_new_group_cb(GBDATA *gb_tree, cached_taxonomy *ct) {
1644    // detects the creation of new groups and call flush_taxonomy_cb() manually
1645#ifdef DUMP_TAXONOMY_CACHING
1646    fputs("flush_taxonomy_if_new_group_cb() has been called\n", stderr);
1647#endif // DUMP_TAXONOMY_CACHING
1648
1649    const char *tree_name = tree_of_cached_taxonomy(ct);
1650    if (tree_name) {
1651        int     groups = 0;
1652        GBDATA *gb_group_node;
1653
1654        for (gb_group_node = GB_entry(gb_tree, "node");
1655             gb_group_node;
1656             gb_group_node = GB_nextEntry(gb_group_node))
1657        {
1658            if (GB_entry(gb_group_node, "group_name")) {
1659                groups++; // count named groups only
1660            }
1661        }
1662
1663#ifdef DUMP_TAXONOMY_CACHING
1664        fprintf(stderr, "cached_groups=%i  counted_groups=%i\n", ct->groups, groups);
1665#endif // DUMP_TAXONOMY_CACHING
1666        if (groups != ct->groups) {
1667#ifdef DUMP_TAXONOMY_CACHING
1668            fprintf(stderr, "Number of groups changed -> invoking flush_taxonomy_cb() manually\n");
1669#endif // DUMP_TAXONOMY_CACHING
1670            flush_taxonomy_cb(gb_tree, ct);
1671        }
1672    }
1673#ifdef DUMP_TAXONOMY_CACHING
1674    else {
1675        fprintf(stderr, "cached taxonomy no longer valid.\n");
1676    }
1677#endif // DUMP_TAXONOMY_CACHING
1678}
1679
1680static cached_taxonomy *get_cached_taxonomy(GBDATA *gb_main, const char *tree_name, GB_ERROR *error) {
1681    long cached;
1682    *error = NULp;
1683    if (!cached_taxonomies) {
1684        cached_taxonomies = GBS_create_hash(20, GB_IGNORE_CASE);
1685    }
1686    cached = GBS_read_hash(cached_taxonomies, tree_name);
1687    if (!cached) {
1688        TreeNode *tree    = GBT_read_tree(gb_main, tree_name, new SimpleRoot);
1689        if (!tree) *error = GB_await_error();
1690        else     *error   = GBT_link_tree(tree, gb_main, false, NULp, NULp);
1691
1692        if (!*error) {
1693            GBDATA *gb_tree = GBT_find_tree(gb_main, tree_name);
1694            if (!gb_tree) {
1695                *error = GBS_global_string("Can't find tree '%s'", tree_name);
1696            }
1697            else {
1698                cached_taxonomy *ct            = ARB_alloc<cached_taxonomy>(1);
1699                long             nodes         = GBT_count_leafs(tree);
1700                int              group_counter = 0;
1701
1702                ct->tree_name = ARB_strdup(tree_name);
1703                ct->taxonomy  = GBS_create_dynaval_hash(int(nodes), GB_IGNORE_CASE, GBS_dynaval_free);
1704                ct->groups    = 0; // counted below
1705
1706                build_taxonomy_rek(tree, ct->taxonomy, "<root>", &group_counter);
1707                cached = (long)ct;
1708                GBS_write_hash(cached_taxonomies, tree_name, (long)ct);
1709
1710                GB_remove_all_callbacks_to(gb_tree, GB_CB_SON_CREATED, CASTSIG(GB_CB, flush_taxonomy_if_new_group_cb));
1711                GB_add_callback(gb_tree, GB_CB_SON_CREATED, makeDatabaseCallback(flush_taxonomy_if_new_group_cb, ct));
1712
1713                {
1714                    GBDATA *gb_tree_entry = GB_entry(gb_tree, "tree");
1715                    GBDATA *gb_group_node;
1716
1717                    if (gb_tree_entry) {
1718                        GB_remove_all_callbacks_to(gb_tree_entry, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1719                        GB_add_callback(gb_tree_entry, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(flush_taxonomy_cb, ct));
1720                    }
1721
1722                    // add callbacks for all node/group_name subentries
1723                    for (gb_group_node = GB_entry(gb_tree, "node");
1724                         gb_group_node;
1725                         gb_group_node = GB_nextEntry(gb_group_node))
1726                    {
1727                        GBDATA *gb_group_name = GB_entry(gb_group_node, "group_name");
1728                        if (gb_group_name) { // group with id = 0 has no name
1729                            GB_remove_all_callbacks_to(gb_group_name, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1730                            GB_add_callback(gb_group_name, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(flush_taxonomy_cb, ct));
1731                            ct->groups++;
1732                        }
1733                    }
1734                }
1735#ifdef DUMP_TAXONOMY_CACHING
1736                fprintf(stderr, "Created taxonomy hash for '%s' (ct=%p)\n", tree_name, ct);
1737#endif // DUMP_TAXONOMY_CACHING
1738            }
1739        }
1740
1741        destroy(tree);
1742    }
1743
1744    if (!*error) {
1745        cached_taxonomy *ct = (cached_taxonomy*)cached;
1746        gb_assert(ct);
1747        return ct;
1748    }
1749
1750    return NULp;
1751}
1752
1753static char *get_taxonomy_string(GB_HASH *tax_hash, const char *group_key, int depth, GB_ERROR *error) {
1754    long  found;
1755    char *result = NULp;
1756
1757    gb_assert(depth>0);
1758    gb_assert(!(group_key[0] == '>' && group_key[1] == '>')); // internal group-pointers not allowed here!
1759
1760    found = GBS_read_hash(tax_hash, group_key);
1761    if (found) {
1762        const char *parent_group_key            = (const char *)found;
1763        if (strcmp(parent_group_key, "<root>") == 0) { // root reached
1764            result = ARB_strdup(group_key+(GROUP_COUNT_CHARS+1)); // return own group name
1765        }
1766        else {
1767            if (depth>1) {
1768                char *parent_name = get_taxonomy_string(tax_hash, parent_group_key, depth-1, error);
1769                if (parent_name) {
1770                    result = GBS_global_string_copy("%s/%s", parent_name, group_key+(GROUP_COUNT_CHARS+1));
1771                    free(parent_name);
1772                }
1773                else {
1774                    *error = GBS_global_string("In get_taxonomy_string(%s): %s", group_key, *error);
1775                    result = NULp;
1776                }
1777            }
1778            else {
1779                result = ARB_strdup(group_key+(GROUP_COUNT_CHARS+1)); // return own group name
1780            }
1781        }
1782    }
1783    else {
1784        *error = GBS_global_string("Not in tax_hash: '%s'", group_key);
1785    }
1786    return result;
1787}
1788
1789static const char *get_taxonomy(GBDATA *gb_species_or_group, const char *tree_name, bool is_current_tree, int depth, GB_ERROR *error) {
1790    GBDATA          *gb_main = GB_get_root(gb_species_or_group);
1791    cached_taxonomy *tax     = get_cached_taxonomy(gb_main, tree_name, error);
1792    const char      *result  = NULp;
1793
1794    if (tax) {
1795        GBDATA *gb_name       = GB_entry(gb_species_or_group, "name");
1796        GBDATA *gb_group_name = GB_entry(gb_species_or_group, "group_name");
1797
1798        if (gb_name && !gb_group_name) { // it's a species
1799            char *name = GB_read_string(gb_name);
1800            if (name) {
1801                GB_HASH *tax_hash = tax->taxonomy;
1802                long     found    = GBS_read_hash(tax_hash, GBS_global_string("!%s", name));
1803
1804                if (found) {
1805                    const char *parent_group = (const char *)found;
1806
1807                    if (strcmp(parent_group, "<root>") == 0) {
1808                        result = ""; // not member of any group
1809                    }
1810                    else {
1811                        static char *parent = NULp;
1812
1813                        freeset(parent, get_taxonomy_string(tax_hash, parent_group, depth, error));
1814                        result = parent;
1815                    }
1816                }
1817                else {
1818                    result = GBS_global_string("Species '%s' not in '%s'", name, tree_name);
1819                }
1820                free(name);
1821            }
1822            else {
1823                *error = GBS_global_string("Species without 'name' entry!");
1824            }
1825        }
1826        else if (gb_group_name && !gb_name) { // it's a group
1827            char *group_name = GB_read_string(gb_group_name);
1828            if (group_name) {
1829                if (is_current_tree) {
1830                    GB_HASH *tax_hash = tax->taxonomy;
1831                    long     found    = GBS_read_hash(tax_hash, GBS_global_string(">>%p", gb_species_or_group));
1832
1833                    if (found) {
1834                        static char *full_group = NULp;
1835                        const char  *group_id   = (const char *)found;
1836
1837                        freeset(full_group, get_taxonomy_string(tax_hash, group_id, depth, error));
1838                        result = full_group;
1839                    }
1840                    else {
1841                        result = GBS_global_string("Group '%s' not in '%s'", group_name, tree_name);
1842                    }
1843                }
1844                else {
1845                    *error = "It's not possible to specify the tree name in taxonomy() for groups";
1846                }
1847                free(group_name);
1848            }
1849            else {
1850                *error = "Group without 'group_name' entry";
1851            }
1852        }
1853        else if (gb_group_name) {
1854            *error = "Container has 'name' and 'group_name' entry - can't detect container type";
1855        }
1856        else {
1857            *error = "Container has neither 'name' nor 'group_name' entry - can't detect container type";
1858        }
1859    }
1860
1861    return result;
1862}
1863
1864static GB_ERROR gbl_taxonomy(GBL_command_arguments *args) {
1865    GB_ERROR error = check_optional_parameters(args, 1, "count", 1, "tree_name", false, true);
1866    if (!error) {
1867        EXPECT_ITEM_REFERENCED(args);
1868        COMMAND_DROPS_INPUT_STREAMS(args);
1869
1870        char *tree_name       = NULp;
1871        bool  is_current_tree = false;
1872        int   depth           = -1;
1873        char *result          = NULp;
1874
1875        if (args->param_count() == 1) {   // only 'depth'
1876            if (!args->get_treename()) {
1877                result = ARB_strdup("No default tree");
1878            }
1879            else {
1880                tree_name = ARB_strdup(args->get_treename());
1881                depth = atoi(args->get_param(0));
1882                is_current_tree = true;
1883            }
1884        }
1885        else { // 'tree_name', 'depth'
1886            tree_name = ARB_strdup(args->get_param(0));
1887            depth     = atoi(args->get_param(1));
1888        }
1889
1890        if (!result) {
1891            if (depth<1) {
1892                error = GBS_global_string("Illegal depth '%i' (allowed 1..n)", depth);
1893            }
1894            if (!error) {
1895                const char *taxonomy_string = get_taxonomy(args->get_item_ref(), tree_name, is_current_tree, depth, &error);
1896                if (taxonomy_string) result = ARB_strdup(taxonomy_string);
1897            }
1898        }
1899
1900        gb_assert(contradicted(result, error));
1901        if (result) PASS_2_OUT(args, result);
1902        free(tree_name);
1903    }
1904    return error;
1905}
1906
1907static GB_ERROR gbl_sequence(GBL_command_arguments *args) {
1908    EXPECT_ITEM_REFERENCED(args);
1909    COMMAND_DROPS_INPUT_STREAMS(args);
1910
1911    GB_ERROR error = check_no_parameter(args);
1912    if (!error) {
1913        switch (identify_gb_item(args->get_item_ref())) {
1914            case GBT_ITEM_UNKNOWN: {
1915                error = "'sequence' used for unknown item";
1916                break;
1917            }
1918            case GBT_ITEM_SPECIES: {
1919                char *use = GBT_get_default_alignment(args->get_gb_main());
1920
1921                if (!use) {
1922                    error = GB_have_error() ? GB_await_error() : "no default alignment defined";
1923                }
1924                else {
1925                    GBDATA *gb_seq = GBT_find_sequence(args->get_item_ref(), use);
1926
1927                    if (gb_seq) PASS_2_OUT(args, GB_read_string(gb_seq));
1928                    else        COPY_2_OUT(args, ""); // if current alignment does not exist -> return empty string
1929
1930                    free(use);
1931                }
1932                break;
1933            }
1934            case GBT_ITEM_GENE: {
1935                char *seq = GBT_read_gene_sequence(args->get_item_ref(), true, 0);
1936
1937                if (!seq) error = GB_await_error();
1938                else PASS_2_OUT(args, seq);
1939
1940                break;
1941            }
1942        }
1943    }
1944    return error;
1945}
1946
1947static GB_ERROR gbl_export_sequence(GBL_command_arguments *args) {
1948    EXPECT_ITEM_REFERENCED(args);
1949    COMMAND_DROPS_INPUT_STREAMS(args);
1950
1951    GB_ERROR error = check_no_parameter(args);
1952    if (!error) {
1953        switch (identify_gb_item(args->get_item_ref())) {
1954            case GBT_ITEM_UNKNOWN: {
1955                error = "'export_sequence' used for unknown item";
1956                break;
1957            }
1958            case GBT_ITEM_SPECIES: {
1959                if (!get_export_sequence) {
1960                    error = "No export-sequence-hook defined (can't use 'export_sequence' here)";
1961                }
1962                else {
1963                    size_t      len;
1964                    const char *seq = get_export_sequence(args->get_item_ref(), &len, &error);
1965
1966                    gb_assert(error || seq);
1967
1968                    if (seq) PASS_2_OUT(args, ARB_strduplen(seq, len));
1969                }
1970                break;
1971            }
1972            case GBT_ITEM_GENE: {
1973                error = "'export_sequence' cannot be used for gene";
1974                break;
1975            }
1976        }
1977    }
1978    return error;
1979}
1980
1981static GB_ERROR gbl_ali_name(GBL_command_arguments *args) {
1982    COMMAND_DROPS_INPUT_STREAMS(args);
1983
1984    GB_ERROR error = check_no_parameter(args);
1985    if (!error) {
1986        GBDATA *gb_main = args->get_gb_main();
1987        char   *use     = GBT_get_default_alignment(gb_main);
1988        PASS_2_OUT(args, use);
1989    }
1990    return error;
1991}
1992
1993static GB_ERROR gbl_sequence_type(GBL_command_arguments *args) {
1994    COMMAND_DROPS_INPUT_STREAMS(args);
1995
1996    GB_ERROR error = check_no_parameter(args);
1997    if (!error) {
1998        GBDATA *gb_main = args->get_gb_main();
1999        char   *use     = GBT_get_default_alignment(gb_main);
2000        PASS_2_OUT(args, GBT_get_alignment_type_string(gb_main, use));
2001        free(use);
2002    }
2003
2004    return error;
2005}
2006
2007static GB_ERROR format(GBL_command_arguments *args, bool simple_format) {
2008    // simple_format: true = "format", false="format_sequence"
2009
2010    GB_ERROR error = NULp;
2011    int      ic;
2012
2013    GBL_BEGIN_PARAMS;
2014    GBL_PARAM_INT(firsttab, "firsttab=", 10, "Indent first line");
2015    GBL_PARAM_INT(tab,      "tab=",      10, "Indent not first line");
2016    GBL_PARAM_INT(width,    "width=",    50, "Sequence width (bases only)");
2017
2018    // "format_sequence"-only
2019    GBL_PARAM_BIT (numleft,  PARAM_IF(!simple_format, "numleft"),  0,  "Numbers left of sequence");
2020    GBL_PARAM_INT (numright, PARAM_IF(!simple_format, "numright="), 0, "Numbers right of sequence (specifies width; -1 -> auto-width)");
2021    GBL_PARAM_UINT(gap,      PARAM_IF(!simple_format, "gap="),     10, "Insert ' ' every n sequence characters");
2022
2023    // "format"-only
2024    GBL_PARAM_STRING(nl,      PARAM_IF(simple_format, "nl="),      " ",  "Break line at characters 'str' if wrapping needed");
2025    GBL_PARAM_STRING(forcenl, PARAM_IF(simple_format, "forcenl="), "\n", "Always break line at characters 'str'");
2026
2027    GBL_TRACE_PARAMS(args);
2028    GBL_END_PARAMS;
2029
2030    if (tab      < 0) tab = 0;
2031    if (firsttab < 0) firsttab = 0;
2032
2033    if (width == 0)               return "Illegal zero width";
2034    if (numleft && numright != 0) return "You may only specify 'numleft' OR 'numright',  not both.";
2035
2036    if (gap<1) gap = UINT_MAX;
2037
2038    for (ic = 0; ic<args->input.size(); ++ic) {
2039        const char *src           = args->input.get(ic);
2040        size_t      data_size     = strlen(src);
2041        size_t      needed_size;
2042        size_t      line_size;
2043        int         numright_used = numright;
2044
2045        if (numright_used<0) {
2046            numright_used = calc_digits(data_size);
2047        }
2048
2049        {
2050            size_t lines;
2051
2052            if (simple_format) {
2053                lines     = data_size/2 + 1; // worst case
2054                line_size = tab + (width>0 ? width : data_size) + 1;
2055            }
2056            else {
2057                size_t gapsPerLine = (width-1)/gap;
2058                lines              = data_size/width+1;
2059                line_size          = tab + width + gapsPerLine + 1;
2060
2061                if (numright_used) {
2062                    // add space for numright
2063                    line_size += numright_used+1; // plus space
2064                }
2065            }
2066
2067            needed_size = lines*line_size + firsttab + 1 + 10;
2068        }
2069
2070        char *result = ARB_alloc<char>(needed_size);
2071        if (!result) {
2072            error = GBS_global_string("Out of memory (tried to alloc %zu bytes)", needed_size);
2073        }
2074        else {
2075            char   *dst       = result;
2076            size_t  rest_data = data_size;
2077
2078            if (simple_format) {
2079                /* format string w/o gaps or numleft
2080                 * does word-wrapping at chars in nl
2081                 */
2082
2083                // build wrap table
2084                unsigned char isWrapChar[256];
2085                memset(isWrapChar, 0, sizeof(isWrapChar));
2086                for (int i = 0; nl[i]; ++i) isWrapChar[(unsigned char)nl[i]] = 1;
2087                for (int i = 0; forcenl[i]; ++i) isWrapChar[(unsigned char)forcenl[i]] = 2;
2088
2089                if (firsttab>0) {
2090                    memset(dst, ' ', firsttab);
2091                    dst += firsttab;
2092                }
2093
2094                while (width>0 && rest_data>unsigned(width)) {
2095                    int take;
2096                    int move;
2097                    int took;
2098
2099                    for (take = width; take > 0; --take) {
2100                        if (isWrapChar[(unsigned char)src[take]]) break;
2101                    }
2102                    if (take <= 0) { // no wrap character found -> hard wrap at width
2103                        take  = move = width;
2104                    }
2105                    else { // soft wrap at last found wrap character
2106                        move = take+1;
2107                    }
2108
2109                    for (took = 0; took<take; took++) {
2110                        char c = src[took];
2111                        if (isWrapChar[(unsigned char)c] == 2) { // forced newline
2112                            take = took;
2113                            move = take+1;
2114                            break;
2115                        }
2116                        dst[took] = c;
2117                    }
2118
2119                    dst       += take;
2120                    src       += move;
2121                    rest_data -= move;
2122
2123                    if (rest_data>0) {
2124                        *dst++ = '\n';
2125                        if (tab>0) {
2126                            memset(dst, ' ', tab);
2127                            dst += tab;
2128                        }
2129                    }
2130                }
2131
2132                if (rest_data>0) {
2133                    size_t j, k;
2134                    for (j = 0, k = 0; j<rest_data; ++j) {
2135                        char c = src[j];
2136
2137                        if (isWrapChar[(unsigned char)c] == 2) {
2138                            dst[k++] = '\n';
2139                            if (tab>0) {
2140                                memset(dst+k, ' ', tab);
2141                                k += tab;
2142                            }
2143                        }
2144                        else {
2145                            dst[k++] = c;
2146                        }
2147                    }
2148                    src       += j;
2149                    dst       += k;
2150                    rest_data  = 0;
2151                }
2152            }
2153            else {
2154                // "format_sequence" with gaps and numleft
2155                char       *format        = NULp;
2156                const char *src_start     = src;
2157                const char *dst_linestart = dst;
2158
2159                if (numleft) {
2160                    /* Warning: Be very careful, when you change format strings here!
2161                     * currently all format strings result in '%u' or '%-##u' (where # are digits)
2162                     */
2163                    if (firsttab>0) {
2164                        char *firstFormat = GBS_global_string_copy("%%-%iu ", firsttab-1);
2165                        dst += sprintf(dst, firstFormat, (unsigned)1);
2166                        free(firstFormat);
2167                    }
2168                    else {
2169                        dst += sprintf(dst, "%u ", (unsigned)1);
2170                    }
2171                    format = tab>0 ? GBS_global_string_copy("%%-%iu ", tab-1) : ARB_strdup("%u ");
2172                }
2173                else if (firsttab>0) {
2174                    memset(dst, ' ', firsttab);
2175                    dst += firsttab;
2176                }
2177
2178                while (rest_data>0) {
2179                    size_t take = (width>0 && rest_data>unsigned(width)) ? width : rest_data;
2180
2181                    rest_data -= take;
2182
2183                    while (take>gap) {
2184                        memcpy(dst, src, gap);
2185                        dst  += gap;
2186                        src  += gap;
2187                        *dst++ = ' ';
2188                        take -= gap;
2189                    }
2190
2191                    memcpy(dst, src, take);
2192                    dst += take;
2193                    src += take;
2194
2195                    if (numright_used) {
2196                        if (rest_data) *dst++ = ' ';
2197                        else {
2198                            // fill in missing spaces for proper alignment of numright
2199                            size_t currSize = dst-dst_linestart;
2200                            size_t wantSize = line_size-numright_used-1;
2201                            if (currSize<wantSize) {
2202                                size_t spaces  = wantSize-currSize;
2203                                memset(dst, ' ', spaces);
2204                                dst           += spaces;
2205                            }
2206                        }
2207                        unsigned int num  = (src-src_start);
2208                        dst              += sprintf(dst, "%*u", numright_used, num);
2209                    }
2210
2211                    if (rest_data>0) {
2212                        *dst++ = '\n';
2213                        dst_linestart = dst;
2214                        if (numleft) {
2215                            unsigned int num  = (src-src_start)+1; // this goes to the '%u' (see comment above)
2216                            dst              += sprintf(dst, format, num);
2217                        }
2218                        else if (tab>0) {
2219                            memset(dst, ' ', tab);
2220                            dst += tab;
2221                        }
2222                    }
2223                }
2224
2225                free(format);
2226            }
2227
2228            *dst++ = 0;         // close str
2229
2230#if defined(DEBUG)
2231            { // check for array overflow
2232                size_t used_size = dst-result;
2233                gb_assert(used_size <= needed_size);
2234                ARB_realloc(result, used_size);
2235            }
2236#endif // DEBUG
2237        }
2238
2239        if (!error) PASS_2_OUT(args, result);
2240        else free(result);
2241    }
2242    return error;
2243}
2244
2245static GB_ERROR gbl_format         (GBL_command_arguments *args) { return format(args, true); }
2246static GB_ERROR gbl_format_sequence(GBL_command_arguments *args) { return format(args, false); }
2247
2248
2249static char *gbl_read_seq_sai_or_species(GBDATA *gb_main, const char *species, const char *sai, const char *ali, size_t *seqLen) {
2250    /* Reads the alignment 'ali'  of 'species' or 'sai'.
2251     * If 'ali' is NULp, use default alignment.
2252     * Returns NULp in case of error (which is exported then)
2253     */
2254
2255    char     *seq   = NULp;
2256    GB_ERROR  error = NULp;
2257
2258    int sources = !!species + !!sai;
2259    if (sources != 1) {
2260        error = "Either parameters 'species' or 'SAI' must be specified";
2261    }
2262    else {
2263        GBDATA     *gb_item = NULp;
2264        const char *what    = NULp;
2265        const char *name    = NULp;
2266
2267        if (species) {
2268            gb_item = GBT_find_species(gb_main, species);
2269            what    = "species";
2270            name    = species;
2271        }
2272        else {
2273            gb_item = GBT_find_SAI(gb_main, sai);
2274            what    = "SAI";
2275            name    = sai;
2276        }
2277
2278        if (!gb_item) error = GBS_global_string("Can't find %s '%s'", what, name);
2279        else {
2280            char *freeMe = NULp;
2281
2282            if (!ali) {
2283                ali = freeMe = GBT_get_default_alignment(gb_main);
2284                if (!ali) error = "can't detect default alignment";
2285            }
2286
2287            if (ali) {
2288                GBDATA *gb_ali = GB_entry(gb_item, ali);
2289
2290                if (gb_ali) {
2291                    GBDATA *gb_seq;
2292
2293                    for (gb_seq = GB_child(gb_ali); gb_seq; gb_seq = GB_nextChild(gb_seq)) {
2294                        long type = GB_read_type(gb_seq);
2295                        if (type == GB_BITS) {
2296                            seq     = GB_read_bits(gb_seq, '-', '+');
2297                            if (seqLen) *seqLen = GB_read_bits_count(gb_seq);
2298                            break;
2299                        }
2300                        if (type == GB_STRING) {
2301                            seq     = GB_read_string(gb_seq);
2302                            if (seqLen) *seqLen = GB_read_string_count(gb_seq);
2303                            break;
2304                        }
2305                    }
2306                }
2307
2308                if (!seq) error = GBS_global_string("%s '%s' has no (usable) data in alignment '%s'", what, name, ali);
2309            }
2310            free(freeMe);
2311        }
2312    }
2313
2314    if (error) {
2315        gb_assert(!seq);
2316        GB_export_error(error);
2317    }
2318
2319    return seq;
2320}
2321
2322struct common_filter_params {
2323    const char *align;
2324    const char *sai;
2325    const char *species;
2326    int         first;
2327    int         pairwise;
2328};
2329
2330#define GBL_COMMON_FILTER_PARAMS                                                                                        \
2331    common_filter_params common_param;                                                                                  \
2332    GBL_STRUCT_PARAM_STRING(common_param, align,    "align=",    NULp, "alignment to use (defaults to default alignment)"); \
2333    GBL_STRUCT_PARAM_STRING(common_param, sai,      "SAI=",      NULp, "Use default sequence of given SAI as a filter"); \
2334    GBL_STRUCT_PARAM_STRING(common_param, species,  "species=",  NULp, "Use default sequence of given species as a filter"); \
2335    GBL_STRUCT_PARAM_BIT   (common_param, first,    "first=",    0,    "Use 1st stream as filter for other streams");   \
2336    GBL_STRUCT_PARAM_BIT   (common_param, pairwise, "pairwise=", 0,    "Use 1st stream as filter for 2nd, 3rd for 4th, ...")
2337
2338typedef char* (*filter_fun)(const char *seq, const char *filter, size_t flen, void *param);
2339/* Note:
2340 * filter_fun has to return a heap copy of the filter-result.
2341 * if 'flen' != 0, it contains the length of 'filter'
2342 * 'param' may be any client data
2343 */
2344
2345static GB_ERROR apply_filters(GBL_command_arguments *args, common_filter_params *common, filter_fun filter_one, void *param) {
2346    GB_ERROR error = NULp;
2347
2348    if (args->input.size()==0) error = "No input stream";
2349    else {
2350        int methodCount = !!common->sai + !!common->species + !!common->pairwise + !!common->first;
2351
2352        if (methodCount != 1) error = "Need exactly one of the parameters 'SAI', 'species', 'pairwise' or 'first'";
2353        else {
2354            if (common->pairwise) {
2355                if (args->input.size() % 2) error = "Using 'pairwise' requires an even number of input streams";
2356                else {
2357                    int i;
2358                    for (i = 1; i<args->input.size(); i += 2) {
2359                        PASS_2_OUT(args, filter_one(args->input.get(i), args->input.get(i-1), 0, param));
2360                    }
2361                }
2362            }
2363            else {
2364                int     i      = 0;
2365                char   *filter = NULp;
2366                size_t  flen   = 0;
2367
2368                if (common->first) {
2369                    if (args->input.size()<2) error = "Using 'first' needs at least 2 input streams";
2370                    else {
2371                        const char *in = args->input.get(i++);
2372                        gb_assert(in);
2373
2374                        flen   = strlen(in);
2375                        filter = ARB_strduplen(in, flen);
2376                    }
2377                }
2378                else {
2379                    filter = gbl_read_seq_sai_or_species(args->get_gb_main(), common->species, common->sai, common->align, &flen);
2380                    if (!filter) error = GB_await_error();
2381                }
2382
2383                gb_assert(filter || error);
2384                if (filter) {
2385                    for (; i<args->input.size(); ++i) {
2386                        PASS_2_OUT(args, filter_one(args->input.get(i), filter, flen, param));
2387                    }
2388                }
2389                free(filter);
2390            }
2391        }
2392    }
2393    return error;
2394}
2395
2396// -------------------------
2397//      calculate diff
2398
2399struct diff_params {
2400    char equalC;
2401    char diffC;
2402};
2403static char *calc_diff(const char *seq, const char *filter, size_t /*flen*/, void *paramP) {
2404    // filters 'seq' through 'filter'
2405    // - replace all equal     positions by 'equal_char' (if != 0)
2406    // - replace all differing positions by 'diff_char'  (if != 0)
2407
2408    diff_params *param      = (diff_params*)paramP;
2409    char         equal_char = param->equalC;
2410    char         diff_char  = param->diffC;
2411
2412    char *result = ARB_strdup(seq);
2413    int   p;
2414
2415    for (p = 0; result[p] && filter[p]; ++p) {
2416        if (result[p] == filter[p]) {
2417            if (equal_char) result[p] = equal_char;
2418        }
2419        else {
2420            if (diff_char) result[p] = diff_char;
2421        }
2422    }
2423
2424    // if 'seq' is longer than 'filter' and diff_char is given
2425    // -> fill rest of 'result' with 'diff_char'
2426    if (diff_char) {
2427        for (; result[p]; ++p) {
2428            result[p] = diff_char;
2429        }
2430    }
2431
2432    return result;
2433}
2434static GB_ERROR gbl_diff(GBL_command_arguments *args) {
2435    GBL_BEGIN_PARAMS;
2436    GBL_COMMON_FILTER_PARAMS;
2437
2438    diff_params param;
2439    GBL_STRUCT_PARAM_CHAR(param, equalC,   "equal=",    '.', "symbol for equal characters");
2440    GBL_STRUCT_PARAM_CHAR(param, diffC,    "differ=",   0,   "symbol for diff characters (default: use char from input stream)");
2441
2442    GBL_TRACE_PARAMS(args);
2443    GBL_END_PARAMS;
2444
2445    return apply_filters(args, &common_param, calc_diff, &param);
2446}
2447
2448// -------------------------
2449//      standard filter
2450
2451enum filter_function { FP_FILTER, FP_MODIFY };
2452
2453struct filter_params { // used by gbl_filter and gbl_change_gc
2454    filter_function function;
2455
2456    const char *include;
2457    const char *exclude;
2458
2459    // FP_MODIFY only:
2460    int         change_pc;
2461    const char *change_to;
2462};
2463
2464static char *filter_seq(const char *seq, const char *filter, size_t flen, void *paramP) {
2465    filter_params *param = (filter_params*)paramP;
2466
2467    size_t slen     = strlen(seq);
2468    if (!flen) flen = strlen(filter);
2469    size_t mlen     = slen<flen ? slen : flen;
2470
2471    GBS_strstruct *out = GBS_stropen(mlen+1); // +1 to avoid invalid, zero-length buffer
2472
2473    const char *charset;
2474    int         include;
2475
2476    if (param->include) {
2477        charset = param->include;
2478        include = 1;
2479    }
2480    else {
2481        gb_assert(param->exclude);
2482        charset = param->exclude;
2483        include = 0;
2484    }
2485
2486    size_t pos  = 0;
2487    size_t rest = slen;
2488    size_t ctl  = 0;
2489    if (param->function == FP_MODIFY) ctl  = strlen(param->change_to);
2490
2491    int inset = 1; // 1 -> check chars in charset, 0 -> check chars NOT in charset
2492    while (rest) {
2493        size_t count;
2494        if (pos >= flen) {      // behind filter
2495            // trigger last loop
2496            count = rest;
2497            inset = 0; // if 'include' -> 'applies' will get false, otherwise true
2498                       // (meaning is: behind filter nothing can match 'include' or 'exclude')
2499        }
2500        else {
2501            count = (inset ? strspn : strcspn)(filter+pos, charset); // count how many chars are 'inset'
2502        }
2503        if (count) {
2504            int applies = !!include == !!inset; // true -> 'filter' matches 'include' or doesn't match 'exclude'
2505            if (count>rest) count = rest;
2506
2507            switch (param->function) {
2508                case FP_FILTER:
2509                    if (applies) GBS_strncat(out, seq+pos, count);
2510                    break;
2511
2512                case FP_MODIFY:
2513                    if (applies) { // then modify
2514                        size_t i;
2515                        for (i = 0; i<count; i++) {
2516                            char c = seq[pos+i];
2517                            if (isalpha(c) && GB_random(100)<param->change_pc) c = param->change_to[GB_random(ctl)];
2518                            GBS_chrcat(out, c);
2519                        }
2520                    }
2521                    else { // otherwise simply copy
2522                        GBS_strncat(out, seq+pos, count);
2523                    }
2524                    break;
2525            }
2526
2527            pos  += count;
2528            rest -= count;
2529        }
2530        inset = 1-inset; // toggle
2531    }
2532    return GBS_strclose(out);
2533}
2534
2535static GB_ERROR gbl_filter(GBL_command_arguments *args) {
2536    GBL_BEGIN_PARAMS;
2537    GBL_COMMON_FILTER_PARAMS;
2538
2539    filter_params param;
2540    GBL_STRUCT_PARAM_STRING(param, exclude, "exclude=", NULp, "Exclude colums");
2541    GBL_STRUCT_PARAM_STRING(param, include, "include=", NULp, "Include colums");
2542    param.function = FP_FILTER;
2543
2544    GBL_TRACE_PARAMS(args);
2545    GBL_END_PARAMS;
2546
2547    GB_ERROR error  = NULp;
2548    int      inOrEx = !!param.include + !!param.exclude;
2549
2550    if (inOrEx != 1)    error = "Need exactly one parameter of: 'include', 'exclude'";
2551    else error                = apply_filters(args, &common_param, filter_seq, &param);
2552
2553    return error;
2554}
2555
2556static GB_ERROR gbl_change_gc(GBL_command_arguments *args) {
2557    GBL_BEGIN_PARAMS;
2558    GBL_COMMON_FILTER_PARAMS;
2559
2560    filter_params param;
2561    GBL_STRUCT_PARAM_STRING(param, exclude,   "exclude=", NULp, "Exclude colums");
2562    GBL_STRUCT_PARAM_STRING(param, include,   "include=", NULp, "Include colums");
2563    GBL_STRUCT_PARAM_INT   (param, change_pc, "change=",  0,    "percentage of changed columns (default: silently change nothing)");
2564    GBL_STRUCT_PARAM_STRING(param, change_to, "to=",      "GC", "change to one of this");
2565    param.function = FP_MODIFY;
2566
2567    GBL_TRACE_PARAMS(args);
2568    GBL_END_PARAMS;
2569
2570    GB_ERROR error  = NULp;
2571    int      inOrEx = !!param.include + !!param.exclude;
2572
2573    if (inOrEx != 1) error = "Need exactly one parameter of: 'include', 'exclude'";
2574    else {
2575        error = apply_filters(args, &common_param, filter_seq, &param);
2576    }
2577
2578    return error;
2579}
2580
2581static GB_ERROR gbl_exec(GBL_command_arguments *args) {
2582    EXPECT_PARAMS_PASSED(args, "command[,arguments]+");
2583
2584    // write inputstreams to temp file:
2585    GB_ERROR error = NULp;
2586    char *inputname;
2587    int i;
2588    {
2589        char *filename = GB_unique_filename("arb_exec_input", "tmp");
2590        FILE *out      = GB_fopen_tempfile(filename, "wt", &inputname);
2591
2592        if (!out) error = GB_await_error();
2593        else {
2594            for (i=0; i<args->input.size(); i++) {
2595                fprintf(out, "%s\n", args->input.get(i));
2596            }
2597            fclose(out);
2598        }
2599        free(filename);
2600    }
2601
2602    if (!error) {
2603        // build shell command to execute
2604        char *sys;
2605        {
2606            GBS_strstruct *str = GBS_stropen(1000);
2607
2608            GBS_strcat(str, args->get_param(0));
2609            for (i=1; i<args->param_count(); i++) {
2610                GBS_strcat(str, " \'");
2611                GBS_strcat(str, args->get_param(i)); // @@@ use GBK_singlequote here?
2612                GBS_chrcat(str, '\'');
2613            }
2614            GBS_strcat(str, " <");
2615            GBS_strcat(str, inputname);
2616
2617            sys = GBS_strclose(str);
2618        }
2619
2620        char *result = NULp;
2621        {
2622            FILE *in = popen(sys, "r");
2623            if (in) {
2624                GBS_strstruct *str = GBS_stropen(4096);
2625
2626                while ((i=getc(in)) != EOF) { GBS_chrcat(str, i); }
2627                result = GBS_strclose(str);
2628                pclose(in);
2629            }
2630            else {
2631                error = GBS_global_string("Cannot execute shell command '%s'", sys);
2632            }
2633        }
2634
2635        if (!error) {
2636            gb_assert(result);
2637            PASS_2_OUT(args, result);
2638        }
2639
2640        free(sys);
2641    }
2642
2643    gb_assert(GB_is_privatefile(inputname, false));
2644    GB_unlink_or_warn(inputname, &error);
2645    free(inputname);
2646
2647    return error;
2648}
2649
2650
2651static GBL_command_definition gbl_command_table[] = {
2652    { "ali_name",        gbl_ali_name },
2653    { "caps",            gbl_caps },
2654    { "change",          gbl_change_gc },
2655    { "checksum",        gbl_checksum },
2656    { "command",         gbl_command },
2657    { "compare",         gbl_compare },
2658    { "colsplit",        gbl_colsplit },
2659    { "icompare",        gbl_icompare },
2660    { "contains",        gbl_contains },
2661    { "icontains",       gbl_icontains },
2662    { "count",           gbl_count },
2663    { "crop",            gbl_crop },
2664    { "cut",             gbl_cut },
2665    { "dd",              gbl_dd },
2666    { "define",          gbl_define },
2667    { "diff",            gbl_diff },
2668    { "div",             gbl_div },
2669    { "fdiv",            gbl_fdiv },
2670    { "do",              gbl_do },
2671    { "drop",            gbl_drop },
2672    { "dropempty",       gbl_dropempty },
2673    { "dropzero",        gbl_dropzero },
2674    { "echo",            gbl_echo },
2675    { "equals",          gbl_equals },
2676    { "iequals",         gbl_iequals },
2677    { "escape",          gbl_escape },
2678    { "unescape",        gbl_unescape },
2679    { "eval",            gbl_eval },
2680    { "exec",            gbl_exec },
2681    { "export_sequence", gbl_export_sequence },
2682    { "extract_sequence", gbl_extract_sequence },
2683    { "extract_words",   gbl_extract_words },
2684    { "filter",          gbl_filter },
2685    { "findspec",        gbl_findspec },
2686    { "findacc",         gbl_findacc },
2687    { "findgene",        gbl_findgene },
2688    { "format",          gbl_format },
2689    { "format_sequence", gbl_format_sequence },
2690    { "gcgchecksum",     gbl_gcgchecksum },
2691    { "head",            gbl_head },
2692    { "inRange",         gbl_inRange },
2693    { "isAbove",         gbl_isAbove },
2694    { "isBelow",         gbl_isBelow },
2695    { "isEqual",         gbl_isEqual },
2696    { "isEmpty",         gbl_isEmpty },
2697    { "keep",            gbl_keep },
2698    { "left",            gbl_head },
2699    { "len",             gbl_len },
2700    { "lower",           gbl_lower },
2701    { "merge",           gbl_merge },
2702    { "mid",             gbl_mid },
2703    { "mid0",            gbl_mid0 },
2704    { "minus",           gbl_minus },
2705    { "fminus",          gbl_fminus },
2706    { "mult",            gbl_mult },
2707    { "fmult",           gbl_fmult },
2708    { "and",             gbl_and },
2709    { "or",              gbl_or },
2710    { "not",             gbl_not },
2711    { "origin_gene",     gbl_origin_gene },
2712    { "origin_organism", gbl_origin_organism },
2713    { "partof",          gbl_partof },
2714    { "ipartof",         gbl_ipartof },
2715    { "per_cent",        gbl_per_cent },
2716    { "fper_cent",       gbl_fper_cent },
2717    { "plus",            gbl_plus },
2718    { "fplus",           gbl_fplus },
2719    { "pretab",          gbl_pretab },
2720    { "quote",           gbl_quote },
2721    { "unquote",         gbl_unquote },
2722    { "readdb",          gbl_readdb },
2723    { "remove",          gbl_remove },
2724    { "rest",            gbl_rest },
2725    { "right",           gbl_tail },
2726    { "round",           gbl_round },
2727    { "select",          gbl_select },
2728    { "sequence",        gbl_sequence },
2729    { "sequence_type",   gbl_sequence_type },
2730    { "split",           gbl_split },
2731    { "srt",             gbl_srt },
2732    { "streams",         gbl_streams },
2733    { "swap",            gbl_swap },
2734    { "tab",             gbl_tab },
2735    { "tail",            gbl_tail },
2736    { "taxonomy",        gbl_taxonomy },
2737    { "toback",          gbl_toback },
2738    { "tofront",         gbl_tofront },
2739    { "trace",           gbl_trace },
2740    { "translate",       gbl_translate },
2741    { "upper",           gbl_upper },
2742
2743    { NULp, NULp }
2744};
2745
2746const GBL_command_lookup_table& ACI_get_standard_commands() {
2747    static GBL_command_lookup_table clt(gbl_command_table, ARRAY_ELEMS(gbl_command_table)-1);
2748    return clt;
2749}
2750
Note: See TracBrowser for help on using the repository browser.