Python molar mass calculator
For such a simple parsing job, I'd use a regular expression and re.findall()` to split the string into a list of tokens.  It's just one line instead of over 20 lines. The tokens are an element (an upper case letter possibly with a lower case letter), a repeat count (1 or more digits), or a bracket. The &quot;{}[]&quot; matches any of the brackets.
    tokens = re.findall(r&quot;[A-Z][a-z]|[0-9]+|[](){}[]&quot;, string)

collections.Counter() is useful for counting the elements. Lists used as simple stacks help keep track of bracketed sub-compounds.
    composition = Counter()
    match = []
    stack = []

Using a list of tokens may be less optimal for large parsing tasks, but is sufficient for this command line calculator.  Plus, it lets you &quot;peek&quot; at the next token to see if its a number, greatly simplifying the handling of the numbers in a formula.  (I used an if-expression in the final code)
        if tokens[0].isalpha():
            element = tokens.pop(0)

            if tokens[0].isdigit():           # &lt;-- peek at next token to see if
                repeat = int(tokens.pop(0))   #     there is a count
            else:
                repeat = 1

            compound[element] += repeat

When an opening bracket is encountered, save the current state (the Counter) on a stack and start a new state.  Save the matching bracket on a stack too.
        elif tokens[0] in ('(', '[', '{'):
            match.append(MATCH[tokens.pop(0)])
            stack.append(composition)
            composition = Counter()

When a closing bracket is encountered, see if it matches the one on top of the bracket stack.  Pop the saved state from the stack and combine it from the one for the bracketed sub-compound.
        elif tokens[0] == match[-1]:
            tokens.pop(0)
            match.pop()
                
            repeat = int(tokens.pop(0)) if tokens and tokens[0].isdigit() else 1
            for element in composition.keys():
                composition[element] *= repeat
                
            composition.update(stack.pop())

Any other token is an error.
        else:
            if token in (')', ']', '}'):
                raise ValueError(f&quot;Error, mismatched bracket: &quot;
                                 f&quot;expected {match[-1]} got {tokens[0]}.&quot;)
            
            else:
                raise ValueError(f&quot;Error, unrecognized token &quot;
                                 f&quot;in formula: '{tokens[0]}'.&quot;)

If there are any brackets left on the stack at the end, then there are mismatched brackets.
    if match:
        brackets = ', '.join(f&quot;'{b}'&quot; for b in match[::-1])
        raise ValueError(f&quot;Error, missing bracket(s): {brackets}.&quot;)

The full parser:
import re

from collections import Counter

MATCH = {'(':')', '[':']', '{':'}'}

def parse(molecule):

    tokens = re.findall(r&quot;[A-Z][a-z]?|[0-9]+|[]{}()[]&quot;, molecule)
    
    composition = Counter()
    match = []
    stack = []
   
    while tokens:
        # element with optional count
        if tokens[0].isalpha():
            element = tokens.pop(0)
            count = int(tokens.pop(0)) if tokens and tokens[0].isdigit() else 1
            composition[element] += count

        # start a sub-compound
        elif tokens[0] in ('(', '[', '{'):
            match.append(MATCH[tokens.pop(0)])
            stack.append(composition)
            composition = Counter()
            
        # matching close bracket ends a sub-compound with an optional count
        elif tokens[0] == match[-1]:
            tokens.pop(0)
            match.pop()
                
            repeat = int(tokens.pop(0)) if tokens and tokens[0].isdigit() else 1
            for element in composition.keys():
                composition[element] *= repeat
                
            composition.update(stack.pop())

        # &quot;syntax&quot; error in the formula
        else:
            if token[0] in (')', ']', '}'):
                raise ValueError(f&quot;Error, mismatched bracket: &quot;
                                 f&quot;expected '{match[-1]}' got '{token[0]}'.&quot;)
            
            else:
                raise ValueError(f&quot;Error, unrecognized token in &quot;
                                 f&quot;formula: '{token[0]}'.&quot;)

    # left over, unmatched brackets
    if match:
        brackets = ', '.join(f&quot;'{b}'&quot; for b in match[::-1])
        raise ValueError(f&quot;Error, missing bracket(s): {brackets}.&quot;)
    
    return dict(composition)