def _parse(self, string):
nstring = self._norm(string)<EOL>if nstring in self.sounds:<EOL><INDENT>sound = self.sounds[nstring]<EOL>sound.normalized = nstring != string<EOL>sound.source = string<EOL>return sound<EOL><DEDENT>match = list(self._regex.finditer(nstring))<EOL>if len(match) == <NUM_LIT:2>:<EOL><INDENT>sound1 = self._parse(nstring[:match[<NUM_LIT:1>].start()])<EOL>sound2 = self._parse(nstring[match[<NUM_LIT:1>].start():])<EOL>if '<STR_LIT>' not in (sound1.type, sound2.type) andsound1.type == sound2.type:<EOL><INDENT>if sound1.type == '<STR_LIT>':<EOL><INDENT>return Diphthong.from_sounds(  <EOL>string, sound1, sound2, self)<EOL><DEDENT>elif sound1.type == '<STR_LIT>' andsound1.manner in ('<STR_LIT>', '<STR_LIT>', '<STR_LIT>', '<STR_LIT>') andsound2.manner in ('<STR_LIT>', '<STR_LIT>', '<STR_LIT>', '<STR_LIT>'):<EOL><INDENT>return Cluster.from_sounds(  <EOL>string, sound1, sound2, self)<EOL><DEDENT><DEDENT>return UnknownSound(grapheme=nstring, source=string, ts=self)  <EOL><DEDENT>if len(match) != <NUM_LIT:1>:<EOL><INDENT>return UnknownSound(grapheme=nstring, source=string, ts=self)  <EOL><DEDENT>pre, mid, post = nstring.partition(nstring[match[<NUM_LIT:0>].start():match[<NUM_LIT:0>].end()])<EOL>base_sound = self.sounds[mid]<EOL>if isinstance(base_sound, Marker):  <EOL><INDENT>assert pre or post<EOL>return UnknownSound(grapheme=nstring, source=string, ts=self)  <EOL><DEDENT>features = attr.asdict(base_sound)<EOL>features.update(<EOL>source=string,<EOL>generated=True,<EOL>normalized=nstring != string,<EOL>base=base_sound.grapheme)<EOL>grapheme, sound = '<STR_LIT>', '<STR_LIT>'<EOL>for dia in [p + EMPTY for p in pre]:<EOL><INDENT>feature = self.diacritics[base_sound.type].get(dia, {})<EOL>if not feature:<EOL><INDENT>return UnknownSound(  <EOL>grapheme=nstring, source=string, ts=self)<EOL><DEDENT>features[self._feature_values[feature]] = feature<EOL>grapheme += dia[<NUM_LIT:0>]<EOL>sound += self.features[base_sound.type][feature][<NUM_LIT:0>]<EOL><DEDENT>grapheme += base_sound.grapheme<EOL>sound += base_sound.s<EOL>for dia in [EMPTY + p for p in post]:<EOL><INDENT>feature = self.diacritics[base_sound.type].get(dia, {})<EOL>if not feature:<EOL><INDENT>return UnknownSound(  <EOL>grapheme=nstring, source=string, ts=self)<EOL><DEDENT>features[self._feature_values[feature]] = feature<EOL>grapheme += dia[<NUM_LIT:1>]<EOL>sound += self.features[base_sound.type][feature][<NUM_LIT:1>]<EOL><DEDENT>features['<STR_LIT>'] = sound<EOL>new_sound = self.sound_classes[base_sound.type](**features)<EOL>if text_type(new_sound) != sound:<EOL><INDENT>new_sound.alias = True<EOL><DEDENT>if grapheme != sound:<EOL><INDENT>new_sound.alias = True<EOL>new_sound.grapheme = grapheme<EOL><DEDENT>return new_sound<EOL>
Parse a string and return its features.

        :param string: A one-symbol string in NFD

        Notes
        -----
        Strategy is rather simple: we determine the base part of a string and
        then search left and right of this part for the additional features as
        expressed by the diacritics. Fails if a segment has more than one basic
        part.