public ContextFreeGrammar(string[] nonterminals, string[] terminals,
                                  ProductionInfo[] rules, string starting)
        {
            if (HasDuplicates(nonterminals))
            {
                throw new ArgumentException("Nonterminal symbols cannot contain duplicates.");
            }
            if (HasDuplicates(terminals))
            {
                throw new ArgumentException("Terminal symbols cannot contain duplicates.");
            }
            if (HasDuplicates(rules))
            {
                throw new ArgumentException("Rules cannot contain duplicates.");
            }


            bool overlap = terminals.Intersect(nonterminals).Any();

            if (overlap)
            {
                throw new ArgumentException("Terminal symbols and nonterminal symbols are not disjoint.");
            }


            var symbolByString = new Dictionary <string, GrammarSymbol>(nonterminals.Length + terminals.Length);

            this.nonterminals = new GrammarSymbol[nonterminals.Length];
            for (int i = 0; i < nonterminals.Length; i++)
            {
                this.nonterminals[i] = new GrammarSymbol(nonterminals[i], false);
                symbolByString.Add(nonterminals[i], this.nonterminals[i]);
            }

            this.terminals = new GrammarSymbol[terminals.Length];
            for (int i = 0; i < terminals.Length; i++)
            {
                this.terminals[i] = new GrammarSymbol(terminals[i], true);
                symbolByString.Add(terminals[i], this.terminals[i]);
            }

            this.alphabet = (this.nonterminals).Concat(this.terminals).ToArray();


            int startingIndex = Array.IndexOf(nonterminals, starting);

            if (startingIndex < 0)
            {
                throw new ArgumentException("Start symbol must be a nonterminal symbol.");
            }

            this.starting = this.nonterminals[startingIndex];


            var ruleList = new List <Production>(rules.Length);

            foreach (var rule in rules)
            {
                if (!IsValid(rule))
                {
                    throw new ArgumentException("Production rule is invalid.");
                }

                GrammarSymbol original = symbolByString[rule.Original];

                var sentenceSymbols = new List <GrammarSymbol>();
                foreach (string s in rule.DirectDerivative)
                {
                    sentenceSymbols.Add(symbolByString[s]);
                }
                Sentence sentence = new Sentence(sentenceSymbols.ToArray());

                ruleList.Add(new Production(original, sentence));
            }
            this.rules = ruleList.ToArray();
        }