# The pattern matcher, with examples

import string

#----------------------------------------------------------------------------------
# cleanup removes punctuation characters from a string

def cleanup(phrase):
    chars = [ch for ch in phrase if ch not in string.punctuation]
    return string.join(chars, "")

#----------------------------------------------------------------------------------

class Rule:

    def __init__(self, pattern, action):
        assert pattern.count('...') <= 1, "no more than one '...' symbol allowed"
        self.pattern = pattern
        self.action = action

    def __str__(self):
        return "%s" % self.pattern

    def match(self, phrase):
        phrase = cleanup(phrase)
        wordsLeft = string.split(phrase)
        symbolsLeft = self.pattern
        substitutions = []
        while len(symbolsLeft) > 0 and len(wordsLeft) > 0:
            firstSymbol = symbolsLeft[0]
            symbolsLeft = symbolsLeft[1:]
            if firstSymbol == '...':
                numMatched = len(wordsLeft) - len(symbolsLeft)
                wordsMatched = wordsLeft[:numMatched]
                substitutions.append(string.join(wordsMatched))
                wordsLeft = wordsLeft[numMatched:]
            elif firstSymbol == '#':
                try:
                    substitutions.append(eval(wordsLeft[0]))
                    wordsLeft = wordsLeft[1:]
                except:
                    return None
            elif type(firstSymbol) is list and wordsLeft[0] in firstSymbol:
                substitutions.append(wordsLeft[0])
                wordsLeft = wordsLeft[1:]
            elif firstSymbol == '_':
                substitutions.append(wordsLeft[0])
                wordsLeft = wordsLeft[1:]
            elif firstSymbol == wordsLeft[0]:
                wordsLeft = wordsLeft[1:]
            else:
                return None
        if len(symbolsLeft) > 0 or len(wordsLeft) > 0:
            return None
        else:
            return substitutions

    def apply(self, phrase):
        subs = self.match(phrase)
        if subs is not None:
            result = self.action(*subs)
            return result

#----------------------------------------------------------------------------------
# Examples of pattern/action rules

# The pattern symbol '_' matches exactly one word
# The pattern symbol '...' matches one or more words
# Other pattern symbols specify literal word matches

pat1 = ['The', '_', 'in', '_', 'stays', 'mainly', '...']

def action1(noun1, noun2, prep):
    print "Executing action1"
    print "noun1:", noun1
    print "noun2:", noun2
    print "prep:", prep

r1 = Rule(pat1, action1)

# >>> r1.match("The rain in Spain stays mainly in the plain")
# ['rain', 'Spain', 'in the plain']
# >>> r1.match("The snow in Greenland stays mainly on the mountains")
# ['snow', 'Greenland', 'on the mountains']
# >>> r1.apply("The rain in Spain stays mainly in the plain")
# Executing action1
# noun1: rain
# noun2: Spain
# prep: in the plain
# >>> r1.apply("The snow in Greenland stays mainly on the mountains")
# Executing action1
# noun1: snow
# noun2: Greenland
# prep: on the mountains

#----------------------------------------------------------------------------------
# A sublist matches one word from a set of possibilities

pat2 = ['The', '_', 'in', ['Spain', 'Morocco', 'France'], 'stays', 'mainly', '...']

r2 = Rule(pat2, action1)

# >>> r2.match("The snow in Greenland stays mainly on the mountains")
# >>> r2.match("The snow in France stays mainly on the mountains")
# ['snow', 'France', 'on the mountains']
# >>> r2.match("The snow in Morocco stays mainly out of sight")
# ['snow', 'Morocco', 'out of sight']
# >>> r2.apply("The snow in Morocco stays mainly out of sight")
# Executing action1
# noun1: snow
# noun2: Morocco
# prep: out of sight

#----------------------------------------------------------------------------------
# The pattern symbol '#' matches exactly one number

pat3 = ['The', '_', 'in', '#', '...']

def action2(noun, year, rest):
    print "Executing action2"
    print "noun:", noun
    print "year:", year
    print "rest:", rest

r3 = Rule(pat3, action2)

# >>> r3.match("The rain in Spain was really something to see")
# >>> r3.match("The rain in 1993 was really something to see")
# ['rain', 1993, 'was really something to see']
# >>> r3.apply("The rain in 1993 was really something to see")
# Executing action2
# noun: rain
# year: 1993
# rest: was really something to see

