source: tags/ms_r16q3/ARBDB/adlang1.cxx

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