2 # vim:ts=4:sw=4:expandtab
4 # i3 - an improved dynamic tiling window manager
5 # © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
7 # generate-command-parser.pl: script to generate parts of the command parser
8 # from its specification file parser-specs/commands.spec.
10 # Requires only perl >= 5.10, no modules.
17 # reads in a whole file
19 open my $fh, '<', shift;
24 # Stores the different states.
27 # XXX: don’t hardcode input and output
28 my $input = '../parser-specs/commands.spec';
29 my @raw_lines = split("\n", slurp($input));
32 # XXX: In the future, we might switch to a different way of parsing this. The
33 # parser is in many ways not good — one obvious one is that it is hand-crafted
34 # without a good reason, also it preprocesses lines and forgets about line
35 # numbers. Luckily, this is just an implementation detail and the specification
36 # for the i3 command parser is in-tree (not user input).
37 # -- michael, 2012-01-12
39 # First step of preprocessing:
40 # Join token definitions which are spread over multiple lines.
41 for my $line (@raw_lines) {
42 next if $line =~ /^\s*#/ || $line =~ /^\s*$/;
44 if ($line =~ /^\s+->/) {
45 # This is a continued token definition, append this line to the
47 $lines[$#lines] = $lines[$#lines] . $line;
54 # First step: We build up the data structure containing all states and their
59 for my $line (@lines) {
60 if (my ($state) = ($line =~ /^state ([A-Z_]+):$/)) {
61 #say "got a new state: $state";
62 $current_state = $state;
64 # Must be a token definition:
65 # [identifier = ] <tokens> -> <action>
66 #say "token definition: $line";
68 my ($identifier, $tokens, $action) =
70 ^\s* # skip leading whitespace
71 ([a-z_]+ \s* = \s*|) # optional identifier
73 (.*) # optional action
76 # Cleanup the identifier (if any).
77 $identifier =~ s/^\s*(\S+)\s*=\s*$/$1/g;
79 # Cleanup the tokens (remove whitespace).
82 # The default action is to stay in the current state.
83 $action = $current_state if length($action) == 0;
85 #say "identifier = *$identifier*, token = *$tokens*, action = *$action*";
86 for my $token (split(',', $tokens)) {
89 identifier => $identifier,
90 next_state => $action,
92 if (exists $states{$current_state}) {
93 push @{$states{$current_state}}, $store_token;
95 $states{$current_state} = [ $store_token ];
101 # Second step: Generate the enum values for all states.
103 # It is important to keep the order the same, so we store the keys once.
104 my @keys = keys %states;
106 open(my $enumfh, '>', 'GENERATED_enums.h');
108 # XXX: we might want to have a way to do this without a trailing comma, but gcc
110 say $enumfh 'typedef enum {';
112 for my $state (@keys, '__CALL') {
113 say $enumfh " $state = $cnt,";
116 say $enumfh '} cmdp_state;';
119 # Third step: Generate the call function.
120 open(my $callfh, '>', 'GENERATED_call.h');
121 say $callfh 'static void GENERATED_call(const int call_identifier, struct CommandResult *result) {';
122 say $callfh ' switch (call_identifier) {';
124 for my $state (@keys) {
125 my $tokens = $states{$state};
126 for my $token (@$tokens) {
127 next unless $token->{next_state} =~ /^call /;
128 my ($cmd) = ($token->{next_state} =~ /^call (.*)/);
129 my ($next_state) = ($cmd =~ /; ([A-Z_]+)$/);
130 $cmd =~ s/; ([A-Z_]+)$//;
131 # Go back to the INITIAL state unless told otherwise.
132 $next_state ||= 'INITIAL';
134 # Replace the references to identified literals (like $workspace) with
135 # calls to get_string().
136 $cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
137 # Used only for debugging/testing.
138 $fmt =~ s/\$([a-z_]+)/%s/g;
139 $fmt =~ s/"([a-z0-9_]+)"/%s/g;
141 say $callfh " case $call_id:";
142 say $callfh '#ifndef TEST_PARSER';
144 if ($real_cmd =~ /\(\)/) {
145 $real_cmd =~ s/\(/(¤t_match, result/;
147 $real_cmd =~ s/\(/(¤t_match, result, /;
149 say $callfh " $real_cmd;";
154 $cmd = ", $cmd" if length($cmd) > 0;
155 say $callfh qq| printf("$fmt\\n"$cmd);|;
156 say $callfh '#endif';
157 say $callfh " state = $next_state;";
158 say $callfh " break;";
159 $token->{next_state} = "call $call_id";
163 say $callfh ' default:';
164 say $callfh ' printf("BUG in the parser. state = %d\n", call_identifier);';
169 # Fourth step: Generate the token datastructures.
171 open(my $tokfh, '>', 'GENERATED_tokens.h');
173 for my $state (@keys) {
174 my $tokens = $states{$state};
175 say $tokfh 'cmdp_token tokens_' . $state . '[' . scalar @$tokens . '] = {';
176 for my $token (@$tokens) {
177 my $call_identifier = 0;
178 my $token_name = $token->{token};
179 if ($token_name =~ /^'/) {
180 # To make the C code simpler, we leave out the trailing single
181 # quote of the literal. We can do strdup(literal + 1); then :).
182 $token_name =~ s/'$//;
184 my $next_state = $token->{next_state};
185 if ($next_state =~ /^call /) {
186 ($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
187 $next_state = '__CALL';
189 my $identifier = $token->{identifier};
190 say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } }, |;
195 say $tokfh 'cmdp_token_ptr tokens[' . scalar @keys . '] = {';
196 for my $state (@keys) {
197 my $tokens = $states{$state};
198 say $tokfh ' { tokens_' . $state . ', ' . scalar @$tokens . ' },';