docs: def calculate_sets(rules):
      """Calculate FOLLOW sets.
  
      Adapted from: http://lara.epfl.ch/w/cc09:algorithm_for_first_and_follow_sets"""
      symbols = {sym for rule in rules for sym in rule.expansion} | {rule.origin for rule in rules}
  
      # foreach grammar rule X ::= Y(1) ... Y(k)
      # if k=0 or {Y(1),...,Y(k)} subset of NULLABLE then
      #   NULLABLE = NULLABLE union {X}
      # for i = 1 to k
      #   if i=1 or {Y(1),...,Y(i-1)} subset of NULLABLE then
      #     FIRST(X) = FIRST(X) union FIRST(Y(i))
      #   for j = i+1 to k
      #     if i=k or {Y(i+1),...Y(k)} subset of NULLABLE then
      #       FOLLOW(Y(i)) = FOLLOW(Y(i)) union FOLLOW(X)
      #     if i+1=j or {Y(i+1),...,Y(j-1)} subset of NULLABLE then
      #       FOLLOW(Y(i)) = FOLLOW(Y(i)) union FIRST(Y(j))
      # until none of NULLABLE,FIRST,FOLLOW changed in last iteration
  
      NULLABLE = set()
      FIRST = {}
      FOLLOW = {}
      for sym in symbols:
          FIRST[sym]={sym} if sym.is_term else set()
          FOLLOW[sym]=set()
  
      # Calculate NULLABLE and FIRST
      changed = True
      while changed:
          changed = False
  
          for rule in rules:
              if set(rule.expansion) <= NULLABLE:
                  if update_set(NULLABLE, {rule.origin}):
                      changed = True
  
              for i, sym in enumerate(rule.expansion):
                  if set(rule.expansion[:i]) <= NULLABLE:
                      if update_set(FIRST[rule.origin], FIRST[sym]):
                          changed = True
  
      # Calculate FOLLOW
      changed = True
      while changed:
          changed = False
  
          for rule in rules:
              for i, sym in enumerate(rule.expansion):
                  if i==len(rule.expansion)-1 or set(rule.expansion[i+1:]) <= NULLABLE:
                      if update_set(FOLLOW[sym], FOLLOW[rule.origin]):
                          changed = True
  
                  for j in range(i+1, len(rule.expansion)):
                      if set(rule.expansion[i+1:j]) <= NULLABLE:
                          if update_set(FOLLOW[sym], FIRST[rule.expansion[j]]):
                              changed = True
  
      return FIRST, FOLLOW, NULLABLE