from random import randrange


def choose_randomly(nonterminals: list[callable], terminals: list[str]) -> str:
    num_options = len(nonterminals) + len(terminals)
    choice_index = randrange(0, num_options)

    if choice_index < len(nonterminals):
        nonterminal = nonterminals[choice_index]
        return nonterminal()
    else:
        terminal = terminals[choice_index - len(nonterminals)]
        return terminal


# 〈sentence〉 → 〈noun phrase〉〈verb phrase〉〈noun phrase〉
def sentence():
    return f"{noun_phrase()} {verb_phrase()} {noun_phrase()}"


# 〈noun phrase〉 → 〈adjective phrase〉〈noun〉
def noun_phrase():
    return f"{adjective_phrase()} {noun()}"


# 〈adj. phrase〉 → 〈article〉 | 〈possessive〉 | 〈adjective phrase〉〈adjective〉
def adjective_phrase():
    return choose_randomly(
        [article, possessive, lambda: f"{adjective_phrase()} {adjective()}"], []
    )


# 〈verb phrase〉 → 〈verb〉 | 〈adverb〉〈verb phrase〉
def verb_phrase():
    return choose_randomly([verb, lambda: f"{adverb()} {verb_phrase()}"], [])


# 〈noun〉 → dog | trousers | daughter | nose | homework | wizard | pony | ···
def noun():
    return choose_randomly(
        [],
        ["dog", "trousers", "daughter", "nose", "homework", "wizard", "pony"],
    )


# 〈article〉 → the | a | some | every | that | ···
def article():
    return choose_randomly([], ["the", "a", "some", "every", "that"])


# 〈possessive〉 → 〈noun phrase〉’s | my | your | his | her | ···
def possessive():
    return choose_randomly(
        [lambda: f"{noun_phrase()}'s"], ["my", "your", "his", "her", "their"]
    )


# 〈adjective〉 → friendly | furious | fragrant | green | severed | little | ···
def adjective():
    return choose_randomly(
        [], ["friendly", "furious", "fragrant", "green", "severed", "little"]
    )


# 〈verb〉 → ate | found | wrote | killed | mangled | saved | invented | broke | ···
def verb():
    return choose_randomly(
        [], ["ate", "found", "wrote", "killed", "mangled", "saved", "broke"]
    )


# 〈adverb〉 → squarely | incompetently | barely | sort of | awkwardly | totally | ···
def adverb():
    return choose_randomly(
        [],
        [
            "squarely",
            "incompetently",
            "barely",
            "sort of",
            "awkwardly",
            "totally",
        ],
    )


if __name__ == "__main__":
    print(sentence())
