'''
Created on Nov 8, 2010
Modified on April 4, 2011
@author: ola
'''
import re
import sys
import random
import string

def generate_from (nont, rules):
    """
    using the nonterminal in string nont (starts with <)
    look up definition in dictionary rules, pick production to expand
    (choose at random from list associated with nont in dictionary rules)
    and return list that consists of expanding the non-terminal
    using the randomly chosen production for the non-terminal
    """
    words = []
    if nont in rules:
        all_rules = rules[nont]
        chosen_rule = random.choice(all_rules)
        for word in chosen_rule:
            if word.startswith("<"):
                words.extend(generate_from(word, rules))
            else:
                words.append(word)
        return words
    else:
        raise Exception('no definition found named ' + nont + ' --- spelling error?')

def start (rules):
    output = ' '.join(generate_from("<start>", rules))
    idx = output.find('.')
    while idx > 0:
        if output[idx-1].isspace():
            output = output[:idx-1] + output[idx:]
        idx = output.find('.', idx+1)
    
#    idx = output.find(' ')
#    while idx > 0:
#        if idx > 45:
#            print(output[:idx])
#            output = output[idx+1:]
#            idx = 0
#        idx = output.find(' ', idx+1)
    print(output)

def parse_rule (rule, dictionary):
    """
    rule is a string in the proper format for an RSG grammar definition (complete {...})
    modifies dictionary (SIDE_EFFECT) by adding a key for the non-terminal
    in the definition and the value is the list of rules for the non-terminal
    """
    pattern = r"{\s*(<[^>]+>)(.*)}"
    partPattern = r"([^;]+);"
    mat = re.match(pattern, rule)
    if mat:
        nont = mat.group(1).strip()
        remainder = mat.group(2)
        dictionary[nont] = []
        for mat in re.finditer(partPattern, remainder):
            production = mat.group(1)
            parts = production.strip().split()
            dictionary[nont].append(parts)
        if len(dictionary[nont]) == 0:
            raise Exception('no rules found for definition ' + nont + ' --- missing semicolon?')
        elif ('{' in [ x[0] for x in dictionary[nont] ]):
            raise Exception('definition ' + nont + ' unfinished --- missing }?')
    else:
        raise Exception('improperly formatted definition ' + rule)

def initialize (text):
    """
    Create a dictionary of grammar definitions from source
    and return the dictionary. Keys are non-terminals, values
    are lists of rules to expand the non-terminals (each rule is a list)
    """
    flat = re.sub(r"\s+"," ", text)
    gram = r"{[^}]+}"
    dictionary = {}
    for m in re.finditer(gram, flat):
        parse_rule(m.group(), dictionary)
    if len(dictionary) == 0:
        raise Exception('no definitions found --- misplaced }?')
    elif len(dictionary) == 1:
        raise Exception('only one definition found --- misplaced }?')
    else:
        return dictionary

def main (filename):
    try:
        f = open(filename)
        d = initialize(f.read())
        f.close()
        for _ in range(3):
            print('<P>')
            start(d)
            print('</P>')
    except Exception as e:
        if 'recursion' in e.args[0]:
            print('<xmp>Error reading grammar: one of your definitions is defined only in terms of itself :(</xmp>')
        else:
            print('<xmp>Error reading grammar: ' + e.args[0] + '</xmp>')

if __name__ == "__main__":
    if len(sys.argv) == 2:
        main(sys.argv[1])
    else:
        main("apt-issues.g")

{
<start>
Duke Basketball will <verb> the <basketballgame>.
<Playername> will <accomplishment> .;
}

{
<verb>
beat ;
humiliate ;
destroy ;
}

{
<basketballgame>
Tar Heels ;
Jayhawks ;
ACC conference ;
}

{
<Playername>
Jabari Parker ;
Rodney Hood ;
Quinn Cook ;
Tyler Thornton ;
Matt Jones ;
Rasheed Sulaimon ;
Josh Hairston ;
Amile Jefferson ;
Andre Dawkins ;
Marshall Plumlee ;
Nick Pagliuca ;
Todd Zafirovski ;
Semi Ojeleye ;
}

{
<accomplishment>
score a double-double ;
score a triple-double ;
make the Sports Center top 10 plays of the week ;
be named the ACC top player of the week ;
}