source: branches/ali/ARBDB/adlang1.cxx

Last change on this file was 19366, checked in by westram, 18 months ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 94.8 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", SPLIT_DROPEMPTY);
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                for (para = ppara; para; para = para->next) pcount++;
130
131                gbl_param **params;
132                ARB_calloc(params, pcount);
133                {
134                    int k;
135                    for (k = 0, para = ppara; para; para = para->next) params[k++] = para;
136                }
137
138                GBS_strstruct str(1000);
139
140                for (pcount--; pcount>=0; pcount--) {
141                    para = params[pcount];
142                    if (para->param_name) {
143                        str.cat("  ");
144                        str.cat(para->param_name);
145                        switch (para->type) {
146                            case GB_STRING: str.cat("STRING"); break;
147                            case GB_INT:    str.cat("INT");    break;
148                            case GB_FLOAT:  str.cat("FLOAT");  break;
149                            case GB_BYTE:   str.cat("CHAR");   break;
150                            case GB_BIT:    str.cat("    ");   break;
151                            default:        str.cat("????"); gb_assert(0); break;
152                        }
153                        str.cat("\t\t;");
154                        str.cat(para->help_text);
155                        str.cat("\n");
156                    }
157                }
158
159                freenull(params);
160
161                return GB_export_errorf("Unknown Parameter '%s' in command '%s'\n  PARAMETERS:\n%s",
162                                        argument, com, str.get_data());
163            }
164        }
165
166        return error;
167    }
168};
169
170
171
172// -------------------------
173//      String functions
174
175static int gbl_stricmp(const char *s1, const char *s2) {
176    // case insensitive strcmp
177    int i;
178    for (i = 0; ; ++i) {
179        char c1 = tolower(s1[i]);
180        char c2 = tolower(s2[i]);
181
182        if (c1 == c2) {
183            if (!c1) break; // equal strings
184        }
185        else {
186            if (c1<c2) return -1;
187            return 1;
188        }
189    }
190    return 0;
191}
192static int gbl_strincmp(const char *s1, const char *s2, int size2) {
193    // case insensitive strcmp
194    int i;
195    for (i = 0; i<size2; ++i) {
196        char c1 = tolower(s1[i]);
197        char c2 = tolower(s2[i]);
198
199        if (c1 == c2) {
200            if (!c1) break; // equal strings
201        }
202        else {
203            if (c1<c2) return -1;
204            return 1;
205        }
206    }
207    return 0;
208}
209static const char *gbl_stristr(const char *haystack, const char *needle) {
210    // case insensitive strstr
211    const char *hp          = haystack;
212    char        c1          = toupper(needle[0]);
213    char        c2          = tolower(c1);
214    int         needle_size = strlen(needle);
215
216    if (c1 == c2) {
217        hp = strchr(hp, c1);
218        while (hp) {
219            if (gbl_strincmp(hp, needle, needle_size) == 0) return hp;
220            hp = strchr(hp+1, c1);
221        }
222    }
223    else {
224        while (hp) {
225            const char *h1 = strchr(hp, c1);
226            const char *h2 = strchr(hp, c2);
227
228            if (h1 && h2) {
229                if (h1<h2) {
230                    if (gbl_strincmp(h1, needle, needle_size) == 0) return h1;
231                    hp = h1+1;
232                }
233                else {
234                    gb_assert(h1>h2);
235                    if (gbl_strincmp(h2, needle, needle_size) == 0) return h2;
236                    hp = h2+1;
237                }
238            }
239            else {
240                if (h1) { hp = h1; }
241                else if (h2) { hp = h2; c1 = c2; }
242                else { hp = NULp; }
243
244                while (hp) {
245                    if (gbl_strincmp(hp, needle, needle_size) == 0) return hp;
246                    hp = strchr(hp+1, c1);
247                }
248            }
249        }
250    }
251    return NULp;
252}
253
254inline int approve_pos(int pos, int len) { return pos<0 ? (-pos<len ? len+pos : 0) : pos; }
255
256static GB_ERROR gbl_mid_streams(const GBL_streams& arg_input, GBL_streams& arg_out, int start, int end) {
257    // used as well to copy all streams (e.g. by 'dd')
258    for (int i=0; i<arg_input.size(); i++) {
259        const char *p   = arg_input.get(i);
260        int         len = strlen(p);
261
262        int s = approve_pos(start, len);
263        int e = approve_pos(end, len);
264
265        char *res;
266        if (s >= len || e<s) {
267            res = ARB_strdup("");
268        }
269        else {
270            gb_assert(s >= 0);
271            res = ARB_strpartdup(p+s, p+e);
272        }
273        arg_out.insert(res);
274    }
275    return NULp;
276}
277
278static GB_ERROR gbl_trace(GBL_command_arguments *args) {
279    int tmp_trace;
280
281    EXPECT_PARAMS(args, 1, "0|1");
282
283    tmp_trace = atoi(args->get_param(0));
284    if (tmp_trace<0 || tmp_trace>1) return GBS_global_string("Illegal value %i to trace", tmp_trace);
285
286    if (tmp_trace != traceACI) {
287        traceACI = 1;
288        print_trace(GBS_global_string("%sctivated ACI trace\n", tmp_trace ? "A" : "De-a"));
289        traceACI = tmp_trace;
290    }
291
292    return gbl_mid_streams(args->input, args->output, 0, -1); // copy all streams
293}
294
295/* ---------------------------------------------------------------------------------------
296 * Binary operators work on pairs of values.
297 * Three different operational modes are implemented for all binary operators:
298 *
299 * 1. inputstreams|operator
300 *
301 *    The number of inputstreams has to be even and the operator will be
302 *    applied to pairs of them.
303 *
304 *    Example : a;b;c;d;e;f | plus
305 *    Result  : a+b;c+d;e+f
306 *
307 * 2. inputstreams|operator(x)
308 *
309 *    The number of inputstreams has to be at least 1.
310 *    The operator is applied to each inputstream.
311 *
312 *    Example : a;b;c | plus(d)
313 *    Result  : a+d;b+d;c+d
314 *
315 * 3. operator(x, y)
316 *
317 *    @@@ this decription does not match behavior!
318 *    @@@ check description in helpfile as well
319 *
320 *    Inputstreams will be ignored and the operator is applied
321 *    to the arguments
322 *
323 *    Example : a;b | plus(c,d)
324 *    Result  : c+d
325 */
326
327template <typename T>
328GB_ERROR gbl_apply_binary_operator(GBL_command_arguments *args, char *(*op)(const char *, const char *, T), T client_data) {
329    GB_ERROR error = NULp;
330    switch (args->param_count()) {
331        case 0:
332            gb_assert(args->set_params_checked());
333            if (args->input.size() == 0) error = "Expect at least two input streams if called with 0 parameters";
334            else if (args->input.size()%2) error = "Expect an even number of input streams if called with 0 parameters";
335            else {
336                int inputpairs = args->input.size()/2;
337                int i;
338                for (i = 0; i<inputpairs; ++i) {
339                    PASS_2_OUT(args, op(args->input.get(i*2), args->input.get(i*2+1), client_data));
340                }
341            }
342            break;
343
344        case 1:
345            gb_assert(args->set_params_checked());
346            if (args->input.size() == 0) error = "Expect at least one input stream if called with 1 parameter";
347            else {
348                int         i;
349                const char *argument = args->get_param(0);
350                for (i = 0; i<args->input.size(); ++i) {
351                    PASS_2_OUT(args, op(args->input.get(i), argument, client_data));
352                }
353            }
354            break;
355
356        case 2:
357            gb_assert(args->set_params_checked());
358            for (int i = 0; i<args->input.size(); ++i) {
359                char *result1       = args->get_callEnv().interpret_subcommand(args->input.get(i), args->get_param(0)); // @@@ EVALUATED_PARAM (#768)
360                if (!result1) error = GB_await_error();
361                else {
362                    char *result2       = args->get_callEnv().interpret_subcommand(args->input.get(i), args->get_param(1)); // @@@ EVALUATED_PARAM (#768)
363                    if (!result2) error = GB_await_error();
364                    else {
365                        PASS_2_OUT(args, op(result1, result2, client_data));
366                        free(result2);
367                    }
368                    free(result1);
369                }
370            }
371            break;
372
373        default:
374            error = check_optional_parameters(args, 0, NULp, 2, "Expr1[,Expr2]", true, false);
375            break;
376    }
377
378    return error;
379}
380
381// --------------------------------
382//      escape/unescape strings
383
384static char *unEscapeString(const char *escapedString) {
385    // replaces all \x by x
386    char *result = nulldup(escapedString);
387    char *to     = result;
388    char *from   = result;
389
390    while (1) {
391        char c = *from++;
392        if (!c) break;
393
394        if (c=='\\') {
395            *to++ = *from++;
396        }
397        else {
398            *to++ = c;
399        }
400    }
401    *to = 0;
402    return result;
403}
404static char *escapeString(const char *unescapedString) {
405    // replaces all '\' and '"' by '\\' and '\"'
406    int         len    = strlen(unescapedString);
407    char       *result = ARB_alloc<char>(2*len+1);
408    char       *to     = result;
409    const char *from   = unescapedString;
410
411    while (1) {
412        char c = *from++;
413        if (!c) break;
414
415        if (c=='\\' || c == '\"') {
416            *to++ = '\\';
417            *to++ = c;
418        }
419        else {
420            *to++ = c;
421        }
422    }
423    *to = 0;
424    return result;
425}
426
427// ---------------------------------
428//      the commands themselves:
429
430static GB_ERROR gbl_quote(GBL_command_arguments *args) {
431    EXPECT_NO_PARAM(args);
432
433    for (int i=0; i<args->input.size(); i++) {
434        FORMAT_2_OUT(args, "\"%s\"", args->input.get(i));
435    }
436    return NULp;
437}
438static GB_ERROR gbl_unquote(GBL_command_arguments *args) {
439    EXPECT_NO_PARAM(args);
440
441    for (int i=0; i<args->input.size(); i++) {
442        const char *str = args->input.get(i);
443        int         len = strlen(str);
444
445        if (str[0] == '\"' && str[len-1] == '\"') {
446            PASS_2_OUT(args, ARB_strpartdup(str+1, str+len-2));
447        }
448        else {
449            IN_2_OUT(args, i);
450        }
451    }
452    return NULp;
453}
454
455static GB_ERROR gbl_escape(GBL_command_arguments *args) {
456    EXPECT_NO_PARAM(args);
457
458    for (int i=0; i<args->input.size(); i++) {
459        char *escaped = escapeString(args->input.get(i));
460        PASS_2_OUT(args, escaped);
461    }
462    return NULp;
463}
464static GB_ERROR gbl_unescape(GBL_command_arguments *args) {
465    EXPECT_NO_PARAM(args);
466
467    for (int i=0; i<args->input.size(); i++) {
468        char *unescaped = unEscapeString(args->input.get(i));
469        PASS_2_OUT(args, unescaped);
470    }
471    return NULp;
472}
473
474static GB_ERROR gbl_command(GBL_command_arguments *args) {
475    EXPECT_PARAMS(args, 1, "\"ACI command\"");
476
477    GB_ERROR  error   = NULp;
478    char     *command = unEscapeString(args->get_param(0));
479
480    if (traceACI) {
481        print_trace(GBS_global_string("executing command '%s'\n", command));
482    }
483
484    for (int i=0; i<args->input.size() && !error; i++) {
485        char *result = args->get_callEnv().interpret_subcommand(args->input.get(i), command);
486        if (!result) error = GB_await_error();
487        else PASS_2_OUT(args, result);
488    }
489    free(command);
490    return error;
491}
492
493static GB_ERROR gbl_eval(GBL_command_arguments *args) {
494    EXPECT_PARAMS(args, 1, "\"expression evaluating to ACI command\"");
495
496    GB_ERROR  error   = NULp;
497    char     *to_eval = unEscapeString(args->get_param(0));
498    TRACE_ACI(GBS_global_string("evaluating '%s'\n", to_eval));
499
500    char *command = args->get_callEnv().interpret_subcommand("", to_eval); // evaluate independent
501    if (!command) error = GB_await_error();
502    else {
503        TRACE_ACI(GBS_global_string("executing  '%s'\n", command));
504
505        for (int i=0; i<args->input.size() && !error; i++) {
506            char *result       = args->get_callEnv().interpret_subcommand(args->input.get(i), command);
507            if (!result) error = GB_await_error();
508            else  PASS_2_OUT(args, result);
509        }
510        free(command);
511    }
512    free(to_eval);
513    return error;
514}
515
516class DefinedCommands : virtual Noncopyable {
517    GB_HASH *cmds;
518public:
519    DefinedCommands() { cmds = GBS_create_dynaval_hash(100, GB_MIND_CASE, GBS_dynaval_free); }
520    ~DefinedCommands() { GBS_free_hash(cmds); }
521
522    void set(const char *name, char* cmd) { GBS_dynaval_free(GBS_write_hash(cmds, name, (long)cmd)); } // takes ownership of 'cmd'!
523    const char *get(const char *name) const { return (const char *)GBS_read_hash(cmds, name); }
524};
525
526static DefinedCommands defined_commands;
527
528static GB_ERROR gbl_define(GBL_command_arguments *args) {
529    COMMAND_DROPS_INPUT_STREAMS(args);
530    EXPECT_PARAMS(args, 2, "name, \"ACI command\"");
531
532    const char *name = args->get_param(0);
533    char       *cmd  = unEscapeString(args->get_param(1));
534
535    defined_commands.set(name, cmd);
536    TRACE_ACI(GBS_global_string("defining command '%s'='%s'\n", name, cmd));
537    return NULp;
538}
539
540static GB_ERROR gbl_do(GBL_command_arguments *args) {
541    EXPECT_PARAMS(args, 1, "definedCommandName");
542
543    GB_ERROR    error = NULp;
544    const char *name  = args->get_param(0);
545    const char *cmd   = defined_commands.get(name);
546
547    if (!cmd) {
548        error = GBS_global_string("Can't do undefined command '%s' - use define(%s, ...) first", name, name);
549    }
550    else {
551        TRACE_ACI(GBS_global_string("executing defined command '%s'='%s' on %i streams\n", name, cmd, args->input.size()));
552
553        for (int i=0; i<args->input.size() && !error; i++) {
554            char *result       = args->get_callEnv().interpret_subcommand(args->input.get(i), cmd);
555            if (!result) error = GB_await_error();
556            else  PASS_2_OUT(args, result);
557        }
558    }
559    return error;
560}
561
562static GB_ERROR gbl_streams(GBL_command_arguments *args) {
563    EXPECT_NO_PARAM(args);
564
565    FORMAT_2_OUT(args, "%i", args->input.size());
566    return NULp;
567}
568
569static GB_ERROR expect_used_in_genome_db(GBL_command_arguments *args) {
570    if (GEN_is_genome_db(args->get_gb_main(), -1)) return NULp;
571    return GBS_global_string("ACI command '%s' can only be used in genome databases.", args->get_cmdName());
572}
573
574static GB_ERROR apply_to_origin(GBL_command_arguments *args, bool organism) {
575    EXPECT_PARAMS(args, 1, "\"ACI command\"");
576    EXPECT_ITEM_REFERENCED(args);
577
578    GB_ERROR error = expect_used_in_genome_db(args);
579    if (!error) {
580        if (!GEN_is_pseudo_gene_species(args->get_item_ref())) {
581            error = GBS_global_string("'%s' applies to gene-species only", args->get_cmdName());
582        }
583        else {
584            GBDATA *gb_origin = NULp;
585            if (organism) {
586                gb_origin = GEN_find_origin_organism(args->get_item_ref(), NULp);
587            }
588            else {
589                gb_origin = GEN_find_origin_gene(args->get_item_ref(), NULp);
590            }
591
592            if (!error && !gb_origin) error = GB_await_error();
593
594            if (!error) {
595                char         *command = unEscapeString(args->get_param(0));
596                GBL_call_env  callEnv(gb_origin, args->get_env()); // refer to gb_origin for subcommands
597                // Note: if calling env has a FieldTracker, field access from 'command' is not tracked.
598                //       That access applies to different item.
599
600                for (int i=0; i<args->input.size() && !error; i++) {
601                    char *result       = callEnv.interpret_subcommand(args->input.get(i), command);
602                    if (!result) error = GB_await_error();
603                    else         PASS_2_OUT(args, result);
604                }
605
606                free(command);
607            }
608        }
609    }
610    return error;
611}
612
613static GB_ERROR gbl_origin_gene(GBL_command_arguments *args) { return apply_to_origin(args, false); }
614static GB_ERROR gbl_origin_organism(GBL_command_arguments *args) { return apply_to_origin(args, true); }
615
616
617static GB_ERROR applyToItemFoundByKey(GBL_command_arguments *args, const char *itemname, GBDATA *gb_item_data, const char *key) {
618    GB_ERROR  error        = NULp;
619    char     *command      = unEscapeString(args->get_param(0));
620
621    for (int i=0; i<args->input.size() && !error; i++) {
622        const char *in = args->input.get(i);
623        if (in[0]) { // silently ignore empty input streams
624            GBDATA *gb_item = NULp;
625            {
626                GBDATA *gb_field = GB_find_string(gb_item_data, key, in, GB_IGNORE_CASE, SEARCH_GRANDCHILD);
627                if (gb_field) {
628                    gb_item = GB_get_father(gb_field);
629                }
630                else {
631                    error = GBS_global_string("No %s with %s '%s' found.", itemname, key, in);
632                }
633            }
634            if (gb_item) {
635                GBL_call_env callEnv(gb_item, args->get_env()); // refer to gb_item for subcommands
636                // Note: if calling env has a FieldTracker, field access from 'command' is not tracked.
637                //       That access applies to different item.
638
639                char *result       = callEnv.interpret_subcommand("", command);
640                if (!result) error = GB_await_error();
641                else  PASS_2_OUT(args, result);
642            }
643            else {
644                if (!error) error = GB_await_error();
645            }
646        }
647    }
648
649    free(command);
650    return error;
651}
652static GB_ERROR gbl_findspec(GBL_command_arguments *args) {
653    EXPECT_PARAMS(args, 1, "\"ACI command\"");
654    return applyToItemFoundByKey(args, "species", GBT_get_species_data(args->get_gb_main()), "name");
655}
656static GB_ERROR gbl_findacc(GBL_command_arguments *args) {
657    EXPECT_PARAMS(args, 1, "\"ACI command\"");
658    return applyToItemFoundByKey(args, "species", GBT_get_species_data(args->get_gb_main()), "acc");
659}
660
661static GB_ERROR gbl_findgene(GBL_command_arguments *args) {
662    EXPECT_PARAMS(args, 1, "\"ACI command\"");
663    EXPECT_ITEM_REFERENCED(args);
664
665    GB_ERROR error = expect_used_in_genome_db(args);
666    if (!error) {
667        GBDATA *gb_item = args->get_item_ref();
668        if (GEN_is_organism(gb_item)) {
669            error = applyToItemFoundByKey(args, "gene", GEN_find_gene_data(gb_item), "name");
670        }
671        else if (strcmp(GB_read_key_pntr(gb_item), "gene") == 0) {
672            // if applied to gene -> find "brother" of gene
673            GBDATA *gb_organism = GB_get_grandfather(gb_item);
674            if (GEN_is_organism(gb_organism)) {
675                error = applyToItemFoundByKey(args, "gene", GEN_find_gene_data(gb_organism), "name");
676            }
677            else {
678                error = "'findgene' cannot be used here (was applied to 'gene', but could not find gene-owning organism)";
679            }
680        }
681        else {
682            error = GBS_global_string("'findgene' cannot be applied to '%s' (need an organism)",
683                                      GBT_get_name_or_description(gb_item));
684        }
685    }
686    return error;
687}
688
689class Tab {
690    bool tab[256];
691public:
692    Tab(bool take, const char *invert) {
693        bool init = !take;
694        for (int i = 0; i<256; ++i) tab[i] = init;
695        for (int i = 0; invert[i]; ++i) tab[safeCharIndex(invert[i])] = take;
696    }
697    bool operator[](int i) const { return tab[i]; }
698};
699
700inline GB_ERROR count_by_tab(GBL_command_arguments *args, const Tab& tab) {
701    for (int i=0; i<args->input.size(); ++i) {
702        long        sum = 0;            // count frequencies
703        const char *p   = args->input.get(i);
704
705        while (*p) sum += tab[safeCharIndex(*(p++))];
706        FORMAT_2_OUT(args, "%li", sum);
707    }
708    return NULp;
709}
710inline GB_ERROR remove_by_tab(GBL_command_arguments *args, const Tab& tab) {
711    GBS_strstruct buf(1000);
712    for (int i=0; i<args->input.size(); ++i) {
713        buf.erase();
714        for (const char *p = args->input.get(i); *p; p++) {
715            if (!tab[(unsigned int)*p]) {
716                buf.put(*p);
717            }
718        }
719        PASS_2_OUT(args, buf.get_copy());
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    GBS_strstruct buf(1000);
838    for (int i=0; i<args->input.size(); i++) {
839        buf.erase();
840        for (const char *p = args->input.get(i); *p; p++) {
841            buf.put(tab[(unsigned char)*p]);
842        }
843        PASS_2_OUT(args, buf.get_copy());
844    }
845    return NULp;
846}
847
848
849static GB_ERROR gbl_echo(GBL_command_arguments *args) {
850    ACCEPT_ANY_PARAMS(args);
851    COMMAND_DROPS_INPUT_STREAMS(args);
852    for (int i=0; i<args->param_count(); i++) PARAM_2_OUT(args, i);
853    return NULp;
854}
855
856static GB_ERROR gbl_dd(GBL_command_arguments *args) {
857    EXPECT_NO_PARAM(args);
858    return gbl_mid_streams(args->input, args->output, 0, -1); // copy all streams
859}
860
861enum Case { UPPER, LOWER, CAPS };
862
863static GB_ERROR convert_case(GBL_command_arguments *args, Case convTo) {
864    EXPECT_NO_PARAM(args);
865
866    for (int i=0; i<args->input.size(); i++) {
867        char *p              = ARB_strdup(args->input.get(i));
868        bool  last_was_alnum = false;
869
870        for (char *pp = p; pp[0]; ++pp) {
871            switch (convTo) {
872                case LOWER:  pp[0] = tolower(pp[0]); break;
873                case UPPER:  pp[0] = toupper(pp[0]); break;
874                case CAPS: {
875                    bool alnum = isalnum(pp[0]);
876                    if (alnum) pp[0] = (last_was_alnum ? tolower : toupper)(pp[0]);
877                    last_was_alnum = alnum;
878                    break;
879                }
880                default: gb_assert(0); break;
881            }
882        }
883
884        PASS_2_OUT(args, p);
885    }
886
887    return NULp;
888}
889
890static GB_ERROR gbl_caps (GBL_command_arguments *args) { return convert_case(args, CAPS); }
891static GB_ERROR gbl_upper(GBL_command_arguments *args) { return convert_case(args, UPPER); }
892static GB_ERROR gbl_lower(GBL_command_arguments *args) { return convert_case(args, LOWER); }
893
894static GB_ERROR gbl_head(GBL_command_arguments *args) {
895    EXPECT_PARAMS(args, 1, "length_of_head");
896    int start = atoi(args->get_param(0));
897    if (start <= 0) return gbl_mid_streams(args->input, args->output, 1, 0); // empty all streams
898    return gbl_mid_streams(args->input, args->output, 0, start-1);
899}
900static GB_ERROR gbl_tail(GBL_command_arguments *args) {
901    EXPECT_PARAMS(args, 1, "length_of_tail");
902    int end = atoi(args->get_param(0));
903    if (end <= 0) return gbl_mid_streams(args->input, args->output, 1, 0); // empty all streams
904    return gbl_mid_streams(args->input, args->output, -end, -1);
905}
906
907inline GB_ERROR mid(GBL_command_arguments *args, int start_index) {
908    EXPECT_PARAMS(args, 2, "start,end");
909    return gbl_mid_streams(args->input, args->output, atoi(args->get_param(0))-start_index, atoi(args->get_param(1))-start_index);
910}
911static GB_ERROR gbl_mid0(GBL_command_arguments *args) { return mid(args, 0); }
912static GB_ERROR gbl_mid (GBL_command_arguments *args) { return mid(args, 1); }
913
914static GB_ERROR tab(GBL_command_arguments *args, bool pretab) {
915    EXPECT_PARAMS(args, 1, "tabstop");
916
917    int tab = atoi(args->get_param(0));
918    for (int i=0; i<args->input.size(); i++) {
919        int len = strlen(args->input.get(i));
920        if (len >= tab) IN_2_OUT(args, i);
921        else {
922            char *p = ARB_alloc<char>(tab+1);
923            if (pretab) {
924                int spaces = tab-len;
925                for (int j = 0; j<spaces; ++j) p[j] = ' ';
926                strcpy(p+spaces, args->input.get(i));
927            }
928            else {
929                strcpy(p, args->input.get(i));
930                for (int j=len; j<tab; j++) p[j] = ' ';
931                p[tab] = 0;
932            }
933            PASS_2_OUT(args, p);
934        }
935    }
936    return NULp;
937}
938static GB_ERROR gbl_tab   (GBL_command_arguments *args) { return tab(args, false); }
939static GB_ERROR gbl_pretab(GBL_command_arguments *args) { return tab(args, true); }
940
941static GB_ERROR gbl_crop(GBL_command_arguments *args) {
942    EXPECT_PARAMS(args, 1, "\"chars_to_crop\"");
943
944    const char *chars_to_crop = args->get_param(0);
945    for (int i=0; i<args->input.size(); i++) {
946        const char *s = args->input.get(i);
947        while (s[0] && strchr(chars_to_crop, s[0])) s++; // crop at beg of line
948
949        int   len = strlen(s);
950        char *p   = ARB_alloc<char>(len+1);
951        strcpy(p, s);
952
953        {
954            char *pe = p+len-1;
955
956            while (pe >= p && strchr(chars_to_crop, pe[0])) { // crop at end of line
957                --pe;
958            }
959            gb_assert(pe >= (p-1));
960            pe[1] = 0;
961        }
962        PASS_2_OUT(args, p);
963    }
964    return NULp;
965}
966
967
968
969static GB_ERROR gbl_cut(GBL_command_arguments *args) {
970    EXPECT_PARAMS_PASSED(args, "streamnumber[,streamnumber]+");
971
972    for (int i=0; i<args->param_count(); i++) {
973        int stream = atoi(args->get_param(i));
974        EXPECT_LEGAL_STREAM_INDEX(args, stream);
975        IN_2_OUT(args, bio2info(stream));
976    }
977    return NULp;
978}
979static GB_ERROR gbl_drop(GBL_command_arguments *args) {
980    EXPECT_PARAMS_PASSED(args, "streamnumber[,streamnumber]+");
981
982    GB_ERROR  error   = NULp;
983    bool     *dropped = ARB_alloc<bool>(args->input.size());
984
985    for (int i=0; i<args->input.size(); ++i) dropped[i] = false;
986
987    for (int i=0; i<args->param_count() && !error; ++i) {
988        int stream = atoi(args->get_param(i));
989        error = check_valid_stream_index(args, stream);
990        if (!error) dropped[bio2info(stream)] = true;
991    }
992
993    if (!error) {
994        for (int i=0; i<args->input.size(); ++i) {
995            if (!dropped[i]) IN_2_OUT(args, i);
996        }
997    }
998    free(dropped);
999
1000    return error;
1001}
1002
1003static GB_ERROR gbl_dropempty(GBL_command_arguments *args) {
1004    EXPECT_NO_PARAM(args);
1005
1006    for (int i=0; i<args->input.size(); ++i) {
1007        if (args->input.get(i)[0]) { // if non-empty
1008            IN_2_OUT(args, i);
1009        }
1010    }
1011    return NULp;
1012}
1013
1014static GB_ERROR gbl_dropzero(GBL_command_arguments *args) {
1015    EXPECT_NO_PARAM(args);
1016
1017    for (int i=0; i<args->input.size(); ++i) {
1018        if (atoi(args->input.get(i))) { // if non-zero
1019            IN_2_OUT(args, i);
1020        }
1021    }
1022    return NULp;
1023}
1024
1025static GB_ERROR gbl_swap(GBL_command_arguments *args) {
1026    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 2, "streamnumber,streamnumber");
1027
1028    if (args->input.size()<2) return "need at least two input streams";
1029
1030    int swap1;
1031    int swap2;
1032    if (args->param_count() == 0) {
1033        swap1 = args->input.size()-1;
1034        swap2 = args->input.size()-2;
1035    }
1036    else {
1037        gb_assert(args->param_count() == 2);
1038
1039        swap1 = atoi(args->get_param(0));
1040        swap2 = atoi(args->get_param(1));
1041
1042        EXPECT_LEGAL_STREAM_INDEX(args, swap1);
1043        EXPECT_LEGAL_STREAM_INDEX(args, swap2);
1044
1045        swap1 = bio2info(swap1);
1046        swap2 = bio2info(swap2);
1047    }
1048
1049    for (int i = 0; i<args->input.size(); ++i) {
1050        int j = i == swap1 ? swap2 : (i == swap2 ? swap1 : i);
1051        IN_2_OUT(args, j);
1052    }
1053
1054    return NULp;
1055}
1056
1057static GB_ERROR backfront_stream(GBL_command_arguments *args, int toback) {
1058    EXPECT_PARAMS(args, 1, "streamnumber");
1059    if (args->input.size()<1) return "need at least one input stream";
1060
1061    int stream_to_move = atoi(args->get_param(0));
1062    EXPECT_LEGAL_STREAM_INDEX(args, stream_to_move);
1063    stream_to_move = bio2info(stream_to_move);
1064
1065    if (!toback) IN_2_OUT(args, stream_to_move);
1066    for (int i = 0; i<args->input.size(); ++i) {
1067        if (i != stream_to_move) IN_2_OUT(args, i);
1068    }
1069    if (toback) IN_2_OUT(args, stream_to_move);
1070
1071    return NULp;
1072}
1073static GB_ERROR gbl_toback (GBL_command_arguments *args) { return backfront_stream(args, 1); }
1074static GB_ERROR gbl_tofront(GBL_command_arguments *args) { return backfront_stream(args, 0); }
1075
1076static GB_ERROR gbl_merge(GBL_command_arguments *args) {
1077    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "\"separator\"");
1078    const char *separator = args->get_optional_param(0, NULp);
1079
1080    if (args->input.size()) {
1081        GBS_strstruct str(1000);
1082        str.cat(args->input.get(0));
1083
1084        for (int i = 1; i<args->input.size(); ++i) {
1085            if (separator) str.cat(separator);
1086            str.cat(args->input.get(i));
1087        }
1088
1089        PASS_2_OUT(args, str.release());
1090    }
1091    return NULp;
1092}
1093
1094static GB_ERROR gbl_split(GBL_command_arguments *args) {
1095    EXPECT_OPTIONAL_PARAMS_CUSTOM(args, 0, NULp, 2, "\"separator\"[,mode]", true, false);
1096
1097    const char *separator = args->get_optional_param(0, "\n");
1098    int split_mode        = atoi(args->get_optional_param(1, "0")); // 0: remove separator, 1: split before separator, 2: split behind separator
1099
1100    if (!separator[0]) {
1101        // e.g. happens if trying to specify character ';' or ','
1102        return "Invalid separator (cannot be empty; please try to quote the parameter)";
1103    }
1104
1105    if (split_mode<0 || split_mode>2) return GBS_global_string("Illegal split mode '%i' (valid: 0..2)", split_mode);
1106
1107    {
1108        size_t sepLen = strlen(separator);
1109
1110        for (int i = 0; i<args->input.size(); ++i) {
1111            const char *in   = args->input.get(i);
1112            const char *from = in; // search from here
1113
1114            while (in) {
1115                const char *splitAt = strstr(from, separator);
1116                if (splitAt) {
1117                    size_t  len;
1118                    char   *copy;
1119
1120                    if (split_mode == 2) splitAt += sepLen; // split behind separator
1121
1122                    len  = splitAt-in;
1123                    copy = ARB_strndup(in, len);
1124
1125                    PASS_2_OUT(args, copy);
1126
1127                    in   = splitAt + (split_mode == 0 ? sepLen : 0);
1128                    from = in+(split_mode == 1 ? sepLen : 0);
1129                }
1130                else {
1131                    COPY_2_OUT(args, in); // last part
1132                    in = NULp;
1133                }
1134            }
1135        }
1136    }
1137
1138    return NULp;
1139}
1140
1141static GB_ERROR gbl_colsplit(GBL_command_arguments *args) {
1142    EXPECT_OPTIONAL_PARAMS(args, 0, NULp, 1, "width");
1143
1144    int width = atoi(args->get_optional_param(0, "1"));
1145    if (width<1) return "Invalid width";
1146
1147    for (int i = 0; i<args->input.size(); ++i) {
1148        const char *in  = args->input.get(i);
1149        int         len = strlen(in);
1150
1151        while (len>0) {
1152            char *part = ARB_strpartdup(in, in+width-1);
1153            PASS_2_OUT(args, part);
1154
1155            in  += width;
1156            len -= width;
1157        }
1158    }
1159
1160    return NULp;
1161}
1162// ----------------------------------
1163//      Extended string functions
1164
1165static char *do_extract_words(const char *source, const char *chars, float minlen, bool sort_output) {
1166    /* extract all words in a text that:
1167     * if minlen < 1.0 -> contain more than minlen*len_of_text characters that also exists in chars
1168     * if minlen > 1.0 -> contain more than minlen characters that also exists in chars
1169     */
1170
1171    int count   = 0;
1172    int iminlen = int(minlen+.5);
1173
1174    char  *= ARB_strdup(source);
1175    char  *= s;
1176    char **ps = ARB_calloc<char*>((strlen(source)>>1) + 1);
1177
1178    while (char *p = strtok(f, " \t,;:|")) {
1179        f = NULp;
1180        int cnt = 0;
1181        const int len = strlen(p);
1182        for (char *h=p; *h; h++) {
1183            if (strchr(chars, *h)) ++cnt;
1184        }
1185
1186        if (minlen == 1.0) {
1187            if (cnt != len) continue;
1188        }
1189        else if (minlen > 1.0) {
1190            if (cnt < iminlen) continue;
1191        }
1192        else {
1193            if (len < 3 || cnt < minlen*len) continue;
1194        }
1195        ps[count] = p;
1196        count ++;
1197    }
1198
1199    if (sort_output) {
1200        GB_sort((void **)ps, 0, count, GB_string_comparator, NULp);
1201    }
1202
1203    GBS_strstruct buf(1000);
1204
1205    for (int cnt = 0; cnt<count; ++cnt) {
1206        if (cnt) buf.put(' ');
1207        buf.cat(ps[cnt]);
1208    }
1209
1210    free(ps);
1211    free(s);
1212
1213    return buf.release_memfriendly();
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 buf(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            buf.cat(val);
1457            free(val);
1458        }
1459    }
1460    PASS_2_OUT(args, buf.release());
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            char keelIndicator[2] = { char(keelTarget ? KEELED_INDICATOR : 0), 0 };
1546
1547            hash_entry = GBS_global_string_copy(">%0*x%s%s",
1548                                                GROUP_COUNT_CHARS, *group_counter,
1549                                                keelIndicator,
1550                                                node->name);
1551            GBS_write_hash(tax_hash, hash_entry, (long)ARB_strdup(parent_group));
1552
1553            hash_binary_entry = GBS_global_string(">>%p", node->gb_node);
1554            GBS_write_hash(tax_hash, hash_binary_entry, (long)ARB_strdup(hash_entry));
1555
1556            if (keelTarget) { // keeled group (projected to son)
1557                if (keelTarget->is_leftson()) {
1558                    build_taxonomy_rek(node->get_leftson(),  tax_hash, hash_entry,   group_counter); // pass down hash_entry only to keelTarget
1559                    build_taxonomy_rek(node->get_rightson(), tax_hash, parent_group, group_counter);
1560                }
1561                else {
1562                    build_taxonomy_rek(node->get_leftson(),  tax_hash, parent_group, group_counter);
1563                    build_taxonomy_rek(node->get_rightson(), tax_hash, hash_entry,   group_counter);
1564                }
1565            }
1566            else { // normal group
1567                build_taxonomy_rek(node->get_leftson(),  tax_hash, hash_entry, group_counter); // pass down hash_entry to both sons
1568                build_taxonomy_rek(node->get_rightson(), tax_hash, hash_entry, group_counter);
1569            }
1570
1571            free(hash_entry);
1572        }
1573        else {
1574            build_taxonomy_rek(node->get_leftson(),  tax_hash, parent_group, group_counter);
1575            build_taxonomy_rek(node->get_rightson(), tax_hash, parent_group, group_counter);
1576        }
1577    }
1578}
1579
1580static GB_HASH *cached_taxonomies = NULp;
1581
1582static bool is_cached_taxonomy(const char */*key*/, long val, void *cl_ct) {
1583    cached_taxonomy *ct1 = (cached_taxonomy *)val;
1584    cached_taxonomy *ct2 = (cached_taxonomy *)cl_ct;
1585
1586    return ct1 == ct2;
1587}
1588
1589static const char *tree_of_cached_taxonomy(cached_taxonomy *ct) {
1590    /* search the hash to find the correct cached taxonomy.
1591     * searching for tree name does not work, because the tree possibly already was deleted
1592     */
1593    const char *tree = GBS_hash_next_element_that(cached_taxonomies, NULp, is_cached_taxonomy, ct);
1594#ifdef DUMP_TAXONOMY_CACHING
1595    if (tree) printf("tree_of_cached_taxonomy: tree='%s' ct->tree_name='%s'\n", tree, ct->tree_name);
1596#endif // DUMP_TAXONOMY_CACHING
1597    return tree;
1598}
1599
1600static void flush_taxonomy_cb(GBDATA *gbd, cached_taxonomy *ct) {
1601    /* this cb is bound all tree db members below "/tree_data/tree_xxx" which
1602     * may have an effect on the displayed taxonomy
1603     * it invalidates cached taxonomies for that tree (when changed or deleted)
1604     */
1605
1606    GB_ERROR    error = NULp;
1607    const char *found = tree_of_cached_taxonomy(ct);
1608
1609    if (found) {
1610#ifdef DUMP_TAXONOMY_CACHING
1611        fprintf(stderr, "Deleting cached taxonomy ct=%p (tree='%s')\n", ct, found);
1612#endif // DUMP_TAXONOMY_CACHING
1613        GBS_write_hash(cached_taxonomies, found, 0); // delete cached taxonomy from hash
1614        free_cached_taxonomy(ct);
1615    }
1616#ifdef DUMP_TAXONOMY_CACHING
1617    else {
1618        fprintf(stderr, "No tree found for cached_taxonomies ct=%p (already deleted?)\n", ct);
1619    }
1620#endif // DUMP_TAXONOMY_CACHING
1621
1622    if (!GB_inside_callback(gbd, GB_CB_DELETE)) {
1623        GB_remove_all_callbacks_to(gbd, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1624    }
1625
1626    if (found && !error) {
1627        GBDATA *gb_main = GB_get_gb_main_during_cb();
1628        if (gb_main) {
1629            GBDATA *gb_tree_refresh = GB_search(gb_main, AWAR_TREE_REFRESH, GB_INT);
1630            if (!gb_tree_refresh) {
1631                error = GBS_global_string("%s (while trying to force refresh)", GB_await_error());
1632            }
1633            else {
1634                GB_touch(gb_tree_refresh); // Note : force tree update
1635            }
1636        }
1637    }
1638
1639    if (error) {
1640        fprintf(stderr, "Error in flush_taxonomy_cb: %s\n", error);
1641    }
1642}
1643
1644static void flush_taxonomy_if_new_group_cb(GBDATA *gb_tree, cached_taxonomy *ct) {
1645    // detects the creation of new groups and call flush_taxonomy_cb() manually
1646#ifdef DUMP_TAXONOMY_CACHING
1647    fputs("flush_taxonomy_if_new_group_cb() has been called\n", stderr);
1648#endif // DUMP_TAXONOMY_CACHING
1649
1650    const char *tree_name = tree_of_cached_taxonomy(ct);
1651    if (tree_name) {
1652        int     groups = 0;
1653        GBDATA *gb_group_node;
1654
1655        for (gb_group_node = GB_entry(gb_tree, "node");
1656             gb_group_node;
1657             gb_group_node = GB_nextEntry(gb_group_node))
1658        {
1659            if (GB_entry(gb_group_node, "group_name")) {
1660                groups++; // count named groups only
1661            }
1662        }
1663
1664#ifdef DUMP_TAXONOMY_CACHING
1665        fprintf(stderr, "cached_groups=%i  counted_groups=%i\n", ct->groups, groups);
1666#endif // DUMP_TAXONOMY_CACHING
1667        if (groups != ct->groups) {
1668#ifdef DUMP_TAXONOMY_CACHING
1669            fprintf(stderr, "Number of groups changed -> invoking flush_taxonomy_cb() manually\n");
1670#endif // DUMP_TAXONOMY_CACHING
1671            flush_taxonomy_cb(gb_tree, ct);
1672        }
1673    }
1674#ifdef DUMP_TAXONOMY_CACHING
1675    else {
1676        fprintf(stderr, "cached taxonomy no longer valid.\n");
1677    }
1678#endif // DUMP_TAXONOMY_CACHING
1679}
1680
1681static cached_taxonomy *get_cached_taxonomy(GBDATA *gb_main, const char *tree_name, GB_ERROR *error) {
1682    long cached;
1683    *error = NULp;
1684    if (!cached_taxonomies) {
1685        cached_taxonomies = GBS_create_hash(20, GB_IGNORE_CASE);
1686    }
1687    cached = GBS_read_hash(cached_taxonomies, tree_name);
1688    if (!cached) {
1689        TreeNode *tree    = GBT_read_tree(gb_main, tree_name, new SimpleRoot);
1690        if (!tree) *error = GB_await_error();
1691        else     *error   = GBT_link_tree(tree, gb_main, false, NULp, NULp);
1692
1693        if (!*error) {
1694            GBDATA *gb_tree = GBT_find_tree(gb_main, tree_name);
1695            if (!gb_tree) {
1696                *error = GBS_global_string("Can't find tree '%s'", tree_name);
1697            }
1698            else {
1699                cached_taxonomy *ct            = ARB_alloc<cached_taxonomy>(1);
1700                long             nodes         = GBT_count_leafs(tree);
1701                int              group_counter = 0;
1702
1703                ct->tree_name = ARB_strdup(tree_name);
1704                ct->taxonomy  = GBS_create_dynaval_hash(int(nodes), GB_IGNORE_CASE, GBS_dynaval_free);
1705                ct->groups    = 0; // counted below
1706
1707                build_taxonomy_rek(tree, ct->taxonomy, "<root>", &group_counter);
1708                cached = (long)ct;
1709                GBS_write_hash(cached_taxonomies, tree_name, (long)ct);
1710
1711                GB_remove_all_callbacks_to(gb_tree, GB_CB_SON_CREATED, CASTSIG(GB_CB, flush_taxonomy_if_new_group_cb));
1712                GB_add_callback(gb_tree, GB_CB_SON_CREATED, makeDatabaseCallback(flush_taxonomy_if_new_group_cb, ct));
1713
1714                {
1715                    GBDATA *gb_tree_entry = GB_entry(gb_tree, "tree");
1716                    GBDATA *gb_group_node;
1717
1718                    if (gb_tree_entry) {
1719                        GB_remove_all_callbacks_to(gb_tree_entry, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1720                        GB_add_callback(gb_tree_entry, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(flush_taxonomy_cb, ct));
1721                    }
1722
1723                    // add callbacks for all node/group_name subentries
1724                    for (gb_group_node = GB_entry(gb_tree, "node");
1725                         gb_group_node;
1726                         gb_group_node = GB_nextEntry(gb_group_node))
1727                    {
1728                        GBDATA *gb_group_name = GB_entry(gb_group_node, "group_name");
1729                        if (gb_group_name) { // group with id = 0 has no name
1730                            GB_remove_all_callbacks_to(gb_group_name, GB_CB_CHANGED_OR_DELETED, CASTSIG(GB_CB, flush_taxonomy_cb));
1731                            GB_add_callback(gb_group_name, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(flush_taxonomy_cb, ct));
1732                            ct->groups++;
1733                        }
1734                    }
1735                }
1736#ifdef DUMP_TAXONOMY_CACHING
1737                fprintf(stderr, "Created taxonomy hash for '%s' (ct=%p)\n", tree_name, ct);
1738#endif // DUMP_TAXONOMY_CACHING
1739            }
1740        }
1741
1742        destroy(tree);
1743    }
1744
1745    if (!*error) {
1746        cached_taxonomy *ct = (cached_taxonomy*)cached;
1747        gb_assert(ct);
1748        return ct;
1749    }
1750
1751    return NULp;
1752}
1753
1754static char *get_taxonomy_string(GB_HASH *tax_hash, const char *group_key, int depth, GB_ERROR *error) {
1755    long  found;
1756    char *result = NULp;
1757
1758    gb_assert(depth>0);
1759    gb_assert(!(group_key[0] == '>' && group_key[1] == '>')); // internal group-pointers not allowed here!
1760
1761    found = GBS_read_hash(tax_hash, group_key);
1762    if (found) {
1763        const char *parent_group_key            = (const char *)found;
1764        if (strcmp(parent_group_key, "<root>") == 0) { // root reached
1765            result = ARB_strdup(group_key+(GROUP_COUNT_CHARS+1)); // return own group name
1766        }
1767        else {
1768            if (depth>1) {
1769                char *parent_name = get_taxonomy_string(tax_hash, parent_group_key, depth-1, error);
1770                if (parent_name) {
1771                    result = GBS_global_string_copy("%s/%s", parent_name, group_key+(GROUP_COUNT_CHARS+1));
1772                    free(parent_name);
1773                }
1774                else {
1775                    *error = GBS_global_string("In get_taxonomy_string(%s): %s", group_key, *error);
1776                    result = NULp;
1777                }
1778            }
1779            else {
1780                result = ARB_strdup(group_key+(GROUP_COUNT_CHARS+1)); // return own group name
1781            }
1782        }
1783    }
1784    else {
1785        *error = GBS_global_string("Not in tax_hash: '%s'", group_key);
1786    }
1787    return result;
1788}
1789
1790static const char *get_taxonomy(GBDATA *gb_species_or_group, const char *tree_name, bool is_current_tree, int depth, GB_ERROR *error) {
1791    GBDATA          *gb_main = GB_get_root(gb_species_or_group);
1792    cached_taxonomy *tax     = get_cached_taxonomy(gb_main, tree_name, error);
1793    const char      *result  = NULp;
1794
1795    if (tax) {
1796        GBDATA *gb_name       = GB_entry(gb_species_or_group, "name");
1797        GBDATA *gb_group_name = GB_entry(gb_species_or_group, "group_name");
1798
1799        if (gb_name && !gb_group_name) { // it's a species
1800            char *name = GB_read_string(gb_name);
1801            if (name) {
1802                GB_HASH *tax_hash = tax->taxonomy;
1803                long     found    = GBS_read_hash(tax_hash, GBS_global_string("!%s", name));
1804
1805                if (found) {
1806                    const char *parent_group = (const char *)found;
1807
1808                    if (strcmp(parent_group, "<root>") == 0) {
1809                        result = ""; // not member of any group
1810                    }
1811                    else {
1812                        static char *parent = NULp;
1813
1814                        freeset(parent, get_taxonomy_string(tax_hash, parent_group, depth, error));
1815                        result = parent;
1816                    }
1817                }
1818                else {
1819                    result = GBS_global_string("Species '%s' not in '%s'", name, tree_name);
1820                }
1821                free(name);
1822            }
1823            else {
1824                *error = GBS_global_string("Species without 'name' entry!");
1825            }
1826        }
1827        else if (gb_group_name && !gb_name) { // it's a group
1828            char *group_name = GB_read_string(gb_group_name);
1829            if (group_name) {
1830                if (is_current_tree) {
1831                    GB_HASH *tax_hash = tax->taxonomy;
1832                    long     found    = GBS_read_hash(tax_hash, GBS_global_string(">>%p", gb_species_or_group));
1833
1834                    if (found) {
1835                        static char *full_group = NULp;
1836                        const char  *group_id   = (const char *)found;
1837
1838                        freeset(full_group, get_taxonomy_string(tax_hash, group_id, depth, error));
1839                        result = full_group;
1840                    }
1841                    else {
1842                        result = GBS_global_string("Group '%s' not in '%s'", group_name, tree_name);
1843                    }
1844                }
1845                else {
1846                    *error = "It's not possible to specify the tree name in taxonomy() for groups";
1847                }
1848                free(group_name);
1849            }
1850            else {
1851                *error = "Group without 'group_name' entry";
1852            }
1853        }
1854        else if (gb_group_name) {
1855            *error = "Container has 'name' and 'group_name' entry - can't detect container type";
1856        }
1857        else {
1858            *error = "Container has neither 'name' nor 'group_name' entry - can't detect container type";
1859        }
1860    }
1861
1862    return result;
1863}
1864
1865static GB_ERROR gbl_taxonomy(GBL_command_arguments *args) {
1866    GB_ERROR error = check_optional_parameters(args, 1, "count", 1, "tree_name", false, true);
1867    if (!error) {
1868        EXPECT_ITEM_REFERENCED(args);
1869        COMMAND_DROPS_INPUT_STREAMS(args);
1870
1871        char *tree_name       = NULp;
1872        bool  is_current_tree = false;
1873        int   depth           = -1;
1874        char *result          = NULp;
1875
1876        if (args->param_count() == 1) {   // only 'depth'
1877            if (!args->get_treename()) {
1878                result = ARB_strdup("No default tree");
1879            }
1880            else {
1881                tree_name = ARB_strdup(args->get_treename());
1882                depth = atoi(args->get_param(0));
1883                is_current_tree = true;
1884            }
1885        }
1886        else { // 'tree_name', 'depth'
1887            tree_name = ARB_strdup(args->get_param(0));
1888            depth     = atoi(args->get_param(1));
1889        }
1890
1891        if (!result) {
1892            if (depth<1) {
1893                error = GBS_global_string("Illegal depth '%i' (allowed 1..n)", depth);
1894            }
1895            if (!error) {
1896                const char *taxonomy_string = get_taxonomy(args->get_item_ref(), tree_name, is_current_tree, depth, &error);
1897                if (taxonomy_string) result = ARB_strdup(taxonomy_string);
1898            }
1899        }
1900
1901        gb_assert(contradicted(result, error));
1902        if (result) PASS_2_OUT(args, result);
1903        free(tree_name);
1904    }
1905    return error;
1906}
1907
1908static GB_ERROR gbl_sequence(GBL_command_arguments *args) {
1909    EXPECT_ITEM_REFERENCED(args);
1910    COMMAND_DROPS_INPUT_STREAMS(args);
1911
1912    GB_ERROR error = check_no_parameter(args);
1913    if (!error) {
1914        switch (identify_gb_item(args->get_item_ref())) {
1915            case GBT_ITEM_UNKNOWN: {
1916                error = "'sequence' used for unknown item";
1917                break;
1918            }
1919            case GBT_ITEM_SPECIES: {
1920                char *use = GBT_get_default_alignment(args->get_gb_main());
1921
1922                if (!use) {
1923                    error = GB_await_error();
1924                }
1925                else {
1926                    GBDATA *gb_seq = GBT_find_sequence(args->get_item_ref(), use);
1927
1928                    if (gb_seq) PASS_2_OUT(args, GB_read_string(gb_seq));
1929                    else        COPY_2_OUT(args, ""); // if current alignment does not exist -> return empty string
1930
1931                    free(use);
1932                }
1933                break;
1934            }
1935            case GBT_ITEM_GENE: {
1936                char *seq = GBT_read_gene_sequence(args->get_item_ref(), true, 0);
1937
1938                if (!seq) error = GB_await_error();
1939                else PASS_2_OUT(args, seq);
1940
1941                break;
1942            }
1943        }
1944    }
1945    return error;
1946}
1947
1948static GB_ERROR gbl_export_sequence(GBL_command_arguments *args) {
1949    EXPECT_ITEM_REFERENCED(args);
1950    COMMAND_DROPS_INPUT_STREAMS(args);
1951
1952    GB_ERROR error = check_no_parameter(args);
1953    if (!error) {
1954        switch (identify_gb_item(args->get_item_ref())) {
1955            case GBT_ITEM_UNKNOWN: {
1956                error = "'export_sequence' used for unknown item";
1957                break;
1958            }
1959            case GBT_ITEM_SPECIES: {
1960                if (!get_export_sequence) {
1961                    error = "No export-sequence-hook defined (can't use 'export_sequence' here)";
1962                }
1963                else {
1964                    size_t      len;
1965                    const char *seq = get_export_sequence(args->get_item_ref(), &len, &error);
1966
1967                    gb_assert(error || seq);
1968
1969                    if (seq) PASS_2_OUT(args, ARB_strduplen(seq, len));
1970                }
1971                break;
1972            }
1973            case GBT_ITEM_GENE: {
1974                error = "'export_sequence' cannot be used for gene";
1975                break;
1976            }
1977        }
1978    }
1979    return error;
1980}
1981
1982static GB_ERROR gbl_ali_name(GBL_command_arguments *args) {
1983    COMMAND_DROPS_INPUT_STREAMS(args);
1984
1985    GB_ERROR error = check_no_parameter(args);
1986    if (!error) {
1987        GBDATA *gb_main = args->get_gb_main();
1988        char   *use     = GBT_get_default_alignment(gb_main);
1989        if (!use) error = GB_await_error();
1990        else      PASS_2_OUT(args, use);
1991    }
1992    return error;
1993}
1994
1995static GB_ERROR gbl_sequence_type(GBL_command_arguments *args) {
1996    COMMAND_DROPS_INPUT_STREAMS(args);
1997
1998    GB_ERROR error = check_no_parameter(args);
1999    if (!error) {
2000        GBDATA *gb_main = args->get_gb_main();
2001        char   *use     = GBT_get_default_alignment(gb_main);
2002        if (!use) error = GB_await_error();
2003        else      PASS_2_OUT(args, GBT_get_alignment_type_string(gb_main, use));
2004        free(use);
2005    }
2006
2007    return error;
2008}
2009
2010static GB_ERROR format(GBL_command_arguments *args, bool simple_format) {
2011    // simple_format: true = "format", false="format_sequence"
2012
2013    GB_ERROR error = NULp;
2014    int      ic;
2015
2016    GBL_BEGIN_PARAMS;
2017    GBL_PARAM_INT(firsttab, "firsttab=", 10, "Indent first line");
2018    GBL_PARAM_INT(tab,      "tab=",      10, "Indent not first line");
2019    GBL_PARAM_INT(width,    "width=",    50, "Sequence width (bases only)");
2020
2021    // "format_sequence"-only
2022    GBL_PARAM_BIT (numleft,  PARAM_IF(!simple_format, "numleft"),  0,  "Numbers left of sequence");
2023    GBL_PARAM_INT (numright, PARAM_IF(!simple_format, "numright="), 0, "Numbers right of sequence (specifies width; -1 -> auto-width)");
2024    GBL_PARAM_UINT(gap,      PARAM_IF(!simple_format, "gap="),     10, "Insert ' ' every n sequence characters");
2025
2026    // "format"-only
2027    GBL_PARAM_STRING(nl,      PARAM_IF(simple_format, "nl="),      " ",  "Break line at characters 'str' if wrapping needed");
2028    GBL_PARAM_STRING(forcenl, PARAM_IF(simple_format, "forcenl="), "\n", "Always break line at characters 'str'");
2029
2030    GBL_TRACE_PARAMS(args);
2031    GBL_END_PARAMS;
2032
2033    if (tab      < 0) tab = 0;
2034    if (firsttab < 0) firsttab = 0;
2035
2036    if (width == 0)               return "Illegal zero width";
2037    if (numleft && numright != 0) return "You may only specify 'numleft' OR 'numright',  not both.";
2038
2039    if (gap<1) gap = UINT_MAX;
2040
2041    for (ic = 0; ic<args->input.size(); ++ic) {
2042        const char *src           = args->input.get(ic);
2043        size_t      data_size     = strlen(src);
2044        size_t      needed_size;
2045        size_t      line_size;
2046        int         numright_used = numright;
2047
2048        if (numright_used<0) {
2049            numright_used = calc_digits(data_size);
2050        }
2051
2052        {
2053            size_t lines;
2054
2055            if (simple_format) {
2056                lines     = data_size/2 + 1; // worst case
2057                line_size = tab + (width>0 ? width : data_size) + 1;
2058            }
2059            else {
2060                size_t gapsPerLine = (width-1)/gap;
2061                lines              = data_size/width+1;
2062                line_size          = tab + width + gapsPerLine + 1;
2063
2064                if (numright_used) {
2065                    // add space for numright
2066                    line_size += numright_used+1; // plus space
2067                }
2068            }
2069
2070            needed_size = lines*line_size + firsttab + 1 + 10;
2071        }
2072
2073        char *result = ARB_alloc<char>(needed_size);
2074        if (!result) {
2075            error = GBS_global_string("Out of memory (tried to alloc %zu bytes)", needed_size);
2076        }
2077        else {
2078            char   *dst       = result;
2079            size_t  rest_data = data_size;
2080
2081            if (simple_format) {
2082                /* format string w/o gaps or numleft
2083                 * does word-wrapping at chars in nl
2084                 */
2085
2086                // build wrap table
2087                unsigned char isWrapChar[256];
2088                memset(isWrapChar, 0, sizeof(isWrapChar));
2089                for (int i = 0; nl[i]; ++i) isWrapChar[(unsigned char)nl[i]] = 1;
2090                for (int i = 0; forcenl[i]; ++i) isWrapChar[(unsigned char)forcenl[i]] = 2;
2091
2092                if (firsttab>0) {
2093                    memset(dst, ' ', firsttab);
2094                    dst += firsttab;
2095                }
2096
2097                while (width>0 && rest_data>unsigned(width)) {
2098                    int take;
2099                    int move;
2100                    int took;
2101
2102                    for (take = width; take > 0; --take) {
2103                        if (isWrapChar[(unsigned char)src[take]]) break;
2104                    }
2105                    if (take <= 0) { // no wrap character found -> hard wrap at width
2106                        take  = move = width;
2107                    }
2108                    else { // soft wrap at last found wrap character
2109                        move = take+1;
2110                    }
2111
2112                    for (took = 0; took<take; took++) {
2113                        char c = src[took];
2114                        if (isWrapChar[(unsigned char)c] == 2) { // forced newline
2115                            take = took;
2116                            move = take+1;
2117                            break;
2118                        }
2119                        dst[took] = c;
2120                    }
2121
2122                    dst       += take;
2123                    src       += move;
2124                    rest_data -= move;
2125
2126                    if (rest_data>0) {
2127                        *dst++ = '\n';
2128                        if (tab>0) {
2129                            memset(dst, ' ', tab);
2130                            dst += tab;
2131                        }
2132                    }
2133                }
2134
2135                if (rest_data>0) {
2136                    size_t j, k;
2137                    for (j = 0, k = 0; j<rest_data; ++j) {
2138                        char c = src[j];
2139
2140                        if (isWrapChar[(unsigned char)c] == 2) {
2141                            dst[k++] = '\n';
2142                            if (tab>0) {
2143                                memset(dst+k, ' ', tab);
2144                                k += tab;
2145                            }
2146                        }
2147                        else {
2148                            dst[k++] = c;
2149                        }
2150                    }
2151                    src       += j;
2152                    dst       += k;
2153                    rest_data  = 0;
2154                }
2155            }
2156            else {
2157                // "format_sequence" with gaps and numleft
2158                char       *format        = NULp;
2159                const char *src_start     = src;
2160                const char *dst_linestart = dst;
2161
2162                if (numleft) {
2163                    /* Warning: Be very careful, when you change format strings here!
2164                     * currently all format strings result in '%u' or '%-##u' (where # are digits)
2165                     */
2166                    if (firsttab>0) {
2167                        char *firstFormat = GBS_global_string_copy("%%-%iu ", firsttab-1);
2168                        dst += sprintf(dst, firstFormat, (unsigned)1);
2169                        free(firstFormat);
2170                    }
2171                    else {
2172                        dst += sprintf(dst, "%u ", (unsigned)1);
2173                    }
2174                    format = tab>0 ? GBS_global_string_copy("%%-%iu ", tab-1) : ARB_strdup("%u ");
2175                }
2176                else if (firsttab>0) {
2177                    memset(dst, ' ', firsttab);
2178                    dst += firsttab;
2179                }
2180
2181                while (rest_data>0) {
2182                    size_t take = (width>0 && rest_data>unsigned(width)) ? width : rest_data;
2183
2184                    rest_data -= take;
2185
2186                    while (take>gap) {
2187                        memcpy(dst, src, gap);
2188                        dst  += gap;
2189                        src  += gap;
2190                        *dst++ = ' ';
2191                        take -= gap;
2192                    }
2193
2194                    memcpy(dst, src, take);
2195                    dst += take;
2196                    src += take;
2197
2198                    if (numright_used) {
2199                        if (rest_data) *dst++ = ' ';
2200                        else {
2201                            // fill in missing spaces for proper alignment of numright
2202                            size_t currSize = dst-dst_linestart;
2203                            size_t wantSize = line_size-numright_used-1;
2204                            if (currSize<wantSize) {
2205                                size_t spaces  = wantSize-currSize;
2206                                memset(dst, ' ', spaces);
2207                                dst           += spaces;
2208                            }
2209                        }
2210                        unsigned int num  = (src-src_start);
2211                        dst              += sprintf(dst, "%*u", numright_used, num);
2212                    }
2213
2214                    if (rest_data>0) {
2215                        *dst++ = '\n';
2216                        dst_linestart = dst;
2217                        if (numleft) {
2218                            unsigned int num  = (src-src_start)+1; // this goes to the '%u' (see comment above)
2219                            dst              += sprintf(dst, format, num);
2220                        }
2221                        else if (tab>0) {
2222                            memset(dst, ' ', tab);
2223                            dst += tab;
2224                        }
2225                    }
2226                }
2227
2228                free(format);
2229            }
2230
2231            *dst++ = 0;         // close str
2232
2233#if defined(DEBUG)
2234            { // check for array overflow
2235                size_t used_size = dst-result;
2236                gb_assert(used_size <= needed_size);
2237                ARB_realloc(result, used_size);
2238            }
2239#endif // DEBUG
2240        }
2241
2242        if (!error) PASS_2_OUT(args, result);
2243        else free(result);
2244    }
2245    return error;
2246}
2247
2248static GB_ERROR gbl_format         (GBL_command_arguments *args) { return format(args, true); }
2249static GB_ERROR gbl_format_sequence(GBL_command_arguments *args) { return format(args, false); }
2250
2251
2252static char *gbl_read_seq_sai_or_species(GBDATA *gb_main, const char *species, const char *sai, const char *ali, size_t *seqLen) {
2253    /* Reads the alignment 'ali'  of 'species' or 'sai'.
2254     * If 'ali' is NULp, use default alignment.
2255     * Returns NULp in case of error (which is exported then)
2256     */
2257
2258    char     *seq   = NULp;
2259    GB_ERROR  error = NULp;
2260
2261    int sources = !!species + !!sai;
2262    if (sources != 1) {
2263        error = "Either parameters 'species' or 'SAI' must be specified";
2264    }
2265    else {
2266        GBDATA     *gb_item = NULp;
2267        const char *what    = NULp;
2268        const char *name    = NULp;
2269
2270        if (species) {
2271            gb_item = GBT_find_species(gb_main, species);
2272            what    = "species";
2273            name    = species;
2274        }
2275        else {
2276            gb_item = GBT_find_SAI(gb_main, sai);
2277            what    = "SAI";
2278            name    = sai;
2279        }
2280
2281        if (!gb_item) error = GBS_global_string("Can't find %s '%s'", what, name);
2282        else {
2283            char *freeMe = NULp;
2284
2285            if (!ali) {
2286                ali = freeMe = GBT_get_default_alignment(gb_main);
2287                if (!ali) error = GB_await_error();
2288            }
2289
2290            if (ali) {
2291                GBDATA *gb_ali = GB_entry(gb_item, ali);
2292
2293                if (gb_ali) {
2294                    GBDATA *gb_seq;
2295
2296                    for (gb_seq = GB_child(gb_ali); gb_seq; gb_seq = GB_nextChild(gb_seq)) {
2297                        long type = GB_read_type(gb_seq);
2298                        if (type == GB_BITS) {
2299                            seq     = GB_read_bits(gb_seq, '-', '+');
2300                            if (seqLen) *seqLen = GB_read_bits_count(gb_seq);
2301                            break;
2302                        }
2303                        if (type == GB_STRING) {
2304                            seq     = GB_read_string(gb_seq);
2305                            if (seqLen) *seqLen = GB_read_string_count(gb_seq);
2306                            break;
2307                        }
2308                    }
2309                }
2310
2311                if (!seq) error = GBS_global_string("%s '%s' has no (usable) data in alignment '%s'", what, name, ali);
2312            }
2313            free(freeMe);
2314        }
2315    }
2316
2317    if (error) {
2318        gb_assert(!seq);
2319        GB_export_error(error);
2320    }
2321
2322    return seq;
2323}
2324
2325struct common_filter_params {
2326    const char *align;
2327    const char *sai;
2328    const char *species;
2329    int         first;
2330    int         pairwise;
2331};
2332
2333#define GBL_COMMON_FILTER_PARAMS                                                                                        \
2334    common_filter_params common_param;                                                                                  \
2335    GBL_STRUCT_PARAM_STRING(common_param, align,    "align=",    NULp, "alignment to use (defaults to default alignment)"); \
2336    GBL_STRUCT_PARAM_STRING(common_param, sai,      "SAI=",      NULp, "Use default sequence of given SAI as a filter"); \
2337    GBL_STRUCT_PARAM_STRING(common_param, species,  "species=",  NULp, "Use default sequence of given species as a filter"); \
2338    GBL_STRUCT_PARAM_BIT   (common_param, first,    "first=",    0,    "Use 1st stream as filter for other streams");   \
2339    GBL_STRUCT_PARAM_BIT   (common_param, pairwise, "pairwise=", 0,    "Use 1st stream as filter for 2nd, 3rd for 4th, ...")
2340
2341typedef char* (*filter_fun)(const char *seq, const char *filter, size_t flen, void *param);
2342/* Note:
2343 * filter_fun has to return a heap copy of the filter-result.
2344 * if 'flen' != 0, it contains the length of 'filter'
2345 * 'param' may be any client data
2346 */
2347
2348static GB_ERROR apply_filters(GBL_command_arguments *args, common_filter_params *common, filter_fun filter_one, void *param) {
2349    GB_ERROR error = NULp;
2350
2351    if (args->input.size()==0) error = "No input stream";
2352    else {
2353        int methodCount = !!common->sai + !!common->species + !!common->pairwise + !!common->first;
2354
2355        if (methodCount != 1) error = "Need exactly one of the parameters 'SAI', 'species', 'pairwise' or 'first'";
2356        else {
2357            if (common->pairwise) {
2358                if (args->input.size() % 2) error = "Using 'pairwise' requires an even number of input streams";
2359                else {
2360                    int i;
2361                    for (i = 1; i<args->input.size(); i += 2) {
2362                        PASS_2_OUT(args, filter_one(args->input.get(i), args->input.get(i-1), 0, param));
2363                    }
2364                }
2365            }
2366            else {
2367                int     i      = 0;
2368                char   *filter = NULp;
2369                size_t  flen   = 0;
2370
2371                if (common->first) {
2372                    if (args->input.size()<2) error = "Using 'first' needs at least 2 input streams";
2373                    else {
2374                        const char *in = args->input.get(i++);
2375                        gb_assert(in);
2376
2377                        flen   = strlen(in);
2378                        filter = ARB_strduplen(in, flen);
2379                    }
2380                }
2381                else {
2382                    filter = gbl_read_seq_sai_or_species(args->get_gb_main(), common->species, common->sai, common->align, &flen);
2383                    if (!filter) error = GB_await_error();
2384                }
2385
2386                gb_assert(filter || error);
2387                if (filter) {
2388                    for (; i<args->input.size(); ++i) {
2389                        PASS_2_OUT(args, filter_one(args->input.get(i), filter, flen, param));
2390                    }
2391                }
2392                free(filter);
2393            }
2394        }
2395    }
2396    return error;
2397}
2398
2399// -------------------------
2400//      calculate diff
2401
2402struct diff_params {
2403    char equalC;
2404    char diffC;
2405};
2406static char *calc_diff(const char *seq, const char *filter, size_t /*flen*/, void *paramP) {
2407    // filters 'seq' through 'filter'
2408    // - replace all equal     positions by 'equal_char' (if != 0)
2409    // - replace all differing positions by 'diff_char'  (if != 0)
2410
2411    diff_params *param      = (diff_params*)paramP;
2412    char         equal_char = param->equalC;
2413    char         diff_char  = param->diffC;
2414
2415    char *result = ARB_strdup(seq);
2416    int   p;
2417
2418    for (p = 0; result[p] && filter[p]; ++p) {
2419        if (result[p] == filter[p]) {
2420            if (equal_char) result[p] = equal_char;
2421        }
2422        else {
2423            if (diff_char) result[p] = diff_char;
2424        }
2425    }
2426
2427    // if 'seq' is longer than 'filter' and diff_char is given
2428    // -> fill rest of 'result' with 'diff_char'
2429    if (diff_char) {
2430        for (; result[p]; ++p) {
2431            result[p] = diff_char;
2432        }
2433    }
2434
2435    return result;
2436}
2437static GB_ERROR gbl_diff(GBL_command_arguments *args) {
2438    GBL_BEGIN_PARAMS;
2439    GBL_COMMON_FILTER_PARAMS;
2440
2441    diff_params param;
2442    GBL_STRUCT_PARAM_CHAR(param, equalC,   "equal=",    '.', "symbol for equal characters");
2443    GBL_STRUCT_PARAM_CHAR(param, diffC,    "differ=",   0,   "symbol for diff characters (default: use char from input stream)");
2444
2445    GBL_TRACE_PARAMS(args);
2446    GBL_END_PARAMS;
2447
2448    return apply_filters(args, &common_param, calc_diff, &param);
2449}
2450
2451// -------------------------
2452//      standard filter
2453
2454enum filter_function { FP_FILTER, FP_MODIFY };
2455
2456struct filter_params { // used by gbl_filter and gbl_change_gc
2457    filter_function function;
2458
2459    const char *include;
2460    const char *exclude;
2461
2462    // FP_MODIFY only:
2463    int         change_pc;
2464    const char *change_to;
2465};
2466
2467static char *filter_seq(const char *seq, const char *filter, size_t flen, void *paramP) {
2468    filter_params *param = (filter_params*)paramP;
2469
2470    size_t slen     = strlen(seq);
2471    if (!flen) flen = strlen(filter);
2472    size_t mlen     = slen<flen ? slen : flen;
2473
2474    GBS_strstruct out(mlen+1); // +1 to avoid invalid, zero-length buffer
2475
2476    const char *charset;
2477    int         include;
2478
2479    if (param->include) {
2480        charset = param->include;
2481        include = 1;
2482    }
2483    else {
2484        gb_assert(param->exclude);
2485        charset = param->exclude;
2486        include = 0;
2487    }
2488
2489    size_t pos  = 0;
2490    size_t rest = slen;
2491    size_t ctl  = 0;
2492    if (param->function == FP_MODIFY) ctl  = strlen(param->change_to);
2493
2494    int inset = 1; // 1 -> check chars in charset, 0 -> check chars NOT in charset
2495    while (rest) {
2496        size_t count;
2497        if (pos >= flen) {      // behind filter
2498            // trigger last loop
2499            count = rest;
2500            inset = 0; // if 'include' -> 'applies' will get false, otherwise true
2501                       // (meaning is: behind filter nothing can match 'include' or 'exclude')
2502        }
2503        else {
2504            count = (inset ? strspn : strcspn)(filter+pos, charset); // count how many chars are 'inset'
2505        }
2506        if (count) {
2507            int applies = !!include == !!inset; // true -> 'filter' matches 'include' or doesn't match 'exclude'
2508            if (count>rest) count = rest;
2509
2510            switch (param->function) {
2511                case FP_FILTER:
2512                    if (applies) out.ncat(seq+pos, count);
2513                    break;
2514
2515                case FP_MODIFY:
2516                    if (applies) { // then modify
2517                        size_t i;
2518                        for (i = 0; i<count; i++) {
2519                            char c                                               = seq[pos+i];
2520                            if (isalpha(c) && GB_random(100)<param->change_pc) c = param->change_to[GB_random(ctl)];
2521                            out.put(c);
2522                        }
2523                    }
2524                    else { // otherwise simply copy
2525                        out.ncat(seq+pos, count);
2526                    }
2527                    break;
2528            }
2529
2530            pos  += count;
2531            rest -= count;
2532        }
2533        inset = 1-inset; // toggle
2534    }
2535    return out.release();
2536}
2537
2538static GB_ERROR gbl_filter(GBL_command_arguments *args) {
2539    GBL_BEGIN_PARAMS;
2540    GBL_COMMON_FILTER_PARAMS;
2541
2542    filter_params param;
2543    GBL_STRUCT_PARAM_STRING(param, exclude, "exclude=", NULp, "Exclude colums");
2544    GBL_STRUCT_PARAM_STRING(param, include, "include=", NULp, "Include colums");
2545    param.function = FP_FILTER;
2546
2547    GBL_TRACE_PARAMS(args);
2548    GBL_END_PARAMS;
2549
2550    GB_ERROR error  = NULp;
2551    int      inOrEx = !!param.include + !!param.exclude;
2552
2553    if (inOrEx != 1)    error = "Need exactly one parameter of: 'include', 'exclude'";
2554    else error                = apply_filters(args, &common_param, filter_seq, &param);
2555
2556    return error;
2557}
2558
2559static GB_ERROR gbl_change_gc(GBL_command_arguments *args) {
2560    GBL_BEGIN_PARAMS;
2561    GBL_COMMON_FILTER_PARAMS;
2562
2563    filter_params param;
2564    GBL_STRUCT_PARAM_STRING(param, exclude,   "exclude=", NULp, "Exclude colums");
2565    GBL_STRUCT_PARAM_STRING(param, include,   "include=", NULp, "Include colums");
2566    GBL_STRUCT_PARAM_INT   (param, change_pc, "change=",  0,    "percentage of changed columns (default: silently change nothing)");
2567    GBL_STRUCT_PARAM_STRING(param, change_to, "to=",      "GC", "change to one of this");
2568    param.function = FP_MODIFY;
2569
2570    GBL_TRACE_PARAMS(args);
2571    GBL_END_PARAMS;
2572
2573    GB_ERROR error  = NULp;
2574    int      inOrEx = !!param.include + !!param.exclude;
2575
2576    if (inOrEx != 1) error = "Need exactly one parameter of: 'include', 'exclude'";
2577    else {
2578        error = apply_filters(args, &common_param, filter_seq, &param);
2579    }
2580
2581    return error;
2582}
2583
2584static GB_ERROR gbl_exec(GBL_command_arguments *args) {
2585    EXPECT_PARAMS_PASSED(args, "command[,arguments]+");
2586
2587    // write inputstreams to temp file:
2588    GB_ERROR error = NULp;
2589    char *inputname;
2590    {
2591        char *filename = GB_unique_filename("arb_exec_input", "tmp");
2592        FILE *out      = GB_fopen_tempfile(filename, "wt", &inputname);
2593
2594        if (!out) error = GB_await_error();
2595        else {
2596            for (int i = 0; i<args->input.size(); i++) {
2597                fprintf(out, "%s\n", args->input.get(i));
2598            }
2599            fclose(out);
2600        }
2601        free(filename);
2602    }
2603
2604    if (!error) {
2605        // build shell command to execute
2606        char *sys;
2607        {
2608            GBS_strstruct str(1000);
2609
2610            str.cat(args->get_param(0));
2611            for (int i = 1; i<args->param_count(); i++) {
2612                str.cat(" \'");
2613                str.cat(args->get_param(i)); // @@@ use GBK_singlequote here?
2614                str.put('\'');
2615            }
2616            str.cat(" <");
2617            str.cat(inputname);
2618
2619            sys = str.release();
2620        }
2621
2622        char *result = NULp;
2623        {
2624            FILE *in = popen(sys, "r");
2625            if (in) {
2626                GBS_strstruct str(4096);
2627
2628                int i;
2629                while ((i=getc(in)) != EOF) { str.put(i); }
2630                result = str.release();
2631                pclose(in);
2632            }
2633            else {
2634                error = GBS_global_string("Cannot execute shell command '%s'", sys);
2635            }
2636        }
2637
2638        if (!error) {
2639            gb_assert(result);
2640            PASS_2_OUT(args, result);
2641        }
2642
2643        free(sys);
2644    }
2645
2646    gb_assert(GB_is_privatefile(inputname, false));
2647    GB_unlink_or_warn(inputname, &error);
2648    free(inputname);
2649
2650    return error;
2651}
2652
2653
2654static GBL_command_definition gbl_command_table[] = {
2655    { "ali_name",        gbl_ali_name },
2656    { "caps",            gbl_caps },
2657    { "change",          gbl_change_gc },
2658    { "checksum",        gbl_checksum },
2659    { "command",         gbl_command },
2660    { "compare",         gbl_compare },
2661    { "colsplit",        gbl_colsplit },
2662    { "icompare",        gbl_icompare },
2663    { "contains",        gbl_contains },
2664    { "icontains",       gbl_icontains },
2665    { "count",           gbl_count },
2666    { "crop",            gbl_crop },
2667    { "cut",             gbl_cut },
2668    { "dd",              gbl_dd },
2669    { "define",          gbl_define },
2670    { "diff",            gbl_diff },
2671    { "div",             gbl_div },
2672    { "fdiv",            gbl_fdiv },
2673    { "do",              gbl_do },
2674    { "drop",            gbl_drop },
2675    { "dropempty",       gbl_dropempty },
2676    { "dropzero",        gbl_dropzero },
2677    { "echo",            gbl_echo },
2678    { "equals",          gbl_equals },
2679    { "iequals",         gbl_iequals },
2680    { "escape",          gbl_escape },
2681    { "unescape",        gbl_unescape },
2682    { "eval",            gbl_eval },
2683    { "exec",            gbl_exec },
2684    { "export_sequence", gbl_export_sequence },
2685    { "extract_sequence", gbl_extract_sequence },
2686    { "extract_words",   gbl_extract_words },
2687    { "filter",          gbl_filter },
2688    { "findspec",        gbl_findspec },
2689    { "findacc",         gbl_findacc },
2690    { "findgene",        gbl_findgene },
2691    { "format",          gbl_format },
2692    { "format_sequence", gbl_format_sequence },
2693    { "gcgchecksum",     gbl_gcgchecksum },
2694    { "head",            gbl_head },
2695    { "inRange",         gbl_inRange },
2696    { "isAbove",         gbl_isAbove },
2697    { "isBelow",         gbl_isBelow },
2698    { "isEqual",         gbl_isEqual },
2699    { "isEmpty",         gbl_isEmpty },
2700    { "keep",            gbl_keep },
2701    { "left",            gbl_head },
2702    { "len",             gbl_len },
2703    { "lower",           gbl_lower },
2704    { "merge",           gbl_merge },
2705    { "mid",             gbl_mid },
2706    { "mid0",            gbl_mid0 },
2707    { "minus",           gbl_minus },
2708    { "fminus",          gbl_fminus },
2709    { "mult",            gbl_mult },
2710    { "fmult",           gbl_fmult },
2711    { "and",             gbl_and },
2712    { "or",              gbl_or },
2713    { "not",             gbl_not },
2714    { "origin_gene",     gbl_origin_gene },
2715    { "origin_organism", gbl_origin_organism },
2716    { "partof",          gbl_partof },
2717    { "ipartof",         gbl_ipartof },
2718    { "per_cent",        gbl_per_cent },
2719    { "fper_cent",       gbl_fper_cent },
2720    { "plus",            gbl_plus },
2721    { "fplus",           gbl_fplus },
2722    { "pretab",          gbl_pretab },
2723    { "quote",           gbl_quote },
2724    { "unquote",         gbl_unquote },
2725    { "readdb",          gbl_readdb },
2726    { "remove",          gbl_remove },
2727    { "rest",            gbl_rest },
2728    { "right",           gbl_tail },
2729    { "round",           gbl_round },
2730    { "select",          gbl_select },
2731    { "sequence",        gbl_sequence },
2732    { "sequence_type",   gbl_sequence_type },
2733    { "split",           gbl_split },
2734    { "srt",             gbl_srt },
2735    { "streams",         gbl_streams },
2736    { "swap",            gbl_swap },
2737    { "tab",             gbl_tab },
2738    { "tail",            gbl_tail },
2739    { "taxonomy",        gbl_taxonomy },
2740    { "toback",          gbl_toback },
2741    { "tofront",         gbl_tofront },
2742    { "trace",           gbl_trace },
2743    { "translate",       gbl_translate },
2744    { "upper",           gbl_upper },
2745
2746    { NULp, NULp }
2747};
2748
2749const GBL_command_lookup_table& ACI_get_standard_commands() {
2750    static GBL_command_lookup_table clt(gbl_command_table, ARRAY_ELEMS(gbl_command_table)-1);
2751    return clt;
2752}
2753
Note: See TracBrowser for help on using the repository browser.