utf8: add unit test for g_utf8_make_valid
[glib.git] / gobject / glib-mkenums.in
blobf7da97506e8cae9b9ee1d19cdc90bd6dd621f628
1 #! @PERL_PATH@
3 use warnings;
4 use File::Basename;
5 use File::Copy "move";
6 use File::Temp;
7 use Cwd;
8 use Safe;
10 # glib-mkenums.pl 
11 # Information about the current enumeration
12 my $flags;                      # Is enumeration a bitmask?
13 my $option_underscore_name;     # Overriden underscore variant of the enum name
14                                 # for example to fix the cases we don't get the
15                                 # mixed-case -> underscorized transform right.
16 my $option_lowercase_name;      # DEPRECATED.  A lower case name to use as part
17                                 # of the *_get_type() function, instead of the
18                                 # one that we guess. For instance, when an enum
19                                 # uses abnormal capitalization and we can not
20                                 # guess where to put the underscores.
21 my $seenbitshift;               # Have we seen bitshift operators?
22 my $enum_prefix;                # Prefix for this enumeration
23 my $enumname;                   # Name for this enumeration
24 my $enumshort;                  # $enumname without prefix
25 my $enumname_prefix;            # prefix of $enumname
26 my $enumindex = 0;              # Global enum counter
27 my $firstenum = 1;              # Is this the first enumeration per file?
28 my @entries;                    # [ $name, $val ] for each entry
29 my $sandbox = Safe->new;        # sandbox for safe evaluation of expressions
31 my $output;                     # Filename to write result into
33 sub parse_trigraph {
34     my $opts = shift;
35     my @opts;
37     for $opt (split /\s*,\s*/, $opts) {
38         $opt =~ s/^\s*//;
39         $opt =~ s/\s*$//;
40         my ($key,$val) = $opt =~ /(\w+)(?:=(.+))?/;
41         defined $val or $val = 1;
42         push @opts, $key, $val;
43     }
44     @opts;
46 sub parse_entries {
47     my $file = shift;
48     my $file_name = shift;
49     my $looking_for_name = 0;
50     
51     while (<$file>) {
52         # read lines until we have no open comments
53         while (m@/\*([^*]|\*(?!/))*$@) {
54             my $new;
55             defined ($new = <$file>) || die "Unmatched comment in $ARGV";
56             $_ .= $new;
57         }
58         # strip comments w/o options
59         s@/\*(?!<)
60             ([^*]+|\*(?!/))*
61            \*/@@gx;
62         
63         # strip newlines
64         s@\n@ @;
65         
66         # skip empty lines
67         next if m@^\s*$@;
68         
69         if ($looking_for_name) {
70             if (/^\s*(\w+)/) {
71                 $enumname = $1;
72                 return 1;
73             }
74         }
75         
76         # Handle include files
77         if (/^\#include\s*<([^>]*)>/ ) {
78             my $file= "../$1";
79             open NEWFILE, $file or die "Cannot open include file $file: $!\n";
80             
81             if (parse_entries (\*NEWFILE, $NEWFILE)) {
82                 return 1;
83             } else {
84                 next;
85             }
86         }
87         
88         if (/^\s*\}\s*(\w+)/) {
89             $enumname = $1;
90             $enumindex++;
91             return 1;
92         }
93         
94         if (/^\s*\}/) {
95             $enumindex++;
96             $looking_for_name = 1;
97             next;
98         }
100         if (m@^\s*
101               (\w+)\s*                   # name
102               (?:=(                      # value
103                    \s*\w+\s*\(.*\)\s*       # macro with multiple args
104                    |                        # OR
105                    (?:[^,/]|/(?!\*))*       # anything but a comma or comment
106                   ))?,?\s*
107               (?:/\*<                    # options
108                 (([^*]|\*(?!/))*)
109                >\s*\*/)?,?
110               \s*$
111              @x) {
112             my ($name, $value, $options) = ($1,$2,$3);
114             if (!defined $flags && defined $value && $value =~ /<</) {
115                 $seenbitshift = 1;
116             }
118             if (defined $options) {
119                 my %options = parse_trigraph($options);
120                 if (!defined $options{skip}) {
121                     push @entries, [ $name, $value, $options{nick} ];
122                 }
123             } else {
124                 push @entries, [ $name, $value ];
125             }
126         } elsif (m@^\s*\#@) {
127             # ignore preprocessor directives
128         } else {
129             print STDERR "$0: $file_name:$.: Failed to parse `$_'\n";
130         }
131     }
133     return 0;
136 sub version {
137     print "glib-mkenums version glib-@GLIB_VERSION@\n";
138     print "glib-mkenums comes with ABSOLUTELY NO WARRANTY.\n";
139     print "You may redistribute copies of glib-mkenums under the terms of\n";
140     print "the GNU General Public License which can be found in the\n";
141     print "GLib source package. Sources, examples and contact\n";
142     print "information are available at http://www.gtk.org\n";
143     exit 0;
145 sub usage {
146     print "Usage:\n";
147     print "  glib-mkenums [OPTION...] [FILES...]\n\n";
148     print "Help Options:\n";
149     print "  -h, --help            Show this help message\n\n";
150     print "Utility Options:\n";
151     print "  --identifier-prefix <text>   Identifier prefix\n";
152     print "  --symbol-prefix <text>       Symbol prefix\n";
153     print "  --fhead <text>               Output file header\n";
154     print "  --fprod <text>               Per input file production\n";
155     print "  --ftail <text>               Output file trailer\n";
156     print "  --eprod <text>               Per enum text (produced prior to value iterations)\n";
157     print "  --vhead <text>               Value header, produced before iterating over enum values\n";
158     print "  --vprod <text>               Value text, produced for each enum value\n";
159     print "  --vtail <text>               Value tail, produced after iterating over enum values\n";
160     print "  --comments <text>            Comment structure\n";
161     print "  --template file              Template file\n";
162     print "  --output file                Output file\n";
163     print "  -v, --version                Print version informations\n\n";
164     print "Production text substitutions:\n";
165     print "  \@EnumName\@            PrefixTheXEnum\n";
166     print "  \@enum_name\@           prefix_the_xenum\n";
167     print "  \@ENUMNAME\@            PREFIX_THE_XENUM\n";
168     print "  \@ENUMSHORT\@           THE_XENUM\n";
169     print "  \@ENUMPREFIX\@          PREFIX\n";
170     print "  \@VALUENAME\@           PREFIX_THE_XVALUE\n";
171     print "  \@valuenick\@           the-xvalue\n";
172     print "  \@valuenum\@            the integer value (limited support, Since: 2.26)\n";
173     print "  \@type\@                either enum or flags\n";
174     print "  \@Type\@                either Enum or Flags\n";
175     print "  \@TYPE\@                either ENUM or FLAGS\n";
176     print "  \@filename\@            name of current input file\n";
177     print "  \@basename\@            base name of the current input file (Since: 2.22)\n";
178     exit 0;
181 # production variables:
182 my $idprefix = "";    # "G", "Gtk", etc
183 my $symprefix = "";   # "g", "gtk", etc, if not just lc($idprefix)
184 my $fhead = "";   # output file header
185 my $fprod = "";   # per input file production
186 my $ftail = "";   # output file trailer
187 my $eprod = "";   # per enum text (produced prior to value itarations)
188 my $vhead = "";   # value header, produced before iterating over enum values
189 my $vprod = "";   # value text, produced for each enum value
190 my $vtail = "";   # value tail, produced after iterating over enum values
191 my $comment_tmpl = "";   # comment template
193 sub read_template_file {
194   my ($file) = @_;
195   my %tmpl = ('file-header', $fhead, 
196               'file-production', $fprod, 
197               'file-tail', $ftail, 
198               'enumeration-production', $eprod,
199               'value-header', $vhead,
200               'value-production', $vprod,
201               'value-tail', $vtail,
202               'comment', $comment_tmpl);
203   my $in = 'junk';
204   open (FILE, $file) || die "Can't open $file: $!\n";
205   while (<FILE>) {
206     if (/^\/\*\*\*\s+(BEGIN|END)\s+([\w-]+)\s+\*\*\*\//) {
207       if (($in eq 'junk') && ($1 eq 'BEGIN') && (exists($tmpl{$2}))) {
208         $in = $2;
209         next;
210       }
211       elsif (($in eq $2) && ($1 eq 'END') && (exists($tmpl{$2}))) {
212         $in = 'junk';
213         next;
214       } else {
215           die "Malformed template file $file\n";
216       }
217     }
218     if (!($in eq 'junk')) {
219         $tmpl{$in} .= $_;
220     }
221   }
222   close (FILE);
223   if (!($in eq 'junk')) {
224       die "Malformed template file $file\n";
225   }
226   $fhead = $tmpl{'file-header'};
227   $fprod = $tmpl{'file-production'};
228   $ftail = $tmpl{'file-tail'};
229   $eprod = $tmpl{'enumeration-production'};
230   $vhead = $tmpl{'value-header'};
231   $vprod = $tmpl{'value-production'};
232   $vtail = $tmpl{'value-tail'};
233   $comment_tmpl = $tmpl{'comment'};
235   # default to C-style comments
236   $comment_tmpl = "/* \@comment\@ */" if $comment_tmpl eq "";
239 if (!defined $ARGV[0]) {
240     usage;
242 while ($_=$ARGV[0],/^-/) {
243     shift;
244     last if /^--$/;
245     if (/^--template$/)                      { read_template_file (shift); }
246     elsif (/^--identifier-prefix$/)          { $idprefix = shift }
247     elsif (/^--symbol-prefix$/)              { $symprefix = shift }
248     elsif (/^--fhead$/)                      { $fhead = $fhead . shift }
249     elsif (/^--fprod$/)                      { $fprod = $fprod . shift }
250     elsif (/^--ftail$/)                      { $ftail = $ftail . shift }
251     elsif (/^--eprod$/)                      { $eprod = $eprod . shift }
252     elsif (/^--vhead$/)                      { $vhead = $vhead . shift }
253     elsif (/^--vprod$/)                      { $vprod = $vprod . shift }
254     elsif (/^--vtail$/)                      { $vtail = $vtail . shift }
255     elsif (/^--comments$/)                   { $comment_tmpl = shift }
256     elsif (/^--output$/)                     { $output = shift }
257     elsif (/^--help$/ || /^-h$/ || /^-\?$/)  { usage; }
258     elsif (/^--version$/ || /^-v$/)          { version; }
259     else { usage; }
260     last if not defined($ARGV[0]);
263 if (defined ($output)) {
264     my($out_fn, $out_dir, $out_suffix) = fileparse($output, qr{\.\w+$});
265     if ($out_dir eq '') { $out_dir = cwd(); }
267     $out_suffix =~ s/^\./_/;   # .foo -> _foo
269     $OUTPUT = File::Temp->new("$out_fn$out_suffix\_XXXXXX", DIR => $out_dir, UNLINK => 0);
270     select $OUTPUT;         # Make all print calls from here on go to OUTPUT
273 # put auto-generation comment
275     my $comment = $comment_tmpl;
276     $comment =~ s/\@comment\@/Generated data (by glib-mkenums)/;
277     print "\n" . $comment . "\n\n";
280 if (length($fhead)) {
281     my $prod = $fhead;
282     my $base = basename ($ARGV[0]);
284     $prod =~ s/\@filename\@/$ARGV[0]/g;
285     $prod =~ s/\@basename\@/$base/g;
286     $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
287     $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
288     chomp ($prod);
289                 
290     print "$prod\n";
293 while (<>) {
294     if (eof) {
295         close (ARGV);           # reset line numbering
296         $firstenum = 1;         # Flag to print filename at next enum
297     }
299     # read lines until we have no open comments
300     while (m@/\*([^*]|\*(?!/))*$@) {
301         my $new;
302         defined ($new = <>) || die "Unmatched comment in $ARGV";
303         $_ .= $new;
304     }
305     # strip comments w/o options
306     s@/\*(?!<)
307        ([^*]+|\*(?!/))*
308        \*/@@gx;
309         
310     # ignore forward declarations
311     next if /^\s*typedef\s+enum.*;/;
313     if (m@^\s*typedef\s+enum\s*
314            ({)?\s*
315            (?:/\*<
316              (([^*]|\*(?!/))*)
317             >\s*\*/)?
318            \s*({)?
319          @x) {
320         if (defined $2) {
321             my %options = parse_trigraph ($2);
322             next if defined $options{skip};
323             $enum_prefix = $options{prefix};
324             $flags = $options{flags};
325             $option_lowercase_name = $options{lowercase_name};
326             $option_underscore_name = $options{underscore_name};
327         } else {
328             $enum_prefix = undef;
329             $flags = undef;
330             $option_lowercase_name = undef;
331             $option_underscore_name = undef;
332         }
333         if (defined $option_lowercase_name) {
334             if (defined $option_underscore_name) {
335                 print STDERR "$0: $ARGV:$.: lowercase_name overriden with underscore_name\n";
336                 $option_lowercase_name = undef;
337             } else {
338                 print STDERR "$0: $ARGV:$.: lowercase_name is deprecated, use underscore_name\n";
339             }
340         }
341         # Didn't have trailing '{' look on next lines
342         if (!defined $1 && !defined $4) {
343             while (<>) {
344                 if (eof) {
345                     die "Hit end of file while parsing enum in $ARGV";
346                 }
347                 if (s/^\s*\{//) {
348                     last;
349                 }
350             }
351         }
353         $seenbitshift = 0;
354         @entries = ();
356         # Now parse the entries
357         parse_entries (\*ARGV, $ARGV);
359         # figure out if this was a flags or enums enumeration
360         if (!defined $flags) {
361             $flags = $seenbitshift;
362         }
364         # Autogenerate a prefix
365         if (!defined $enum_prefix) {
366             for (@entries) {
367                 my $nick = $_->[2];
368                 if (!defined $nick) {
369                     my $name = $_->[0];
370                     if (defined $enum_prefix) {
371                         my $tmp = ~ ($name ^ $enum_prefix);
372                         ($tmp) = $tmp =~ /(^\xff*)/;
373                         $enum_prefix = $enum_prefix & $tmp;
374                     } else {
375                         $enum_prefix = $name;
376                     }
377                 }
378             }
379             if (!defined $enum_prefix) {
380                 $enum_prefix = "";
381             } else {
382                 # Trim so that it ends in an underscore
383                 $enum_prefix =~ s/_[^_]*$/_/;
384             }
385         } else {
386             # canonicalize user defined prefixes
387             $enum_prefix = uc($enum_prefix);
388             $enum_prefix =~ s/-/_/g;
389             $enum_prefix =~ s/(.*)([^_])$/$1$2_/;
390         }
391         
392         for $entry (@entries) {
393             my ($name,$num,$nick) = @{$entry};
394             if (!defined $nick) {
395                 ($nick = $name) =~ s/^$enum_prefix//;
396                 $nick =~ tr/_/-/;
397                 $nick = lc($nick);
398                 @{$entry} = ($name, $num, $nick);
399             }
400         }
401         
403         # Spit out the output
404         if (defined $option_underscore_name) {
405             $enumlong = uc $option_underscore_name;
406             $enumsym = lc $option_underscore_name;
407             $enumshort = $enumlong;
408             $enumshort =~ s/^[A-Z][A-Z0-9]*_//;
410             $enumname_prefix = $enumlong;
411             $enumname_prefix =~ s/_$enumshort$//;
412         } elsif (!$symprefix && !$idprefix) {
413             # enumname is e.g. GMatchType
414             $enspace = $enumname;
415             $enspace =~ s/^([A-Z][a-z]*).*$/$1/;
417             $enumshort = $enumname;
418             $enumshort =~ s/^[A-Z][a-z]*//;
419             $enumshort =~ s/([^A-Z])([A-Z])/$1_$2/g;
420             $enumshort =~ s/([A-Z][A-Z])([A-Z][0-9a-z])/$1_$2/g;
421             $enumshort = uc($enumshort);
423             $enumname_prefix = $enumname;
424             $enumname_prefix =~ s/^([A-Z][a-z]*).*$/$1/;
425             $enumname_prefix = uc($enumname_prefix);
427             $enumlong = uc($enspace) . "_" . $enumshort;
428             $enumsym = lc($enspace) . "_" . lc($enumshort);
430             if (defined($option_lowercase_name)) {
431                 $enumsym = $option_lowercase_name;
432             }
433         } else {
434             $enumshort = $enumname;
435             if ($idprefix) {
436                 $enumshort =~ s/^${idprefix}//;
437             } else {
438                 $enumshort =~ s/^[A-Z][a-z]*//;
439             }
440             $enumshort =~ s/([^A-Z])([A-Z])/$1_$2/g;
441             $enumshort =~ s/([A-Z][A-Z])([A-Z][0-9a-z])/$1_$2/g;
442             $enumshort = uc($enumshort);
444             $enumname_prefix = $symprefix && uc($symprefix) || uc($idprefix);
446             $enumlong = $enumname_prefix . "_" . $enumshort;
447             $enumsym = lc($enumlong);
448         }
450         if ($firstenum) {
451             $firstenum = 0;
452             
453             if (length($fprod)) {
454                 my $prod = $fprod;
455                 my $base = basename ($ARGV);
457                 $prod =~ s/\@filename\@/$ARGV/g;
458                 $prod =~ s/\@basename\@/$base/g;
459                 $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
460                 $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
461                 chomp ($prod);
462                 
463                 print "$prod\n";
464             }
465         }
466         
467         if (length($eprod)) {
468             my $prod = $eprod;
470             $prod =~ s/\@enum_name\@/$enumsym/g;
471             $prod =~ s/\@EnumName\@/$enumname/g;
472             $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
473             $prod =~ s/\@ENUMNAME\@/$enumlong/g;
474             $prod =~ s/\@ENUMPREFIX\@/$enumname_prefix/g;
475             if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
476             if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
477             if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
478             $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
479             $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
480             chomp ($prod);
482             print "$prod\n";
483         }
485         if (length($vhead)) {
486             my $prod = $vhead;
488             $prod =~ s/\@enum_name\@/$enumsym/g;
489             $prod =~ s/\@EnumName\@/$enumname/g;
490             $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
491             $prod =~ s/\@ENUMNAME\@/$enumlong/g;
492             $prod =~ s/\@ENUMPREFIX\@/$enumname_prefix/g;
493             if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
494             if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
495             if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
496             $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
497             $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
498             chomp ($prod);
499             
500             print "$prod\n";
501         }
503         if (length($vprod)) {
504             my $prod = $vprod;
505             my $next_num = 0;
506             
507             $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
508             $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
509             for (@entries) {
510                 my ($name,$num,$nick) = @{$_};
511                 my $tmp_prod = $prod;
513                 if ($prod =~ /\@valuenum\@/) {
514                     # only attempt to eval the value if it is requested
515                     # this prevents us from throwing errors otherwise
516                     if (defined $num) {
517                         # use sandboxed perl evaluation as a reasonable
518                         # approximation to C constant folding
519                         $num = $sandbox->reval ($num);
521                         # make sure it parsed to an integer
522                         if (!defined $num or $num !~ /^-?\d+$/) {
523                             die "Unable to parse enum value '$num'";
524                         }
525                     } else {
526                         $num = $next_num;
527                     }
529                     $tmp_prod =~ s/\@valuenum\@/$num/g;
530                     $next_num = $num + 1;
531                 }
533                 $tmp_prod =~ s/\@VALUENAME\@/$name/g;
534                 $tmp_prod =~ s/\@valuenick\@/$nick/g;
535                 if ($flags) { $tmp_prod =~ s/\@type\@/flags/g; } else { $tmp_prod =~ s/\@type\@/enum/g; }
536                 if ($flags) { $tmp_prod =~ s/\@Type\@/Flags/g; } else { $tmp_prod =~ s/\@Type\@/Enum/g; }
537                 if ($flags) { $tmp_prod =~ s/\@TYPE\@/FLAGS/g; } else { $tmp_prod =~ s/\@TYPE\@/ENUM/g; }
538                 chomp ($tmp_prod);
540                 print "$tmp_prod\n";
541             }
542         }
544         if (length($vtail)) {
545             my $prod = $vtail;
547             $prod =~ s/\@enum_name\@/$enumsym/g;
548             $prod =~ s/\@EnumName\@/$enumname/g;
549             $prod =~ s/\@ENUMSHORT\@/$enumshort/g;
550             $prod =~ s/\@ENUMNAME\@/$enumlong/g;
551             $prod =~ s/\@ENUMPREFIX\@/$enumname_prefix/g;
552             if ($flags) { $prod =~ s/\@type\@/flags/g; } else { $prod =~ s/\@type\@/enum/g; }
553             if ($flags) { $prod =~ s/\@Type\@/Flags/g; } else { $prod =~ s/\@Type\@/Enum/g; }
554             if ($flags) { $prod =~ s/\@TYPE\@/FLAGS/g; } else { $prod =~ s/\@TYPE\@/ENUM/g; }
555             $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
556             $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
557             chomp ($prod);
558             
559             print "$prod\n";
560         }
561     }
564 if (length($ftail)) {
565     my $prod = $ftail;
566     my $base = basename ($ARGV);
568     $prod =~ s/\@filename\@/$ARGV/g;
569     $prod =~ s/\@basename\@/$base/g;
570     $prod =~ s/\\a/\a/g; $prod =~ s/\\b/\b/g; $prod =~ s/\\t/\t/g; $prod =~ s/\\n/\n/g;
571     $prod =~ s/\\f/\f/g; $prod =~ s/\\r/\r/g;
572     chomp ($prod);
573                 
574     print "$prod\n";
577 # put auto-generation comment
579     my $comment = $comment_tmpl;
580     $comment =~ s/\@comment\@/Generated data ends here/;
581     print "\n" . $comment . "\n\n";
584 if (defined ($output)) {
585     select STDOUT;
586     my $tmpfilename = $OUTPUT->filename;
587     close ($OUTPUT)
588       || warn "Closing output file $tmpfilename failed: $!";
589     move ($tmpfilename, $output)
590       || die "Could not rename $tmpfilename to $output: $!";