#! /usr/bin/perl # # Copyright (C) 2007 Tomash Brechko. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the same terms as Perl itself, either Perl version 5.8.8 # or, at your option, any later version of Perl 5 you may have # available. # use warnings; use strict; use FindBin; @ARGV == 3 or die "Usage: $FindBin::Script KEYWORD_FILE FILE_C FILE_H\n"; my ($keyword_file, $file_c, $file_h) = @ARGV; my %C; my @keywords; open(my $kw, '<', $keyword_file) or die "open(< $keyword_file): $!"; my $section = 0; while (my $line = <$kw>) { chomp $line; if ($line =~ /^\s*(?:#.*)?$/) { next; } elsif ($line =~ /^\s*%%\s*$/) { ++$section; next; } if ($section == 0 and $line =~ /^\s*(\S+)\s*=\s*(\S+)\s*$/) { $C{$1} = $2; } elsif ($section == 1) { push @keywords, $line; } else { die "Can't parse line: $line"; } } close($kw); sub dispatch_keywords { my ($words) = @_; return $words if @$words <= 1; my $len = 0; my $common = 1; while ($common) { ++$len; my $prefix = substr($$words[0], 0, $len); $common = ! grep(!/^$prefix/, @$words); } --$len; my $prefix = substr($$words[0], 0, $len); my %subtree; foreach my $word (@$words) { my $key = substr($word, $len, 1); my $val = substr($word, $len + 1); push @{$subtree{$key}}, $val; } foreach my $val (values %subtree) { $val = dispatch_keywords($val); } return [$prefix, \%subtree]; } my $tree = dispatch_keywords(\@keywords); my @external_enum = qw(NO_MATCH); sub create_switch { my ($depth, $prefix, $common, $hash) = @_; my $I = ' ' x ($depth * 4); my @keys = sort keys %$hash; (my $common_ident = $common) =~ s/[^A-Z_]//g; my $phase = $prefix . $common_ident; my $res = ''; if ($common) { if ($C{loose_match}) { $res .= <<"EOF"; $I *pos += @{[ length $common ]}; EOF } else { $res .= <<"EOF"; $I match_pos = "$common"; $I do $I { $I if (**pos != *match_pos) $I return NO_MATCH; $I ++*pos; $I ++match_pos; $I } $I while (*match_pos != '\\0'); EOF } } if ($common or $depth) { if (! @keys) { push @external_enum, $phase; $res .= <<"EOF"; $I return $phase; EOF return $res; } } $res .= <<"EOF"; $I switch (*(*pos)++) $I { EOF foreach my $key (@keys) { my $subphase = $phase . $key; $res .= <<"EOF"; $I case '$key': EOF $res .= create_switch($depth + 1, $subphase, @{$$hash{$key}}); } $res .= <<"EOF"; $I default: $I return NO_MATCH; $I } EOF return $res; } my $switch = create_switch(0, 'MATCH_', @$tree); my $gen_comment = <<"EOF"; /* This file was generated with $FindBin::Script from $keyword_file. Instead of editing this file edit the keyword file and regenerate. */ EOF open(my $fc, '>', $file_c) or die "open(> $file_c): $!"; my $i = 0; print $fc <<"EOF"; $gen_comment #include "$file_h" enum $C{parser_func}_e $C{parser_func}(char **pos) { EOF unless ($C{loose_match}) { print $fc <<"EOF"; char *match_pos; EOF } print $fc <<"EOF"; $switch /* Never reach here. */ } EOF close($fc) or die "close($file_c): $!"; my $guard = uc $file_h; $guard =~ s/[^[:alnum:]_]/_/g; open(my $fh, '>', $file_h) or die "open(> $file_h): $!"; print $fh <<"EOF"; $gen_comment #ifndef $guard #define $guard 1 enum $C{parser_func}_e { @{[ join ",\n ", @external_enum ]} }; extern enum $C{parser_func}_e $C{parser_func}(char **pos); #endif /* ! $guard */ EOF close($fh) or die "close($file_h): $!";