public static <T> Cfg<T> of(final List<? extends Rule<? extends T>> rules) {
		@SuppressWarnings("unchecked")
		final var rules0 = (List<Rule<T>>)List.copyOf(rules);

		// Rules must not be empty.
		if (rules0.isEmpty()) {
			throw new IllegalArgumentException(
				"The list of rules must not be empty."
			);
		}

		final List<Symbol<T>> symbols = rules0.stream()
			.flatMap(Cfg::ruleSymbols)
			.distinct()
			.toList();

		final List<NonTerminal<T>> nonTerminals = symbols.stream()
			.map(rule -> rule.at(null))
			.distinct()
			.filter(NonTerminal.class::isInstance)
			.map(nt -> (NonTerminal<T>)nt)
			.toList();

		final List<Terminal<T>> terminals = symbols.stream()
			.map(rule -> rule.at(null))
			.distinct()
			.filter(Terminal.class::isInstance)
			.map(nt -> (Terminal<T>)nt)
			.toList();

		final Set<Symbol<T>> allSymbols = new HashSet<>(symbols);
		allSymbols.addAll(nonTerminals);
		allSymbols.addAll(terminals);

		return new Cfg<>(
			nonTerminals.stream()
				.map(s -> (NonTerminal<T>)select(s, allSymbols))
				.toList(),
			terminals.stream()
				.map(s -> (Terminal<T>)select(s, allSymbols))
				.toList(),
			rules0.stream()
				.map(r -> rebuild(r, allSymbols))
				.toList(),
			(NonTerminal<T>)select(rules0.getFirst().start(), allSymbols)
		);
	}
Create a grammar object with the given rules. Duplicated rules are merged
into one rule. The <em>start</em> symbol of the first rule is chosen as
the start symbol of the created CFG

@param rules the rules the grammar consists of
@throws IllegalArgumentException if the rule list is empty or a rule is
        defined more than once, the start symbol points to a missing rule
        or the rules uses symbols not defined in the list of
        {@link #nonTerminals()} or {@link #terminals()}
@throws NullPointerException if the list of rules is {@code null}
jenetics.ext/src/main/java/io/jenetics/ext/grammar/Cfg.java