X-Git-Url: https://git.sur5r.net/?p=i3%2Fi3;a=blobdiff_plain;f=generate-command-parser.pl;h=052e4c66338c72076b6e7121eedeec3172d6b63f;hp=175d7101592839e379b546845f347cd3df70187d;hb=HEAD;hpb=cc2fda92422e29a61c929ffde8d3199305b89946 diff --git a/generate-command-parser.pl b/generate-command-parser.pl index 175d7101..052e4c66 100755 --- a/generate-command-parser.pl +++ b/generate-command-parser.pl @@ -2,7 +2,7 @@ # vim:ts=4:sw=4:expandtab # # i3 - an improved dynamic tiling window manager -# © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) +# © 2009 Michael Stapelberg and contributors (see also: LICENSE) # # generate-command-parser.pl: script to generate parts of the command parser # from its specification file parser-specs/commands.spec. @@ -12,8 +12,18 @@ use strict; use warnings; use Data::Dumper; +use Getopt::Long; use v5.10; +my $input = ''; +my $prefix = ''; +my $result = GetOptions( + 'input=s' => \$input, + 'prefix=s' => \$prefix +); + +die qq|Input file "$input" does not exist!| unless -e $input; + # reads in a whole file sub slurp { open my $fh, '<', shift; @@ -24,8 +34,6 @@ sub slurp { # Stores the different states. my %states; -# XXX: don’t hardcode input and output -my $input = '../parser-specs/commands.spec'; my @raw_lines = split("\n", slurp($input)); my @lines; @@ -57,7 +65,7 @@ for my $line (@raw_lines) { my $current_state; for my $line (@lines) { - if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) { + if (my ($state) = ($line =~ /^state ([A-Z0-9_]+):$/)) { #say "got a new state: $state"; $current_state = $state; } else { @@ -69,21 +77,21 @@ for my $line (@lines) { ($line =~ / ^\s* # skip leading whitespace ([a-z_]+ \s* = \s*|) # optional identifier - (.*?) -> \s* # token + (.*?) -> \s* # token (.*) # optional action /x); # Cleanup the identifier (if any). $identifier =~ s/^\s*(\S+)\s*=\s*$/$1/g; - # Cleanup the tokens (remove whitespace). - $tokens =~ s/\s*//g; - # The default action is to stay in the current state. $action = $current_state if length($action) == 0; #say "identifier = *$identifier*, token = *$tokens*, action = *$action*"; for my $token (split(',', $tokens)) { + # Cleanup trailing/leading whitespace. + $token =~ s/^\s*//g; + $token =~ s/\s*$//g; my $store_token = { token => $token, identifier => $identifier, @@ -101,25 +109,29 @@ for my $line (@lines) { # Second step: Generate the enum values for all states. # It is important to keep the order the same, so we store the keys once. -my @keys = keys %states; +# We sort descendingly by length to be able to replace occurrences of the state +# name even when one state’s name is included in another one’s (like FOR_WINDOW +# is in FOR_WINDOW_COMMAND). +my @keys = sort { (length($b) <=> length($a)) or ($a cmp $b) } keys %states; -open(my $enumfh, '>', 'GENERATED_enums.h'); +open(my $enumfh, '>', "GENERATED_${prefix}_enums.h"); -# XXX: we might want to have a way to do this without a trailing comma, but gcc -# seems to eat it. +my %statenum; say $enumfh 'typedef enum {'; my $cnt = 0; for my $state (@keys, '__CALL') { - say $enumfh " $state = $cnt,"; + say $enumfh ',' if $cnt > 0; + print $enumfh " $state = $cnt"; + $statenum{$state} = $cnt; $cnt++; } -say $enumfh '} cmdp_state;'; +say $enumfh "\n} cmdp_state;"; close($enumfh); # Third step: Generate the call function. -open(my $callfh, '>', 'GENERATED_call.h'); -say $callfh 'static char *GENERATED_call(const int call_identifier) {'; -say $callfh ' char *output = NULL;'; +open(my $callfh, '>', "GENERATED_${prefix}_call.h"); +my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR'; +say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {"; say $callfh ' switch (call_identifier) {'; my $call_id = 0; for my $state (@keys) { @@ -133,29 +145,52 @@ for my $state (@keys) { $next_state ||= 'INITIAL'; my $fmt = $cmd; # Replace the references to identified literals (like $workspace) with - # calls to get_string(). + # calls to get_string(). Also replaces state names (like FOR_WINDOW) + # with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.). + $cmd =~ s/$_/$statenum{$_}/g for @keys; $cmd =~ s/\$([a-z_]+)/get_string("$1")/g; - # Used only for debugging/testing. + $cmd =~ s/\&([a-z_]+)/get_long("$1")/g; + # For debugging/testing, we print the call using printf() and thus need + # to generate a format string. The format uses %d for s, + # literal numbers or state IDs and %s for NULL, s and literal + # strings. + + # remove the function name temporarily, so that the following + # replacements only apply to the arguments. + my ($funcname) = ($fmt =~ /^(.+)\(/); + $fmt =~ s/^$funcname//; + + $fmt =~ s/$_/%d/g for @keys; $fmt =~ s/\$([a-z_]+)/%s/g; + $fmt =~ s/\&([a-z_]+)/%ld/g; $fmt =~ s/"([a-z0-9_]+)"/%s/g; + $fmt =~ s/(?:-?|\b)[0-9]+\b/%d/g; + + $fmt = $funcname . $fmt; say $callfh " case $call_id:"; + say $callfh " result->next_state = $next_state;"; say $callfh '#ifndef TEST_PARSER'; my $real_cmd = $cmd; if ($real_cmd =~ /\(\)/) { - $real_cmd =~ s/\(/(¤t_match/; + $real_cmd =~ s/\(/(¤t_match, result/; } else { - $real_cmd =~ s/\(/(¤t_match, /; + $real_cmd =~ s/\(/(¤t_match, result, /; } - say $callfh " output = $real_cmd;"; + say $callfh " $real_cmd;"; say $callfh '#else'; # debug $cmd =~ s/[^(]+\(//; $cmd =~ s/\)$//; $cmd = ", $cmd" if length($cmd) > 0; - say $callfh qq| printf("$fmt\\n"$cmd);|; + $cmd =~ s/, NULL//g; + say $callfh qq| fprintf(stderr, "$fmt\\n"$cmd);|; + # The cfg_criteria functions have side-effects which are important for + # testing. They are implemented as stubs in the test parser code. + if ($real_cmd =~ /^cfg_criteria/) { + say $callfh qq| $real_cmd;|; + } say $callfh '#endif'; - say $callfh " state = $next_state;"; say $callfh " break;"; $token->{next_state} = "call $call_id"; $call_id++; @@ -163,18 +198,18 @@ for my $state (@keys) { } say $callfh ' default:'; say $callfh ' printf("BUG in the parser. state = %d\n", call_identifier);'; +say $callfh ' assert(false);'; say $callfh ' }'; -say $callfh ' return output;'; say $callfh '}'; close($callfh); # Fourth step: Generate the token datastructures. -open(my $tokfh, '>', 'GENERATED_tokens.h'); +open(my $tokfh, '>', "GENERATED_${prefix}_tokens.h"); for my $state (@keys) { my $tokens = $states{$state}; - say $tokfh 'cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {'; + say $tokfh 'static cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {'; for my $token (@$tokens) { my $call_identifier = 0; my $token_name = $token->{token}; @@ -189,12 +224,12 @@ for my $state (@keys) { $next_state = '__CALL'; } my $identifier = $token->{identifier}; - say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } }, |; + say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|; } say $tokfh '};'; } -say $tokfh 'cmdp_token_ptr tokens[' . scalar @keys . '] = {'; +say $tokfh 'static cmdp_token_ptr tokens[' . scalar @keys . '] = {'; for my $state (@keys) { my $tokens = $states{$state}; say $tokfh ' { tokens_' . $state . ', ' . scalar @$tokens . ' },';