def _analyze(
    nlp: spacy.Language,
    sentence: str,
    word: str,
    option: str,
    opposite_word: str,
    create_word_fn: CreateWordFn,
) -> Mapping[str, str]:
  """Analyzes the syntactic role of a word in a sentence."""
  # The 2 versions of the same example will share the fake word.
  seed = sentence
  sentence = sentence.replace(_OPTION, option)
  tokens = {token.idx: token for token in nlp(sentence.format(word=word))}
  token = tokens.get(sentence.index(_WORD))
  if not token:
    raise ValueError('token not found in document')

  option_pos = set(t.pos_ for t in tokens.values() if t.text == option)

  pos, tag, lemma = token.pos_, token.tag_, token.lemma_
  dobj = set(child.lemma_ for child in token.children if child.dep_ == 'dobj')
  if lemma in ('more', 'less', 'few'):
    raise ValueError('quantifier')
  if lemma in ('be'):
    raise ValueError('Skipped verb')

  wordnet_pos = _POS_TAGS.get(pos)
  if not wordnet_pos:
    raise ValueError(f'{pos}:{tag} is not supported.')
  if lemma == 'lose' and not dobj:
    synsets = [wordnet.synset('lose.v.02')]  # "not winning" sense.
  elif lemma == 'keep' and opposite_word == 'lose':
    synsets = [wordnet.synset('keep.v.03')]  # Retain possession sense.
  elif lemma == 'strong' and opposite_word == 'soft':
    synsets = [wordnet.synset('hard.a.03')]  # "not soft" sense.
  elif lemma == 'soft' and opposite_word == 'louder':
    synsets = [wordnet.synset('soft.a.03')]  # Low volume sense.
  elif lemma == 'work' and opposite_word == 'unemployed':
    synsets = [wordnet.synset('work.v.02')]
  elif lemma == 'hard' and opposite_word in ('soft', 'flimsy'):
    synsets = [wordnet.synset('hard.a.03')]  # "not soft" sense.
  elif lemma == 'old' and opposite_word in ('new',):
    synsets = [wordnet.synset('old.a.02')]  # "not new" sense.
  elif lemma == 'full' and (opposite_word in ('hungry',) or
                            _PROPN in option_pos):
    synsets = [wordnet.synset('full.s.04')]
  elif lemma == 'light' and (option.lower() in ('brown',) or
                             opposite_word in ('darker',)):
    synsets = [wordnet.synset('light.a.02')]  # For colors.
  elif lemma == 'heavy' and option.lower() in ('black',):
    synsets = [wordnet.synset('dark.a.02')]  # For colors.
  elif lemma == 'high' and option.lower() in ('boots', 'the shelf'):
    synsets = [wordnet.synset('high.a.02')]  # Literal sense.
  elif lemma == 'low' and option.lower() in ('shoes',):
    synsets = [wordnet.synset('low.a.02')]  # Literal sense.
  elif lemma == 'pass':
    synsets = [wordnet.synset('pass.v.14')]
  elif lemma == 'poor':
    if _PROPN in option_pos:
      synsets = [wordnet.synset('poor.a.02')]  # not rich sense.
    else:
      synsets = [wordnet.synset('poor.s.06')]  # unsatisfactory sense.
  elif lemma == 'shallow' and _PROPN in option_pos:
    synsets = [wordnet.synset('shallow.s.03')]  # Metaphorical.
  elif lemma == 'deep':
    if _PROPN in option_pos:
      synsets = [wordnet.synset('deep.s.02')]  # Metaphorical.
    else:
      synsets = [wordnet.synset('deep.a.03')]  # Physical sense.
  elif lemma == 'short':
    if _PROPN in option_pos or option.lower() in ('tent',):
      synsets = [wordnet.synset('short.a.03')]  # Stature sense.
    elif option.lower() in ('the meeting', 'story'):
      synsets = [wordnet.synset('short.a.01')]  # Temporal sense.
    else:
      synsets = [wordnet.synset('short.a.02')]  # Spatial sense.
  elif wordnet_pos == wordnet.ADV:
    if lemma in _ADV_SYNSETS:
      synsets = [wordnet.synset(_ADV_SYNSETS[lemma])]
    else:
      # Adjective definitions are usually better descriptors.
      synsets = (
          wordnet.synsets(lemma, pos=wordnet.ADJ) +
          wordnet.synsets(lemma, pos=wordnet.ADV))
  elif lemma in _SYNSETS:
    synsets = [wordnet.synset(_SYNSETS[lemma])]
  else:
    synsets = wordnet.synsets(lemma, pos=wordnet_pos)
  if not synsets:
    raise ValueError(f'no synset found for {lemma}:{pos}.')

  definition = synsets[0].definition()
  # Remove suffixes/prefixes from definitions.
  definition = definition.split('; ;')[0]
  definition = definition.split('; --')[0]
  definition = definition.split('; e.g.')[0]
  definition = definition.split(' (sometimes used in combinations')[0]
  definition = definition.replace('(literal meaning) ', '')
  definition = definition.replace('literal meanings; ', '')
  # Remove to from the beginning on some definitions.
  if definition.startswith('to '):
    definition = definition[3:]
  fake_word, fake_lemma, fake_root = create_word_fn(seed, word, lemma, tag)
  return dict(
      pos=pos,
      tag=tag,
      morph=str(token.morph),
      lemma=lemma,
      definition=definition,
      fake_word=fake_word,
      fake_lemma=fake_lemma,
      fake_root=fake_root,
      examples=synsets[0].examples())
Analyzes the syntactic role of a word in a sentence.
language/wino_dict/utils.py