protected void computeFollows() {
		// Put EOI after the start symbol
		// follow.put(grammar.getStart(), AssemblyEOI.EOI);

		boolean changed = true;
		while (changed) {
			changed = false;
			// [A] => ... [X] ALPHA [B] ... implies Follow[X] includes First(ALPHA) and First[B]
			// [A] => ... [B] ALPHA implies Follow[B] includes Follow[A]
			// Walk each production from left, scanning for non-terminals
			// For each, walk to the right, adding the first of each to the current (not LHS)
			// Finish the subwalk after a terminal or non-nullable symbol
			// If you hit the end, add follow(LHS) to follow the current symbol
			for (AbstractAssemblyProduction<?> prod : grammar) {
				nextX: for (int i = 0; i < prod.getRHS().size(); i++) {
					AssemblySymbol px = prod.getRHS().getSymbol(i);
					if (px instanceof AssemblyNonTerminal) {
						AssemblyNonTerminal X = (AssemblyNonTerminal) px;
						int j;
						for (j = i + 1; j < prod.getRHS().size(); j++) {
							AssemblySymbol B = prod.getRHS().getSymbol(j);
							if (B instanceof AssemblyNonTerminal) {
								AssemblyNonTerminal nt = (AssemblyNonTerminal) B;
								changed |= follow.putAll(X, first.get(nt));
								if (!nullable.contains(B)) {
									continue nextX;
								}
							}
							else if (B instanceof AssemblyTerminal) {
								AssemblyTerminal t = (AssemblyTerminal) B;
								changed |= follow.put(X, t);
								continue nextX;
							}
						}
						// If I got here, I never encountered a non-nullable symbol
						// Do a simple substitution for understanding:
						//    [A] => ... [X] ALPHA  (we never hit non-nullable B)
						changed |= follow.putAll(X, follow.get(prod.getLHS()));
					}
				}
			}
		}
	}
Compute the follow set for each non-terminal
Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/parse/AssemblyFirstFollow.java