use warnings; use strict; use feature 'unicode_strings'; use open IO => ':encoding(UTF-8)', ':std'; use Getopt::Long qw/:config bundling auto_version auto_help/; our $VERSION = 0.1; my $java = undef; my $debug = undef; my $lax_parity = undef; GetOptions("java|j+" => \$java, "lax_parity|p" => \$lax_parity, "debug|g" => \$debug) or die "Cannot parse options"; # multidimensional hash; first key is growing side identifier; # second key is shrinking side identifier my %program = (); my $id = qr/(?!x)\pL\w*/o; my $string = qr/$id(?:\s+$id)*|/o; my $dirstring = qr/<\s*$string|$string\s*>/o; sub parse_dirstring { my $ds = shift; if ($ds =~ s/^$//) { return [reverse(split(" ", $ds))]; } else { die "unreachable"; } } my $input_lhs = undef; my $input_rhs = undef; my $growside = undef; sub input_growside { $growside eq '<' ? $input_lhs : $input_rhs; } sub input_shrinkside { $growside eq '>' ? $input_lhs : $input_rhs; } for my $line (<>) { chomp $line; $line =~ s/#.*$//; $line =~ s/^\s*//; $line =~ s/\s*$//; next if $line eq ''; if ($line =~ /=/) { if ($line =~ /^($id)\s*([<>])\s*($id)\s*=\s*($dirstring)$/) { if ($2 eq '<') { $program{$1}{$3} = parse_dirstring($4); $lax_parity or scalar @{$program{$1}{$3}} % 2 and die "replacement string $4 must have even length"; } else { $program{$3}{$1} = parse_dirstring($4); $lax_parity or scalar @{$program{$3}{$1}} % 2 and die "replacement string $4 must have even length"; } } else { die "Malformed line '$line'"; } } elsif ($line =~ /^($string)\s*([<>])\s*($string)$/) { defined $growside and die "two initial inputs given"; $growside = $2; $input_lhs = $1; $input_rhs = $3; $input_lhs = parse_dirstring "$input_lhs>"; $input_rhs = parse_dirstring "<$input_rhs"; } else { die "Malformed line '$line'"; } } unless ($lax_parity) { scalar @{input_shrinkside()} % 2 and die "the side that cannot grow on the first step must have even length"; scalar @{input_growside()} % 2 or die "the side that can grow on the first step must have odd length"; } my %all_shrinkside_keys; %all_shrinkside_keys = (%all_shrinkside_keys, %$_) for values %program; for my $growside_key (keys %program) { exists $all_shrinkside_keys{$growside_key} and die "symbol '$growside_key' has rules on both sides of an arrow"; } if ($java) { # compile to Java, rather than running the program $lax_parity and die "the Java construction does not work with lax parity"; # when -j is given twice, mangle into a 15-character character set sub jjescape { my $s = shift; $java == 1 and return $s; my @s = split '', $s; @s = map { sprintf "%o", ord $_ } @s; @s = map y/01234567/abcdeitu/r, @s; scalar join "0", @s; } sub mprint { my $s = join "", @_; if ($java == 1) { print $s; } else { $s =~ s/x/a7/g; $s =~ s/([^02367?\\abcdeitu])/sprintf "\\u%04x", ord $1/eg; $s =~ /([^02367?\\abcdeitu])/ and die "Bad character '$1' in escaped string '$s'"; print $s; } } my %seen; mprint "interface xx {}\n"; mprint "interface ", jjescape($_), " {}\n" for sort keys %all_shrinkside_keys; for my $gk (keys %program) { $seen{$gk} = 1; my $gkj = jjescape $gk; mprint "class $gkj implements"; for my $sk (keys %{$program{$gk}}) { my $skj = jjescape $sk; $seen{$sk} = 1; $seen{$_} ||= 0 for @{$program{$gk}{$sk}}; my $len = scalar @{$program{$gk}{$sk}}; my $sk2 = $len ? "$skj<" : $skj; mprint "\n $sk2", join("", ">" x $len, ","; } mprint " xx {}\n"; } for my $k (keys %seen) { unless ($seen{$k}) { # We saw a value but it doesn't have any rules. # Create a dummy class for it. my $kj = jjescape $k; mprint "class $kj implements xx {}\n"; } } mprint "class x {\n"; mprint " ", join "" x scalar @{input_growside()}, " xc;\n"; mprint " ", join "" x scalar @{input_shrinkside()}, " xd = xc;\n"; mprint "}\n"; exit 0; } for (;;) { my @reversed_lhs = reverse @$input_lhs; $debug and print "@reversed_lhs$growside@$input_rhs\n"; # An empty growside can only happen when using lax parity. This # terminates in failure unless the shrinkside is empty too. scalar @{input_growside()} or exit !!scalar @{input_shrinkside()}; # When the shrinkside empties, we terminate successfully # unless we're looking at a value on the growside which has a # shrinkside rule. scalar @{input_shrinkside()} or exit !!exists $all_shrinkside_keys{${input_growside()}[0]}; # A missing rule is another form of unsuccessful termination. my $shrinktoken = shift @{input_shrinkside()}; my $growtoken = shift @{input_growside()}; my $rule = $program{$growtoken}{$shrinktoken}; $growtoken eq $shrinktoken and $rule = []; defined $rule or exit 1; unshift @{input_growside()}, @$rule; $growside ^= "<" ^ ">"; }