package Enum; use strict; use warnings; use DocsParser; BEGIN { use Exporter (); our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); # set the version for version checking $VERSION = 1.00; @ISA = qw(Exporter); @EXPORT = ( ); %EXPORT_TAGS = ( ); # your exported package globals go here, # as well as any optionally exported functions @EXPORT_OK = ( ); } our @EXPORT_OK; # class Enum # { # bool flags; # string type; # string module; # string c_type; # # string array elem_short_names; # string array elem_names; # string array elem_values; # string c_prefix; # # bool mark; # } # # private functions: # sub split_enum_tokens($) { my ($token_string) = @_; my @tokens = (); # index of first opening double quotes between parens - beginning of a new # token. my $begin_token = 0; # index of last closing double quotes between parens - end of a token. my $end_token = 0; # whether we are inside double quotes. my $inside_dquotes = 0; # whether we are inside double and then single quotes (for situations like # "'"'"). my $inside_squotes = 0; my $len = length($token_string); # whether we found opening paren and we are expecting an opening double # quotes. my $near_begin = 0; # count of double quotes pairs between parens. my $dq_count = 0; # whether previous char was a backslash - important only when being between # double quotes. my $backslash = 0; for (my $index = 0; $index < $len; $index++) { my $char = substr($token_string, $index, 1); if ($inside_dquotes) { # if prevous char was backslash, then current char is not important - # we are still inside double or double/single quotes anyway. if ($backslash) { $backslash = 0; } # if current char is backslash. elsif ($char eq '\\') { $backslash = 1; } # if current char is unescaped double quotes and we are not inside single # ones - means, we are going outside string. We mark this place as an end # of the token in case we find a closing paren after this. elsif ($char eq '"' and not $inside_squotes) { $inside_dquotes = 0; $end_token = $index; } # if current char is single quote then switch being inside single quotes # state. elsif ($char eq '\'') { $inside_squotes = not $inside_squotes; } } # current char is opening paren - this means we are near the beginning of # a token (first double quotes after this paren). elsif ($char eq '(') { $near_begin = 1; } # current char is closing paren - this means we reached end of a token at # last closing double quotes. elsif ($char eq ')') { my $token_len = $end_token + 1 - $begin_token; my $token = substr($token_string, $begin_token, $token_len); # there should be three pairs of double quotes. if ($dq_count == 3) { push(@tokens, $token); } else { print STDERR "Wrong value statement while parsing ($token)\n"; } $dq_count = 0; } # current char is opening double quotes - this can be a beginning of # a token. elsif ($char eq '"') { if ($near_begin) { $begin_token = $index; $near_begin = 0; } $inside_dquotes = 1; $dq_count++; } } return @tokens; } # # end of private functions. # sub new { my ($def) = @_; my $self = {}; bless $self; $def =~ s/^\(//; $def =~ s/\)$//; $$self{mark} = 0; $$self{flags} = 0; $$self{c_prefix} = ""; $$self{elem_short_names} = []; $$self{elem_names} = []; $$self{elem_values} = []; # snarf down the fields if($def =~ s/^define-(enum|flags)-extended (\S+)//) { $$self{type} = $2; $$self{flags} = 1 if($1 eq "flags"); } $$self{module} = $1 if($def =~ s/\(in-module "(\S+)"\)//); $$self{c_type} = $1 if($def =~ s/\(c-name "(\S+)"\)//); # values are compound lisp statement if($def =~ s/\(values((?: '\("\S+" "\S+" "[^"]+"\))*) \)//) { $self->parse_values($1); } if($def !~ /^\s*$/) { GtkDefs::error("Unhandled enum def ($def) in $$self{module}\::$$self{type}\n") } # this should never happen warn if(scalar(@{$$self{elem_short_names}}) != scalar(@{$$self{elem_values}})); warn if(scalar(@{$$self{elem_names}}) != scalar(@{$$self{elem_values}})); return $self; } sub parse_values($$) { my ($self, $value) = @_; my $elem_short_names = []; my $elem_names = []; my $elem_values = []; my $common_prefix = undef; # break up the value statements - it works with parens inside double quotes # and handles triples like '("dq-token", "MY_SCANNER_DQ_TOKEN", "'"'"). foreach (split_enum_tokens($value)) { if (/^"(\S+)" "(\S+)" "(.+)"$/) { my ($nick_name, $name, $value) = ($1, $2, $3); # detect whether there is module prefix common to all names, e.g. GTK_ my $prefix = $1 if ($name =~ /^([^_]+_)/); if (not defined($common_prefix)) { $common_prefix = $prefix; } elsif ($prefix ne $common_prefix) { $common_prefix = ""; } # enum.pl generates nick names from the C names of the enum constants. # A nick name consists of the trailing part of the enum name. # The leading part which is common for each constant of an enum type # has been removed. The remaining part is then transformed to lowercase # and hyphens. # The nick names would have been suitable for 'enum class' definitions # if they had not been transformed to lowercase and hyphens. # But the length of the nick names can be used. Create short names # consisting of the trailing part of the names with the same length # as the nick names. my $short_name = substr($name, length($name) - length($nick_name)); push(@$elem_short_names, $short_name); push(@$elem_names, $name); push(@$elem_values, $value); } else { GtkDefs::error("Unknown value statement ($_) in $$self{c_type}\n"); } } if ($common_prefix) { # cut off the module prefix, e.g. GTK_ s/^$common_prefix// foreach (@$elem_names); # Save the common prefix. $$self{c_prefix} = $common_prefix; } $$self{elem_short_names} = $elem_short_names; $$self{elem_names} = $elem_names; $$self{elem_values} = $elem_values; } sub beautify_values($$) { my ($self, $use_short_names) = @_; return if($$self{flags}); my $elem_names = $use_short_names ? $$self{elem_short_names} : $$self{elem_names}; my $elem_values = $$self{elem_values}; my $num_elements = scalar(@$elem_values); return if($num_elements == 0); my $first = $$elem_values[0]; return if($first !~ /^-?[0-9]+$/); my $prev = $first; # Continuous? (Aliases to prior enum values are allowed.) foreach my $value (@$elem_values) { return if ($value =~ /[G-WY-Zg-wy-z_]/); return if(($value < $first) || ($value > $prev + 1)); $prev = $value; } # This point is reached only if the values are a continuous range. # 1) Let's kill all the superfluous values, for better readability. # 2) Substitute aliases to prior enum values. my %aliases = (); for(my $i = 0; $i < $num_elements; ++$i) { my $value = \$$elem_values[$i]; my $alias = \$aliases{$$value}; if(defined($$alias)) { $$value = $$alias; } else { $$alias = $$elem_names[$i]; $$value = "" unless($first != 0 && $$value == $first); } } } sub build_element_list($$$$) { my ($self, $use_short_names, $ref_subst_in, $ref_subst_out, $indent) = @_; my $elem_names = $use_short_names ? $$self{elem_short_names} : $$self{elem_names}; my $elem_values = $$self{elem_values}; my $num_elements = scalar(@$elem_names); my $elements = ""; for(my $i = 0; $i < $num_elements; ++$i) { my $name = $$elem_names[$i]; my $value = $$elem_values[$i]; for(my $ii = 0; $ii < scalar(@$ref_subst_in); ++$ii) { $name =~ s/$$ref_subst_in[$ii]/$$ref_subst_out[$ii]/; $value =~ s/$$ref_subst_in[$ii]/$$ref_subst_out[$ii]/; } # Skip this element, if its name has been deleted. next if($name eq ""); $elements .= ",\n" if($elements ne ""); $elements .= "${indent}${name}"; $elements .= " = ${value}" if($value ne ""); } return $elements; } # The name prefix is defined by: $name_prefix . $short_name eq $name # I.e. what shall be chopped off the name to get the short name. sub get_name_prefix($) { my ($self) = @_; my $name = ${$$self{elem_names}}[0]; my $short_name = ${$$self{elem_short_names}}[0]; my $prefix_length = length($name) - length($short_name); return substr($name, 0, $prefix_length); } sub dump($) { my ($self) = @_; print "\n"; my $elem_names = $$self{elem_names}; my $elem_values = $$self{elem_values}; for(my $i = 0; $i < scalar(@$elem_names); ++$i) { print " \n"; } print "\n\n"; } 1; # indicate proper module load.