source: tags/ms_r17q3/ARBDB/adlang1.cxx

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