chiark / gitweb /
compute-scottish-stv: --sort=alpha
[appendix-a6.git] / normalise
index 6bf8ea664d7b54fce5b65f3c2faea20fe90a51d7..0af8f19623d021aa3d9c34f912fc3be0584fc908 100755 (executable)
--- a/normalise
+++ b/normalise
@@ -1,19 +1,52 @@
 #!/usr/bin/perl -w
+#
+# usage:
+#  normalise [NORM-OPTIONS...] [--] INPUT-FILES...
+#
+# NORM-OPTIONS are
+#   +OPTNAME[=OPTVAL]           Election option
+#   CAND=[DESCRIPTION]
+#   +CAND+OPTNAME[=OPTVAL]      Candidate option
+#   --                          End of options to normalise
+#   -...                        Reserved for future options to normalise
 
 use strict;
 
-our @options, %candiates, @ballots;
+our @options;
+our %candidates; # $candidates{CAND}{Desc}, {Opts}[]
+our %candref; # $candref{CAND} => msg of why needed
+our @ballots;
 
 my $candvoter_re = '\w+';
+my $cands_re = '\w+(?,\w+)*';
+my $opt_re = '\w+(?:=\S*)?';
 
-sub normalise_opts_list ($) {
+sub badinput ($) {
+    die "bad input: $_[0]";
+}
+
+sub normalise_opts_list ($$) {
+    # $ctx is one of Election Candidate Ballot
+    my ($os,$ctx) = @_;
+    $os //= '';
     my @o;
     foreach my $o (split /\s+/, $os) {
        if ($o =~ m/^\w+$/) {
            push @o, $&;
+       } elsif ($o =~ s/^_[Tt]ie\=//) {
+           $o =~ m/^($cands_re)([<>])($cands_re)$/
+               or badinput "bad value \`$_' for tie option";
+           my ($l,$op,$r) = ($1,$2,$3);
+           ($l,$op,$r) = ($r,'>',$l) if $op eq '<';
+           $candref{$_} = "tie break spec" foreach $o =~ m/\w+/g;
+           $l = join ',', sort split /\,/, $l;
+           $r = join ',', sort split /\,/, $r;
+           $l =~ m/\b$_\b/ and badinput "reflexive tie"
+               foreach split /\,/, $r;
+           push @o, "$l$op$r";
        } elsif ($o =~ m/^\w+\=\S+$/) {
-           push @o, $&;S
-       } elseif ($o !~ m/\S/) {
+           push @o, $&;
+       } elsif ($o !~ m/\S/) {
        } else {
            badinput "bad option \`$o'";
        }
@@ -21,41 +54,68 @@ sub normalise_opts_list ($) {
     return @o;
 }
 
-sub normalise_opts ($) {
-    my ($os) = @_;
-    my @o = normalise_opts_list $os;
+sub normalise_opts ($$) {
+    my ($os,$ctx) = @_;
+    my @o = normalise_opts_list $os, $ctx;
     return " | @o";
 }
 
+sub setcanddesc ($$) {
+    my ($cand,$desc) = @_;
+
+    if (length $desc) {
+       badinput "multiple descriptions for $cand" if
+           defined $candidates{$cand}{Desc};
+       $candidates{$cand}{Desc} = $desc;
+    }
+}
+
+while (@ARGV) {
+    $_ = shift @ARGV;
+    if (m/^--$/) {
+       last;
+    } elsif (m/^(\w+)=([^|]+)$/) {
+       setcanddesc $1, $2;
+    } elsif (m/^\+($opt_re)$/) {
+       push @options, $1;
+    } elsif (m/^\+(\w+)\+($opt_re)$/) {
+       push @{ $candidates{$1}{Opts} }, $2;
+    } elsif (m/^-/) {
+       die "unknown normalise option \`$_'\n";
+    } else {
+       # oh!
+       unshift @ARGV, $_;
+       last;
+    }
+}
+
 while (<>) {
     next unless m/\S/;
     next if m/^\#/;
     s/^\s+//;
     s/\s+$//;
     if (m/^\|/) {
-       push @options, normalise_opts_list $';
-    } elsif (m/^($candvoter_re?)\s*=\s*([^|]+?)\s*|(.*)?$/) {
+       push @options, normalise_opts_list $', 'Election';
+    } elsif (m/^($candvoter_re?)\s*=\s*([^|]+?)\s*\|(.*)?$/o) {
+       use Data::Dumper;
+print STDERR Dumper($1,$2,$3);
        my ($cand,$desc,$opts) = ($1,$2,$3);
-       push @{ $candidates{$cand}{Opts} }, normalise_opts $opts;
-       if (length $desc) {
-           badinput "multiple descriptions for $cand" if
-               defined $candidates{$cand}{Desc};
-           $candidates{$cand}{Desc} = $desc;
-       }
-       $desc=$cand unless length $desc;
-       push @candidates, "$cand = $desc".
-    } elsif (m/^($candvoter_re?)?\s*\:([^|]+)(|(.*)?$/) {
+       push @{ $candidates{$cand}{Opts} }, normalise_opts $opts, 'Candidate';
+       setcanddesc $cand, $desc;
+    } elsif (m/^($candvoter_re?)?\s*\:([^|]*)(?:\|(.*))?$/) {
        my ($voter,$opts) = ($1,$3);
        my @p;
        foreach my $p (split /\s+/, $2) {
            if ($p =~ m/^\w+(?:\=\w+)*$/) {
                push @p, $&;
-               $candidates{$_} //= { } foreach my $p =~ m/\w+/g;
+               $candidates{$_} //= { } foreach $p =~ m/\w+/g;
+           } elsif ($p eq '') {
+               # empty entry can only happen if voter casts no prefs at all
            } else {
                badinput "bad vote preference \`$p'";
            }
        }
-       push @ballots, "$voter : @p".normalise_opts $opts;
+       push @ballots, "$voter : @p".normalise_opts $opts, 'Ballot';
     } elsif (m/^\.$/) {
     } else {
        badinput "unknown line format \`$_'";
@@ -64,6 +124,11 @@ while (<>) {
 
 print "| @options\n" or die $!;
 
+foreach my $cand (sort keys %candref) {
+    badinput "missing candidate $cand (ref. by $candref{$cand}"
+       unless defined $candidates{$cand};
+}
+
 foreach my $cand (sort keys %candidates) {
     my $c = $candidates{$cand};
     $c->{Desc} //= $cand;
@@ -75,6 +140,6 @@ foreach my $cand (sort keys %candidates) {
 sub vsortkey { $_[0] =~ m/:/; return "$' : $`"; }
 
 print $_,"\n" or die $! foreach
-    (sort { vsortkey($a) cmp vsortkey($b) } @ballots;
+    sort { vsortkey($a) cmp vsortkey($b) } @ballots;
 
 print ".\n" or die $!;