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;
# 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;
# 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,
next_state => $action,
};
if (exists $states{$current_state}) {
- push $states{$current_state}, $store_token;
+ push @{$states{$current_state}}, $store_token;
} else {
$states{$current_state} = [ $store_token ];
}
# 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 occurences 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) } 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,";
+ $statenum{$state} = $cnt;
$cnt++;
}
say $enumfh '} 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) {
$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.
+ $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;
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++;
}
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};
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 . ' },';