private function build_first_sets(){
		$firsts = array();
		do {
			$changes = 0;
			foreach( $this->rules as $rule ){
				list( $nt, $rhs ) = $rule;
				$s = reset( $rhs );
				// get any special rule processing for this non-terminal
				//$excludela = isset($this->excludela[$nt]) ? $this->excludela[$nt] : null;
				do {
					// add terminal to first set of $nt
					if( $this->is_terminal( $s ) ){
						if( ! isset($firsts[$nt][$s]) ){
							$firsts[$nt][$s] = $s;
							$changes++;
						}
						continue 2;
					}
					// else inherit from first set of this non-terminal
					if( ! isset($firsts[$s]) ){
						// throw new Exception( sprintf("FIRST(%s) not defined", $s) );
						$changes++;
						continue 2;
					}
					// collect all terminal symbols in this non-terminal's set
					// except the empty string which we do not inherit.
					$derives_e = isset( $firsts[$s][P_EPSILON] );
					foreach( $firsts[$s] as $t ){
						// Omit any lookahead symbols denied by special rule
						//if( $excludela && in_array( $t, $excludela,true ) ){
						//	// ignored
						//}
						//else 
						if( $t !== P_EPSILON && ! isset( $firsts[$nt][$t] ) ){
							$firsts[$nt][$t] = $t;
							$changes++;
						}
					}
					// we move to next in sequence if non-terminal can derive empty 
				}
				while( $derives_e && $s = next($rhs) );
			}
		}
		while( $changes > 0 );
		return $firsts;
	}