source: branches/alilink/ARBDB/adlang1.cxx

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