public int closure(LR0ItemSet seed)
	{
		if(memoizedClosures.containsKey(seed)) return memoizedClosures.get(seed);
		closureProductions.clear();
		
		closure_newItems0.clear();
		closure_newItems1.clear();
		
		// One can tell the difference between a seed item
		// and a closure-generated item by the bullet position:
		// only closure-generated items and ^ ::= (*) S $, which
		// is not generated by any closure, have the bullet at
		// the beginning (position = 0).

		// For each seed item:
		for(int i = 0;i < seed.size();i++)
		{
			// If the item's bullet is not at the end
			if(seed.getPosition(i) < spec.pr.getRHSLength(seed.getProduction(i)))
			{
				int sym = spec.pr.getRHSSym(seed.getProduction(i),seed.getPosition(i));
				// and is situated to the left of a nonterminal
				// (i.e., the item is of the form [A ::= a (*) X b]):
				if(spec.nonterminals.get(sym))
				{
					// Mark all productions [X ::= g] for addition to the closure.
					closure_newItems1.or(spec.nt.getProductions(sym));
				}
			}
		}
		
		closureProductions.or(closure_newItems1);
		
		// Repeat until the item set I does not change:
		boolean iChanged = true;
		while(iChanged)
		{
			iChanged = false;
			closure_newItems0.clear();
			closure_newItems0.or(closure_newItems1);
			closure_newItems1.clear();
			// For any item [A ::= a (*) X b] in I
			// (but as mentioned above, the bullet will invariably be at the
			//  beginning, so a is always the empty sequence):
			for(int p = closure_newItems0.nextSetBit(0);p >= 0;p = closure_newItems0.nextSetBit(p+1))
			{
				// Check that the item's bullet is not at the end (with the bullet at the
				// beginning, this means checking that the production does not have an
				// empty RHS) and that the first RHS symbol is a nonterminal X.
				if(spec.pr.getRHSLength(p) > 0 && spec.nonterminals.get(spec.pr.getRHSSym(p,0)))
				{
					// If so, I := union(I,[X ::= (*) g]).
					closure_newItems1.or(spec.nt.getProductions(spec.pr.getRHSSym(p,0)));
				}
				iChanged |= ParserSpec.union(closureProductions,closure_newItems1);
			}
		}
		
		int[] newItems = new int[2 * (seed.size() + closureProductions.cardinality())];
		seed.copyItems(newItems);
		for(int i = seed.size(),p = closureProductions.nextSetBit(0);p >= 0;p = closureProductions.nextSetBit(p+1))
		{
			newItems[2*(i++)] = p;
		}
		LR0ItemSet newClosure = new LR0ItemSet(newItems);
		itemSets.add(newClosure);
		transitionLabels.add(new BitSet());
		transitions.add(new int[transitionArraySize]);
		gotoItems.add(new BitSet[transitionArraySize]);
		memoizedClosures.put(seed,itemSets.size() - 1);
		memoizedClosures.put(newClosure,itemSets.size() - 1);
		return itemSets.size() - 1;
	}