source: branches/profile/ARBDB/adlang1.cxx

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