The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#! /usr/local/bin/perl -ws
$|++;

use Parse::RecDescent;
# $::RD_TRACE = 1;

my $start = "START";		# start symbol

(my $parser = Parse::RecDescent->new(<<'END_OF_GRAMMAR')) or die "bad!";

## return hashref
## { ident => {
##     is => [
##       [weight => item, item, item, ...],
##       [weight => item, item, item, ...], ...
##     ],
##     defined => { line-number => times }
##     used => { line-number => times }
##   }, ...
## }
## item is " literal" or ident
## ident is C-symbol or number (internal for nested rules)

{ my %grammar; my $internal = 0; }

grammar: rule(s) /\Z/ { \%grammar; }

## rule returns identifier (not used)
rule: identifier ":" defn ';' {
           push @{$grammar{$item[1]}{is}}, @{$item[3]};
           $grammar{$item[1]}{defined}{$itempos[1]{line}{to}}++;
           $item[1];
           }
    | <error>

## defn returns listref of choices
defn: <leftop: choice "|" choice>

## choice returns a listref of [weight => @items]
choice: unweightedchoice { [ 1 => @{$item[1]} ] }
    | /\d+(\.\d+)?/ /\@/ unweightedchoice { [ $item[1] => @{$item[3]} ] }

## unweightedchoice returns a listref of @items
unweightedchoice: item(s)

item: quoted_string
    | identifier ...!/:/ {
        $grammar{$item[1]}{used}{$itempos[1]{line}{to}}++;
        $item[1]; # non-leading space flags an identifier
    }
    | "(" defn ")" { # parens for recursion, gensym an internal
        ++$internal;
        push @{$grammar{$internal}{is}}, @{$item[2]};
        $internal;
    }
    | <error>

quoted_string: /"/ <skip:""> quoted_char(s?) /"/ {
        " " . join "", @{$item[3]} # leading space flags a string
    }

## this should be expanded, but it works for this grammar :)
quoted_char:
      /[^\\"]+/
    | /\\n/ { "\n" }
    | /\\"/ { "\"" }

identifier: /[A-Za-z_]\w*/

END_OF_GRAMMAR

my @data = <DATA>;
for (@data) {
  s/^\s*#.*//;
}

(my $parsed = $parser->grammar(join '', @data)) or die "bad parse";

for my $id (sort keys %$parsed) {
  next if $id =~ /^\d+$/;	# skip internals
  my $id_ref = $parsed->{$id};
  unless (exists $id_ref->{defined}) {
    print "$id used in @{[sort keys %{$id_ref->{used}}]} but not defined - FATAL\n";
  }
  unless (exists $id_ref->{used} or $id eq $start) {
    print "$id defined in @{[sort keys %{$id_ref->{defined}}]} but not used - WARNING\n";
  }
}

use Data::Dumper; print Dumper($parsed);
show($start);

sub show {
  my $defn = shift;
  die "missing defn for $defn" unless exists $parsed->{$defn};

  my @choices = @{$parsed->{$defn}{is}};
  my $weight = 0;
  my @keeper = ();
  while (@choices) {
    my ($thisweight, @thisitem) = @{pop @choices};
    $thisweight = 0 if $thisweight < 0; # no funny stuff
    $weight += $thisweight;
    @keeper = @thisitem if rand($weight) < $thisweight;
  }
  for (@keeper) {
    ## should be a list of ids or defns
    die "huh $_ in $defn" if ref $defn;
    if (/^ (.*)/s) {
      print $1;
    } elsif (/^(\w+)$/) {
      show($1);
    } else {
      die "Can't show $_ in $defn\n";
    }
  }
}


__END__
START: stanza "\n---\n" stanza "\n---\n" stanza;

stanza: stanza " " exclaim " " stanza2 | stanza2;
stanza2: sentence " " comparison " " question |
         sentence " " comparison |
         comparison " " comparison " " exclaim |
         address " " question " " question " " sentence;

sentence: sentence "\n" sentence2 | sentence2;
sentence2: "The " adjectiveNotHep " " personNotHep " " verbRelating " the "
adjectiveHep " " personHep "." |
"The " personHep " " verbRelating " the " adjectiveNotHep ", " adjectiveNotHep " " personNotHep ".";

question: question " " question2  | question2;
question2: ques_start " " adjectiveHep " " personNotHep "?" |
ques_start " " adjectiveNotHep " " personHep "?";

comparison: comparison " " comparison2 | comparison2;
comparison2: "One says '" compNotHep "' while the other says '" compHep
"'." |
"One thinks '" compNotHep "' while the other thinks '" compHep "'." |
"They shout '" compNotHep "!' And we shout '" compHep "'." |
"It's " compNotHep " versus " compHep "!" ;

personNotHep:  "capitalist" | "silk purse man" | "square" | "banker" |
"Merchant King" | "pinstripe suit" ;

personHep: "cat" | "beat soul" | "wordsmith" | "hep cat" | "free man" |
"street poet" | "skin beater" | "reed man" ;

adjectiveNotHep: "soul-sucking" | "commercial" | "cash-counting" |
"bloody-handed" | "four-cornered" | "uncool" | "love-snuffing";

adjectiveHep: "love-drunk" | "cool, cool" | "happening" | "tuned-in" |
"street wise" | "wise and learned";

verbRelating: "begrudges" | "fears" | "distresses" | "dodges" |
"dislikes" | "evades" | "curses" | "belittles" | "avoids" | "battles";

compNotHep: "recreation" | "isolation" | "tranportation" | "sacred nation"
  | "complication" | "subordination";
compHep: "fornication" | "instigation" | "interpretation" | "elevation"
| "animation" | "inebriation" | "true relation";

ques_start: 2 @ (5 @ "Could there ever" | 7 @ "How could there") " be a" |
  "Can you picture a" ;
address:  "Catch this:" | "Listen, cats," | "Dig it:" |
  "I lay this on you:";
exclaim: "Heavy, man."| "Heavy." | "Yow!" | "Snap 'em for me." |
  "Dig it.";