#!/usr/bin/env python

# Semantic grammar for
# Ling 130a/230a: Introduction to semantics and pragmatics, Winter 2022
# Chris Potts


######################################################################
# Entities:
HOMER = 'HOMER'
BART = 'BART'
LISA = 'LISA'
MAGGIE = 'MAGGIE'

# Universe:
U = {HOMER, BART, LISA, MAGGIE}

######################################################################
# Semantic lexicon

# Proper names:
Homer = HOMER
Bart = BART
Lisa = LISA
Maggie = MAGGIE

# Ns:
Simpson = {HOMER, BART, LISA, MAGGIE}
child = {BART, LISA, MAGGIE}
student = {BART, LISA}
parent = {HOMER}

# Intransitive Vs:
skateboards = {HOMER, BART}
studies = {LISA}
introspects = {LISA, MAGGIE}
speaks = {BART, LISA, HOMER}

# Transitive Vs:
TEASES_LOOKUP = {
    HOMER: {BART, LISA},
    LISA: {BART, HOMER},
    BART: {HOMER, LISA},
    MAGGIE: set()}

teases = lambda y: TEASES_LOOKUP[y]

ADMIRES_LOOKUP = {
    HOMER: set(),
    LISA: {BART, HOMER, MAGGIE},
    BART: set(),
    MAGGIE: {HOMER, BART, LISA}}

admires = lambda y: ADMIRES_LOOKUP[y]

loves = lambda y: U # Everyone loves everyone.

# Adjectives:
scholarly = lambda X: {LISA, MAGGIE} & X
distractible = lambda X: {BART, HOMER} & X
hungry = lambda X: {MAGGIE, HOMER} & X
Springfieldian = lambda X: U & X

# Negation:
never = lambda X : U - X

# Quantificational determiners:
every = lambda X: (lambda Y : X <= Y)
some =  lambda X: (lambda Y : len(X & Y) >= 1)
no = lambda X: (lambda Y : len(X & Y) == 0)
most = lambda X: (lambda Y: (len(X & Y) / len(X)) > 0.5)


######################################################################
# Semantic grammar

def interpret(semtree):
    # Terminal nodes just get looked up in the grammar:
    if isinstance(semtree, str):
        return globals()[semtree]
    # Non-branching nodes:
    elif len(semtree) == 1:
        return interpret(semtree[0])
    else:
        left, right = semtree
        left_sem = interpret(left)
        right_sem = interpret(right)
        # The Rule S set-membership cases:
        if isinstance(left_sem, str) and isinstance(right_sem, set):
            return left_sem in right_sem
        # All others: whichever function application works!
        else:
            try:
                return left_sem(right_sem)
            except TypeError:
                return right_sem(left_sem)

######################################################################
# Examples:

def demo():

    # Interpet some semantic parsetrees:
    semtrees = [
        ['Lisa'],
        ['child'],
        ['Bart', 'skateboards'],
        ['Maggie', 'skateboards'],
        ['teases', 'Homer'],
        ['teases', 'Bart'],
        ['admires', 'Maggie'],
        ['scholarly', 'child'],
        ['scholarly', 'student'],
        ['hungry', ['scholarly', 'child']],
        ['never', 'skateboards'],
        ['never', ['admires', 'Maggie']],
        ['Maggie', ['teases', 'Bart']], # Minimal syntax version.
        [['Maggie'], [['teases'], [['Bart']]]],
        [['every', 'child'], 'skateboards'],  # Minimal syntax version.
        [[['every'], [['child']]], [['skateboards']]]
    ]

    for semtree in semtrees:
        print(semtree, interpret(semtree))


if __name__ == '__main__':
   demo()
