source: branches/help/PERLTOOLS/arb_proto_2_xsub.cxx

Last change on this file was 18748, checked in by westram, 3 years ago
  • explicitely define function prefixes.
  • link to interface generator code (and vv).
  • bail out on ambiguous function suffix.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.1 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : arb_proto_2_xsub.cxx                               //
4//   Purpose   : generate ARB.xs for perl interface                 //
5//                                                                  //
6//   ReCoded by Ralf Westram (coder@reallysoft.de) in December 2009 //
7//   Institute of Microbiology (Technical University Munich)        //
8//   http://www.arb-home.de/                                        //
9//                                                                  //
10// ================================================================ //
11
12#include <arbdb.h>
13#include <arb_strbuf.h>
14#include <BufferedFileReader.h>
15
16#include <string>
17#include <vector>
18#include <set>
19
20#include <cctype>
21#include <unistd.h>
22#include <arb_str.h>
23#include <arb_diff.h>
24
25#if defined(DEBUG)
26#if defined(DEVEL_RALF)
27// #define TRACE
28#endif // DEVEL_RALF
29#endif // DEBUG
30
31using namespace std;
32
33// --------------------------------------------------------------------------------
34
35#define CHAR_PTR       "char *"
36#define CONST_CHAR_PTR "const char *"
37
38// --------------------------------------------------------------------------------
39
40struct Error {
41    virtual ~Error() {}
42    virtual void print() const = 0;
43};
44
45class ProgramError : public Error {
46    string error;
47public:
48    ProgramError(string message) : error(message) {}
49    ProgramError(const char *message) : error(message) {}
50    ~ProgramError() OVERRIDE {}
51
52    void print() const OVERRIDE {
53        fprintf(stderr, "arb_proto_2_xsub: Error: %s\n", error.c_str());
54    }
55};
56
57class InputFileError : public Error {
58    string located_error;
59public:
60    InputFileError(LineReader& fileBuffer, string message)      : located_error(fileBuffer.lineError(message)) {}
61    InputFileError(LineReader& fileBuffer, const char *message) : located_error(fileBuffer.lineError(message)) {}
62    ~InputFileError() OVERRIDE {}
63
64    void print() const OVERRIDE {
65        fputs(located_error.c_str(), stderr);
66        fputc('\n', stderr);
67    }
68};
69
70// --------------------------------------------------------------------------------
71
72class CommentSkippingFileBuffer : public BufferedFileReader {
73    string open_comment;
74    string close_comment;
75    string eol_comment;
76
77    void throw_error(const char *message) __ATTR__NORETURN { throw InputFileError(*this, message); }
78
79    string read_till_close_comment(string curr_line, size_t comment_startLineNumber) {
80        bool seen_end = false;
81        while (!seen_end) {
82            size_t close = curr_line.find(close_comment);
83            if (close != string::npos) {
84                curr_line = curr_line.substr(close+close_comment.length());
85                seen_end  = true;
86            }
87            else {
88                if (!BufferedFileReader::getLine(curr_line)) {
89                    setLineNumber(comment_startLineNumber);
90                    throw_error("end of file reached while skipping comment");
91                }
92            }
93        }
94        return curr_line;
95    }
96
97public:
98    CommentSkippingFileBuffer(const string&  filename_,
99                              FILE          *in,
100                              const char    *openComment,
101                              const char    *closeComment,
102                              const char    *eolComment) :
103        BufferedFileReader(filename_, in),
104        open_comment(openComment),
105        close_comment(closeComment),
106        eol_comment(eolComment)
107    {}
108
109private:
110    bool getLine_intern(string& line) OVERRIDE {
111        if (BufferedFileReader::getLine_intern(line)) {
112            size_t open = line.find(open_comment);
113            size_t eol  = line.find(eol_comment);
114
115            if (open != eol) {                      // comment found
116                if (open<eol) {
117                    if (eol != string::npos) {
118                        throw_error(GBS_global_string("'%s' inside '%s %s'", eol_comment.c_str(), open_comment.c_str(), close_comment.c_str()));
119                    }
120                    line = line.substr(0, open) + read_till_close_comment(line.substr(open+2), getLineNumber());
121                }
122                else {
123                    arb_assert(eol<open);
124                    if (open != string::npos) {
125                        throw_error(GBS_global_string("'%s' behind '%s'", open_comment.c_str(), eol_comment.c_str()));
126                    }
127                    line = line.substr(0, eol);
128                }
129            }
130            return true;
131        }
132        return false;
133    }
134};
135
136// --------------------------------------------------------------------------------
137
138inline bool is_empty_code(const char *code) { return !code[0]; }
139inline bool contains_preprozessorCode(const char *code) { return strchr(code, '#'); }
140inline bool contains_braces(const char *code) { return strpbrk(code, "{}"); }
141inline bool is_typedef(const char *code) { return ARB_strBeginsWith(code, "typedef"); }
142inline bool is_forward_decl(const char *code) { return ARB_strBeginsWith(code, "class") || ARB_strBeginsWith(code, "struct"); }
143
144inline bool is_prototype(const char *code) {
145    return
146        !is_empty_code(code)             &&
147        !contains_preprozessorCode(code) &&
148        !contains_braces(code)           &&
149        !is_typedef(code)                &&
150        !is_forward_decl(code);
151}
152
153inline void trace_over_braces(const char *code, int& brace_counter) {
154    while (code) {
155        const char *brace = strpbrk(code, "{}");
156        if (!brace) break;
157
158        if (*brace == '{') {
159            ++brace_counter;
160        }
161        else {
162            arb_assert(*brace == '}');
163            --brace_counter;
164        }
165        code = brace+1;
166    }
167}
168
169// --------------------------------------------------------------------------------
170
171inline char *get_token_and_incr_lineno(const char*& code, const char *separator, size_t *lineno) {
172    char *token = NULp;
173    if (code) {
174        const char *sep_pos = strpbrk(code, separator);
175
176        if (!sep_pos) {
177            if (!code[0]) {                         // end of code
178                token = NULp;
179                code  = NULp;
180            }
181            else {
182                token = ARB_strdup(code);
183                code  = NULp;
184            }
185        }
186        else {
187            token = ARB_strpartdup(code, sep_pos-1);
188
189            const char *behind_sep = sep_pos + strspn(sep_pos, separator); // next non 'separator' char
190            if (lineno) {
191                int no_of_linefeeds = 0;
192                while (code<behind_sep) if (*++code == '\n') ++no_of_linefeeds;
193
194                *lineno += no_of_linefeeds;
195            }
196            else {
197                code = behind_sep;
198            }
199        }
200    }
201    return token;
202}
203
204inline char *get_token(const char*& code, const char *separator) {
205    return get_token_and_incr_lineno(code, separator, NULp);
206}
207
208inline bool is_ID_char(char c)  { return isalnum(c) || c == '_'; }
209
210inline const char *next_closing_paren(const char *code) {
211    const char *open_paren  = strchr(code, '(');
212    const char *close_paren = strchr(code, ')');
213
214    if (!open_paren || (close_paren && close_paren<open_paren)) return close_paren;
215
216    close_paren = next_closing_paren(open_paren+1);
217    return next_closing_paren(close_paren+1);
218}
219
220inline const char *next_comma_outside_parens(const char *code) {
221    const char *comma = strchr(code, ',');
222    if (comma) {
223        const char *open_paren = strchr(code, '(');
224        if (open_paren && open_paren<comma) {
225            const char *close_paren = next_closing_paren(open_paren+1);
226            if (!close_paren) throw "Unbalanced parenthesis";
227            comma = next_comma_outside_parens(close_paren+1);
228        }
229    }
230    return comma;
231}
232
233inline bool find_open_close_paren(const char *code, size_t& opening_paren_pos, size_t& closing_paren_pos) {
234    const char *open_paren = strchr(code, '(');
235    if (open_paren) {
236        const char *close_paren = next_closing_paren(open_paren+1);
237        if (close_paren) {
238            opening_paren_pos = open_paren-code;
239            closing_paren_pos = close_paren-code;
240            return true;
241        }
242    }
243    return false;
244}
245
246inline string concat_type_and_name(const string& type, const string& name) {
247    if (type.at(type.length()-1) == '*') return type+name;
248    return type+' '+name;
249}
250
251// ----------------
252//      TypeMap
253
254class TypeMap {
255    // representation of types mapped in 'typemap' file
256    set<string> defined_types;
257
258public:
259    TypeMap() {}
260
261    void load(LineReader& typemap);
262    bool has_definition_for(const string& type_decl) const {
263        return defined_types.find(type_decl) != defined_types.end();
264    }
265};
266
267// -------------
268//      Type
269
270enum TypeClass {
271    INVALID_TYPE,                                   // invalid
272
273    VOID,                                           // no parameter
274    SIMPLE_TYPE,                                    // simple types like int, float, double, ...
275    CONST_CHAR,                                     // 'const char *'
276    HEAP_COPY,                                      // type is 'char*' and interpreted as heap-copy
277    CONVERSION_FUNCTION,                            // convert type using GBP_-conversion functions
278    TYPEMAPPED,                                     // type is defined in file 'typemap'
279
280    CANT_HANDLE,                                    // type cannot be used in perl interface
281    FORBIDDEN,                                      // usage forbidden via 'NOT4PERL'
282};
283
284#if defined(TRACE)
285#define TypeClass2CSTR(type) case type: return #type
286const char *get_TypeClass_name(TypeClass type_class) {
287    switch (type_class) {
288        TypeClass2CSTR(INVALID_TYPE);
289        TypeClass2CSTR(VOID);
290        TypeClass2CSTR(SIMPLE_TYPE);
291        TypeClass2CSTR(CONST_CHAR);
292        TypeClass2CSTR(HEAP_COPY);
293        TypeClass2CSTR(CONVERSION_FUNCTION);
294        TypeClass2CSTR(TYPEMAPPED);
295        TypeClass2CSTR(CANT_HANDLE);
296        TypeClass2CSTR(FORBIDDEN);
297    }
298    return NULp;
299}
300#undef TypeClass2CSTR
301#endif // TRACE
302
303inline string type2id(const string& type) {
304    char *s = GBS_string_eval(type.c_str(),
305                              "const =:"           // remove const (for less ugly names)
306                              " =:"                // remove spaces
307                              "\\*=Ptr");          // rename '*'
308
309    string id(s);
310    free(s);
311    return id;
312}
313inline string conversion_function_name(const string& fromType, const string& toType) {
314    string from = type2id(fromType);
315    string to   = type2id(toType);
316    return string("GBP_")+from+"_2_"+to;
317}
318inline string constCastTo(const string& expr, const string& targetType) {
319    return string("const_cast<")+targetType+">("+expr+")";
320}
321
322class Type { // return- or parameter-type
323    string    c_type;
324    string    perl_type;
325    TypeClass type_class;
326
327    string unify_type_decl(const char *code) {
328        string type_decl;
329        enum { SPACE, STAR, ID, UNKNOWN } last = SPACE, curr;
330        for (int i = 0; code[i]; ++i) {
331            char c = code[i];
332
333            switch (c) {
334                case ' ': curr = SPACE; break;
335                case '*': curr = STAR; break;
336                default: curr = is_ID_char(c) ? ID : UNKNOWN; break;
337            }
338
339            if (last != SPACE && curr != last) type_decl += ' ';
340            if (curr != SPACE)                 type_decl += c;
341
342            last = curr;
343        }
344
345        return last == SPACE
346            ? type_decl.substr(0, type_decl.length()-1)
347            : type_decl;
348    }
349
350    void throw_if_enum() const {
351        size_t enum_pos = c_type.find("enum ");
352        if (enum_pos != string::npos) {
353            const char *enum_type = c_type.c_str()+enum_pos;
354            const char *enum_name = enum_type+5;
355            throw GBS_global_string("do not use '%s', simply use '%s'", enum_type, enum_name);
356        }
357    }
358
359    string convertExpression(const string& expr, const string& fromType, const string& toType) const {
360        arb_assert(possible_in_xsub());
361        throw_if_enum();
362        if (get_TypeClass() == CONVERSION_FUNCTION) {
363            string conversion_function = conversion_function_name(fromType, toType);
364            return conversion_function+"("+expr+")"; // result is toType
365        }
366        return expr;
367    }
368
369    bool cant_handle(const string& type_decl) {
370        return
371            strpbrk(type_decl.c_str(), "().*")                 || // function-parameters, pointer-types not handled in ctor
372            type_decl.find("GB_CB")            != string::npos || // some typedef'ed function-parameters
373            type_decl.find("CharPtrArray")     != string::npos ||
374            type_decl.find("StrArray")         != string::npos ||
375            type_decl.find("GB_Link_Follower") != string::npos;
376    }
377
378    bool is_forbidden(const string& type_decl) {
379        return
380            type_decl.find("NOT4PERL")            != string::npos || // 'NOT4PERL' declares prototype as "FORBIDDEN"
381            type_decl.find("GBQUARK")             != string::npos || // internal information, hide from perl
382            type_decl.find("GB_COMPRESSION_MASK") != string::npos || // internal information, hide from perl
383            type_decl.find("GB_CBUFFER")          != string::npos || // internal ARBDB buffers
384            type_decl.find("GB_BUFFER")           != string::npos; // memory managed by ARBDB
385    }
386
387
388public:
389    static TypeMap globalTypemap;
390
391    Type() : type_class(INVALID_TYPE) {}
392    Type(const char *code) {
393        c_type = unify_type_decl(code);
394
395        if (c_type == "void") { type_class = VOID; }
396        else if (c_type == CONST_CHAR_PTR ||
397                 c_type == "GB_ERROR"     ||
398                 c_type == "GB_CSTR")
399        {
400            type_class = CONST_CHAR;
401            perl_type  = CHAR_PTR;
402        }
403        else if (c_type == CHAR_PTR) {
404            type_class = HEAP_COPY;
405            perl_type  = c_type;
406        }
407        // [Code-TAG: enum_type_replacement]
408        // for each enum type converted here, you need to support a
409        // conversion function in ../ARBDB/adperl.cxx@enum_conversion_functions
410        else if (c_type == "GB_CASE"        ||
411                 c_type == "GB_CB_TYPE"     ||
412                 c_type == "GB_TYPES"       ||
413                 c_type == "GB_UNDO_TYPE"   ||
414                 c_type == "GB_SEARCH_TYPE" ||
415                 c_type == "GB_alignment_type")
416        {
417            type_class = CONVERSION_FUNCTION;
418            perl_type  = CHAR_PTR;
419        }
420        else if (globalTypemap.has_definition_for(c_type)) {
421            type_class = TYPEMAPPED;
422            perl_type  = c_type;
423        }
424        else if (cant_handle(c_type)) { type_class = CANT_HANDLE; }
425        else if (is_forbidden(c_type)) { type_class = FORBIDDEN; } // Caution: this line catches all '*' types not handled above
426        else {
427            type_class = SIMPLE_TYPE;
428            perl_type  = c_type;
429        }
430    }
431
432    const string& c_decl() const { return c_type; }
433    const string& perl_decl() const { return perl_type; }
434
435    TypeClass get_TypeClass() const { return type_class; }
436    bool isVoid() const { return get_TypeClass() == VOID; }
437
438    bool possible_in_xsub() const { return type_class != CANT_HANDLE && type_class != FORBIDDEN; }
439
440    string convert_argument_for_C(const string& perl_arg) const {
441        if (perl_decl() == CHAR_PTR) {
442            if (c_decl() == CHAR_PTR) throw "argument of type 'char*' is forbidden";
443            string const_perl_arg = constCastTo(perl_arg, CONST_CHAR_PTR); // ensure C uses 'const char *'
444            return convertExpression(const_perl_arg, CONST_CHAR_PTR, c_decl());
445        }
446        return convertExpression(perl_arg, perl_decl(), c_decl());
447    }
448    string convert_result_for_PERL(const string& c_expr) const {
449        arb_assert(type_class != HEAP_COPY);
450        if (perl_decl() == CHAR_PTR) {
451            string const_c_expr = convertExpression(c_expr, c_decl(), CONST_CHAR_PTR);
452            return constCastTo(const_c_expr, CHAR_PTR);
453        }
454        return convertExpression(c_expr, c_decl(), perl_decl());
455    }
456
457#if defined(TRACE)
458    void dump_if_impossible_in_xsub(FILE *out) const {
459        if (!possible_in_xsub()) {
460            fprintf(out, "TRACE: - impossible type '%s' (TypeClass='%s')\n",
461                    c_type.c_str(), get_TypeClass_name(type_class));
462        }
463    }
464#endif // TRACE
465
466};
467
468TypeMap Type::globalTypemap;
469
470// ------------------
471//      Parameter
472
473class Parameter {
474    Type   type;
475    string name;
476
477    static long nonameCount;
478
479public:
480    Parameter() {}
481    Parameter(const char *code) {
482        const char *last = strchr(code, 0)-1;
483        while (last[0] == ' ') --last;
484
485        const char *name_start = last;
486        while (name_start >= code && is_ID_char(name_start[0])) --name_start;
487        name_start++;
488
489        if (name_start>code) {
490            string type_def(code, name_start-code);
491            name = string(name_start, last-name_start+1);
492            type = Type(type_def.c_str());
493
494            if (type.possible_in_xsub() && !type.isVoid() && name.empty()) {
495                name = GBS_global_string("noName%li", ++nonameCount);
496            }
497        }
498        else if (strcmp(name_start, "void") == 0) {
499            string no_type(name_start, last-name_start+1);
500            name = "";
501            type = Type(no_type.c_str());
502        }
503        else {
504            throw string("can't parse '")+code+"' (expected 'type name')";
505        }
506    }
507
508    const string& get_name() const { return name; }
509    const Type& get_type() const { return type; }
510
511    TypeClass get_TypeClass() const { return get_type().get_TypeClass(); }
512    bool isVoid() const { return get_TypeClass() == VOID; }
513
514    string perl_typed_param() const { return concat_type_and_name(type.perl_decl(), name); }
515    string c_typed_param   () const { return concat_type_and_name(type.c_decl   (), name); }
516};
517
518long Parameter::nonameCount = 0;
519
520// ------------------
521//      Prototype
522
523typedef vector<Parameter>         Arguments;
524typedef Arguments::const_iterator ArgumentIter;
525
526class Prototype {
527    Parameter function;                             // return-type + function_name
528    Arguments arguments;
529
530    void parse_arguments(const char *arg_list) {
531        const char *comma = next_comma_outside_parens(arg_list);
532        if (comma) {
533            {
534                char *first_param = ARB_strpartdup(arg_list, comma-1);
535                arguments.push_back(Parameter(first_param));
536                free(first_param);
537            }
538            parse_arguments(comma+1);
539        }
540        else { // only one parameter
541            arguments.push_back(Parameter(arg_list));
542        }
543    }
544
545public:
546    Prototype(const char *code) {
547        size_t open_paren, close_paren;
548        if (!find_open_close_paren(code, open_paren, close_paren)) {
549            throw "expected parenthesis";
550        }
551
552        string return_type_and_name(code, open_paren);
553        function = Parameter(return_type_and_name.c_str());
554
555        string arg_list(code+open_paren+1, close_paren-open_paren-1);
556        parse_arguments(arg_list.c_str());
557    }
558
559    const Type& get_return_type() const { return function.get_type(); }
560    const string& get_function_name() const { return function.get_name(); }
561
562    ArgumentIter args_begin() const { return arguments.begin(); }
563    ArgumentIter args_end() const { return arguments.end(); }
564
565    string argument_names_list() const {
566        string       argument_list;
567        bool         first   = true;
568        ArgumentIter arg_end = arguments.end();
569
570        for (ArgumentIter param = arguments.begin(); param != arg_end; ++param) {
571            if (!param->isVoid()) {
572                if (first) first    = false;
573                else argument_list += ", ";
574
575                argument_list += param->get_name();
576            }
577        }
578        return argument_list;
579    }
580
581    string call_arguments() const {
582        string       argument_list;
583        bool         first   = true;
584        ArgumentIter arg_end = arguments.end();
585
586        for (ArgumentIter arg = arguments.begin(); arg != arg_end; ++arg) {
587            if (!arg->isVoid()) {
588                if (first) first    = false;
589                else argument_list += ", ";
590
591                argument_list += arg->get_type().convert_argument_for_C(arg->get_name());
592            }
593        }
594        return argument_list;
595    }
596
597    bool possible_as_xsub() const {
598        if (get_return_type().possible_in_xsub()) {
599            ArgumentIter arg_end = arguments.end();
600            for (ArgumentIter arg = arguments.begin(); arg != arg_end; ++arg) {
601                if (!arg->get_type().possible_in_xsub()) {
602                    return false;
603                }
604            }
605            return true;
606        }
607        return false;
608    }
609
610#if defined(TRACE)
611    void dump_types_impossible_in_xsub(FILE *out) const {
612        get_return_type().dump_if_impossible_in_xsub(out);
613        ArgumentIter arg_end = arguments.end();
614        for (ArgumentIter arg = arguments.begin(); arg != arg_end; ++arg) {
615            arg->get_type().dump_if_impossible_in_xsub(out);
616        }
617    }
618#endif // TRACE
619};
620
621inline void trim(string& text) {
622    const char *whiteSpace = " \t";
623    size_t      leading    = text.find_first_not_of(whiteSpace);
624    size_t      trailing   = text.find_last_not_of(whiteSpace, leading);
625
626    if (trailing != string::npos) {
627        text = text.substr(leading, text.length()-leading-trailing);
628    }
629}
630
631void TypeMap::load(LineReader& typemap_reader) {
632    string line;
633    while (typemap_reader.getLine(line)) {
634        if (line == "TYPEMAP") {
635            while (typemap_reader.getLine(line)) {
636                trim(line);
637                if (!line.empty()) {
638                    Parameter     typemapping(line.c_str());
639                    const string& c_type = typemapping.get_type().c_decl();
640                    defined_types.insert(c_type);
641                }
642            }
643            return;
644        }
645    }
646
647    throw InputFileError(typemap_reader, "Expected to see 'TYPEMAP'");
648}
649
650// ----------------
651//      Package
652
653class Package : virtual Noncopyable {
654    string         prefix;                          // e.g. 'P2A' or 'P2AT'
655    string         name;                            // e.g. 'ARB' or 'BIO'
656    GBS_strstruct *generated_code;
657    GB_HASH       *functions_to_skip;
658
659public:
660    Package(const char *name_, const char *prefix_) :
661        prefix(prefix_),
662        name(name_)
663    {
664        generated_code    = GBS_stropen(100000);
665        functions_to_skip = GBS_create_hash(1000, GB_MIND_CASE);
666    }
667    ~Package() {
668        GBS_free_hash(functions_to_skip);
669        GBS_strforget(generated_code);
670    }
671
672    bool matches_package_prefix(const string& text) const { return text.find(prefix) == 0 && text.at(prefix.length()) == '_'; }
673
674    void mark_function_defined(const string& function) { GBS_write_hash(functions_to_skip, function.c_str(), 1); }
675    bool not_defined(const string& function) const { return GBS_read_hash(functions_to_skip, function.c_str()) == 0; }
676
677    const string& get_prefix() const { return prefix; }
678
679    void append_code(const string& code) { GBS_strncat(generated_code, code.c_str(), code.length()); }
680    void append_code(const char *code) { append_code(string(code)); }
681    void append_code(char code) { GBS_chrcat(generated_code, code); }
682
683    void append_linefeed(size_t count = 1) { while (count--) append_code("\n"); }
684
685    void print_xsubs(FILE *file) {
686        fputs("# --------------------------------------------------------------------------------\n", file);
687        fprintf(file, "MODULE = ARB  PACKAGE = %s  PREFIX = %s_\n\n", name.c_str(), prefix.c_str());
688        fputs(GBS_mempntr(generated_code), file);
689    }
690};
691
692// ----------------------
693//      xsubGenerator
694
695class xsubGenerator {
696    Package arb;
697    Package bio;
698
699    void generate_xsub(const Prototype& prototype);
700public:
701    xsubGenerator() :
702        arb("ARB", "P2A"),
703        bio("BIO", "P2AT")
704    {}
705
706    void mark_handcoded_functions(BufferedFileReader& handcoded) {
707        string line;
708        while (handcoded.getLine(line)) {
709            Package *package = NULp;
710
711            if      (arb.matches_package_prefix(line)) package = &arb;
712            else if (bio.matches_package_prefix(line)) package = &bio;
713
714            if (package) {
715                size_t open_paren = line.find('(');
716                if (open_paren != string::npos) {
717                    package->mark_function_defined(line.substr(0, open_paren));
718                }
719            }
720
721        }
722        handcoded.rewind();
723    }
724
725    void generate_all_xsubs(LineReader& prototype_reader);
726
727    void print_xsubs(FILE *out) {
728        arb.print_xsubs(out);
729        bio.print_xsubs(out);
730    }
731};
732
733inline string prefix_before(const string& name, char separator) {
734    size_t sep_offset = name.find_first_of(separator);
735    if (sep_offset != string::npos) {
736        return name.substr(0, sep_offset);
737    }
738    return "";
739}
740
741inline void GBS_spaces(GBS_strstruct *out, int space_count) {
742    const char *spaces = "          ";
743    arb_assert(space_count <= 10);
744    GBS_strncat(out, spaces+(10-space_count), space_count);
745}
746
747void xsubGenerator::generate_xsub(const Prototype& prototype) {
748    const string&  c_function_name = prototype.get_function_name();
749    string         function_prefix = prefix_before(c_function_name, '_');
750    Package       *package         = NULp;
751
752    // when you change handling of function_prefix, also change ../PERL2ARB/extract_perl_interface.pl@PREFIX_HANDLING
753    if (function_prefix == "GB" || function_prefix == "GBC") {
754        package = &arb;
755    }
756    else if (function_prefix == "GBT" || function_prefix == "GEN") {
757        package = &bio;
758    }
759
760    if (package) {
761        string perl_function_name = package->get_prefix() + c_function_name.substr(function_prefix.length());
762
763        if (package->not_defined(perl_function_name)) {
764            package->mark_function_defined(perl_function_name); // do NOT xsub functions twice
765
766            // generate xsub function header
767            const Type& return_type = prototype.get_return_type();
768            {
769                string argument_names_list = prototype.argument_names_list();
770                string function_header     = return_type.isVoid() ? "void" : return_type.perl_decl();
771
772                function_header += '\n';
773                function_header += perl_function_name+'('+argument_names_list+")\n";
774
775                ArgumentIter arg_end = prototype.args_end();
776                for (ArgumentIter arg = prototype.args_begin(); arg != arg_end; ++arg) {
777                    if (!arg->isVoid()) {
778                        string type_decl = string("    ") + arg->perl_typed_param() + '\n';
779                        function_header += type_decl;
780                    }
781                }
782
783                package->append_code(function_header);
784                package->append_linefeed();
785            }
786
787            // generate xsub function body
788            string call_c_function = c_function_name+'('+prototype.call_arguments()+")";
789            if (return_type.isVoid()) {
790                package->append_code("  PPCODE:\n    ");
791                package->append_code(call_c_function);
792                package->append_code(';');
793            }
794            else {
795                string assign_RETVAL = "    ";
796
797                switch (return_type.get_TypeClass()) {
798                    case CONST_CHAR:
799                    case CONVERSION_FUNCTION:
800                    case SIMPLE_TYPE:
801                    case TYPEMAPPED:
802                        assign_RETVAL = string("    RETVAL = ") + return_type.convert_result_for_PERL(call_c_function);
803                        break;
804
805                    case HEAP_COPY:
806                        // temporarily store heapcopy in static pointer
807                        // defined at ../PERL2ARB/ARB_ext.c@static_pntr
808                        assign_RETVAL =
809                            string("    freeset(static_pntr, ") + call_c_function+");\n"+
810                            "    RETVAL = static_pntr";
811                        break;
812
813                    case VOID:
814                    case INVALID_TYPE:
815                    case CANT_HANDLE:
816                    case FORBIDDEN:
817                        arb_assert(0);
818                }
819
820                string body =
821                    string("  CODE:\n") +
822                    assign_RETVAL + ";\n" +
823                    "\n" +
824                    "  OUTPUT:\n" +
825                    "    RETVAL";
826
827                package->append_code(body);
828            }
829            package->append_linefeed(3);
830        }
831#if defined(TRACE)
832        else {
833            fprintf(stderr, "TRACE: '%s' skipped\n", c_function_name.c_str());
834        }
835#endif // TRACE
836    }
837#if defined(TRACE)
838    else {
839        fprintf(stderr, "TRACE: Skipped function: '%s' (prefix='%s')\n", c_function_name.c_str(), function_prefix.c_str());
840    }
841#endif // TRACE
842}
843
844static void print_prototype_parse_error(LineReader& prototype_reader, const char *err, const char *prototype) {
845    InputFileError(prototype_reader, GBS_global_string("%s (can't xsub '%s')", err, prototype)).print();
846}
847
848void xsubGenerator::generate_all_xsubs(LineReader& prototype_reader) {
849    bool   error_occurred     = false;
850    string line;
851    int    open_brace_counter = 0;
852
853    while (prototype_reader.getLine(line)) {
854        const char *lineStart          = line.c_str();
855        size_t      leading_whitespace = strspn(lineStart, " \t");
856        const char *prototype          = lineStart+leading_whitespace;
857
858        if (!open_brace_counter && is_prototype(prototype)) {
859            try {
860                Prototype proto(prototype);
861                if (proto.possible_as_xsub()) {
862                    generate_xsub(proto);
863                }
864#if defined(TRACE)
865                else {
866                    fprintf(stderr, "TRACE: prototype '%s' not possible as xsub\n", prototype);
867                    proto.dump_types_impossible_in_xsub(stderr);
868                }
869#endif // TRACE
870            }
871            catch(string& err) {
872                print_prototype_parse_error(prototype_reader, err.c_str(), prototype);
873                error_occurred = true;
874            }
875            catch(const char *err) {
876                print_prototype_parse_error(prototype_reader, err, prototype);
877                error_occurred = true;
878            }
879            catch(...) { arb_assert(0); }
880        }
881        else {
882#if defined(TRACE)
883            fprintf(stderr, "TRACE: not a prototype: '%s'\n", prototype);
884#endif // TRACE
885            trace_over_braces(prototype, open_brace_counter);
886        }
887    }
888
889    if (error_occurred) throw ProgramError("could not generate xsubs for all prototypes");
890}
891
892static void print_xs_default(BufferedFileReader& xs_default, const char *proto_filename, FILE *out) {
893    fprintf(out,
894            "/* This file has been generated from\n"
895            " *    %s and\n"
896            " *    %s\n */\n"
897            "\n",
898           xs_default.getFilename().c_str(),
899           proto_filename);
900
901    xs_default.copyTo(out);
902    xs_default.rewind();
903}
904
905static BufferedFileReader *createFileBuffer(const char *filename) {
906    FILE *in = fopen(filename, "rt");
907    if (!in) {
908        GB_export_IO_error("reading", filename);
909        throw ProgramError(GB_await_error());
910    }
911    return new BufferedFileReader(filename, in);
912}
913static BufferedFileReader *createCommentSkippingFileBuffer(const char *filename) {
914    FILE *in = fopen(filename, "rt");
915    if (!in) {
916        GB_export_IO_error("reading", filename);
917        throw ProgramError(GB_await_error());
918    }
919    return new CommentSkippingFileBuffer(filename, in, "/*", "*/", "//");
920}
921
922
923
924static void loadTypemap(const char *typemap_filename) {
925    SmartPtr<BufferedFileReader> typemap = createFileBuffer(typemap_filename);
926    Type::globalTypemap.load(*typemap);
927}
928
929int ARB_main(int argc, char **argv) {
930    bool error_occurred = false;
931    try {
932        if (argc != 4) {
933            fputs("arb_proto_2_xsub converts GB_prototypes for the ARB perl interface\n"
934                  "Usage: arb_proto_2_xsub <prototypes.h> <xs-header> <typemap>\n"
935                  "       <prototypes.h> contains prototypes of ARBDB library\n"
936                  "       <xs-header>    may contain prototypes, which will not be\n"
937                  "                      overwritten by generated default prototypes\n"
938                  "       <typemap>      contains type-conversion-definitions, which can\n"
939                  "                      be handled by xsubpp\n"
940                  , stderr);
941
942            throw ProgramError("Wrong number of command line arguments");
943        }
944        else {
945            const char *proto_filename   = argv[1];
946            const char *xs_default_name  = argv[2];
947            const char *typemap_filename = argv[3];
948
949            loadTypemap(typemap_filename);
950
951            // generate xsubs
952            SmartPtr<BufferedFileReader> xs_default = createFileBuffer(xs_default_name);
953
954            xsubGenerator generator;
955            generator.mark_handcoded_functions(*xs_default);
956            {
957                SmartPtr<BufferedFileReader> prototypes = createCommentSkippingFileBuffer(proto_filename);
958                generator.generate_all_xsubs(*prototypes);
959            }
960
961            // write xsubs
962            FILE *out = stdout;
963            print_xs_default(*xs_default, proto_filename, out);
964            generator.print_xsubs(out);
965        }
966    }
967    catch (Error& err) {
968        err.print();
969        error_occurred = true;
970    }
971    catch (...) {
972        ProgramError("Unexpected exception").print();
973        error_occurred = true;
974    }
975
976    if (error_occurred) {
977        ProgramError("failed").print();
978        return EXIT_FAILURE;
979    }
980    return EXIT_SUCCESS;
981}
982
983
984// --------------------------------------------------------------------------------
985
986#ifdef UNIT_TESTS
987#ifndef TEST_UNIT_H
988#include <test_unit.h>
989#endif
990#include <test_runtool.h>
991
992
993// #define TEST_AUTO_UPDATE // uncomment this to update expected results
994
995void TEST_arb_proto_2_xsub() {
996    TEST_EXPECT_ZERO(chdir("xsub"));
997
998    const char *outname  = "ap2x.out";
999    const char *expected = "ap2x.out.expected";
1000    // input/output data -> ../UNIT_TESTER/run/xsub/
1001
1002    char *cmd = GBS_global_string_copy("arb_proto_2_xsub ptype.header default.xs typemap > %s", outname);
1003    TEST_RUN_TOOL(cmd);
1004
1005#if defined(TEST_AUTO_UPDATE)
1006    TEST_COPY_FILE(outname, expected);
1007#else
1008    TEST_EXPECT_TEXTFILE_DIFFLINES(expected, outname, 0);
1009#endif
1010    TEST_EXPECT_ZERO_OR_SHOW_ERRNO(unlink(outname));
1011
1012    free(cmd);
1013}
1014
1015#endif // UNIT_TESTS
1016
1017// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.