]> git.sur5r.net Git - i3/i3/blobdiff - generate-command-parser.pl
Merge branch 'release-4.16.1'
[i3/i3] / generate-command-parser.pl
index 175d7101592839e379b546845f347cd3df70187d..052e4c66338c72076b6e7121eedeec3172d6b63f 100755 (executable)
@@ -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.
 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 <number>s,
+        # literal numbers or state IDs and %s for NULL, <string>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/\(/(&current_match/;
+            $real_cmd =~ s/\(/(&current_match, result/;
         } else {
-            $real_cmd =~ s/\(/(&current_match, /;
+            $real_cmd =~ s/\(/(&current_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 . ' },';