internal static InstructAST Build(InstructAST left, InstructAST right)
        {
            // a -- a == <fail>
            if (left.Equals(right)) return AnyCharAST.Negate;

            // When b contains a, a -- b matches no codepoints
            if (right.Contains(left)) return AnyCharAST.Negate;

            // When a and b are disjoint, a -- b == a
            if (left.DisjointWith(right)) return left;

            // \p{Any} -- a == !a
            if (AnyCharAST.Instance.Equals(left)) return NotTestAST.Make(right);

            // !a -- !b == b -- a; !a -- b == !(a || b); a -- !b == a && b
            NotTestAST negLeft = left as NotTestAST;
            NotTestAST negRight = right as NotTestAST;
            if (negLeft != null)
            {
                if (negRight != null)
                    return DiffTestAST.Make(negRight.Argument, negLeft.Argument);
                else
                    return NotTestAST.Make(OrTestAST.Make(negLeft.Argument, right));
            }
            else if (negRight != null)
                return AndTestAST.Make(left, negRight.Argument);

            // (a -- b) -- c == a -- (b || c)
            DiffTestAST diffLeft = left as DiffTestAST;
            if (diffLeft != null)
                return DiffTestAST.Make(diffLeft.Left, OrTestAST.Make(diffLeft.Right, right));

            // (a || b) -- c == (a -- c) || (b -- c)
            OrTestAST orLeft = left as OrTestAST;
            if (orLeft != null)
                return OrTestAST.Make(DiffTestAST.Make(orLeft.Left, right), DiffTestAST.Make(orLeft.Right, right));

            // When a and b are overlapping ranges, a -- b == a with a gap
            CharRangeAST leftRange = left as CharRangeAST;
            if (leftRange != null)
            {
                int excludeMin = -1;
                int excludeMax = -1;

                OneCharAST rightChar = right as OneCharAST;
                if (rightChar != null && (leftRange.FoldsCase == rightChar.FoldsCase))
                {
                    excludeMin = excludeMax = rightChar.Character;
                }

                CharRangeAST rightRange = right as CharRangeAST;
                if (rightRange != null && (leftRange.FoldsCase == rightRange.FoldsCase))
                {
                    excludeMin = rightRange.Min;
                    excludeMax = rightRange.Max;
                }

                if (leftRange.Min <= excludeMax && excludeMin <= leftRange.Max)
                    return OrTestAST.Make(CharRangeAST.Make(leftRange.Min, excludeMin - 1, leftRange.FoldsCase),
                        CharRangeAST.Make(excludeMax + 1, leftRange.Max, leftRange.FoldsCase));
            }

            return new DiffTestAST(left, right);
        }
        internal static InstructAST Build(InstructAST left, InstructAST right)
        {
            // a && a == a
            if (left.Equals(right)) return left;

            // <empty> && a == a && <empty> == a if zero-width, <fail> otherwise
            if (left == EmptyAST.Instance)
                return right.IsZeroWidth ? right : AnyCharAST.Negate;
            if (right == EmptyAST.Instance)
                return left.IsZeroWidth ? left : AnyCharAST.Negate;

            // When a contains b, a && b == b
            if (left.Contains(right)) return right;

            // When b contains a, a && b == a
            if (right.Contains(left)) return left;

            // When a and b are disjoint, a && b matches nowhere
            if (left.DisjointWith(right)) return AnyCharAST.Negate;

            // !a && !b == !(a || b), !a && b == b -- a, a && !b == a -- b
            NotTestAST negLeft = left as NotTestAST;
            NotTestAST negRight = right as NotTestAST;
            if (negLeft != null)
            {
                if (negRight != null)
                    return NotTestAST.Make(OrTestAST.Make(negLeft.Argument, negRight.Argument));
                else
                    return DiffTestAST.Make(right, negLeft.Argument);
            }
            else if (negRight != null)
                return DiffTestAST.Make(left, negRight.Argument);

            // (a && b) && c == a && (b && c)
            AndTestAST andRight = right as AndTestAST;
            if (andRight != null)
                return AndTestAST.Make(AndTestAST.Make(left, andRight.Left), andRight.Right);

            // (a || b) && c == (a && c) || (b && c)
            OrTestAST orLeft = left as OrTestAST;
            if (orLeft != null)
                return OrTestAST.Make(AndTestAST.Make(orLeft.Left, right), AndTestAST.Make(orLeft.Right, right));

            // a && (b || c) == (a && b) || (a && c)
            if (!left.IsZeroWidth)
            {
                OrTestAST orRight = right as OrTestAST;
                if (orRight != null)
                    return OrTestAST.Make(AndTestAST.Make(left, orRight.Left), AndTestAST.Make(left, orRight.Right));
            }

            // Intersection of overlapping ranges is the overlap
            CharRangeAST leftRange = left as CharRangeAST;
            CharRangeAST rightRange = right as CharRangeAST;
            if (leftRange != null && rightRange != null && (leftRange.FoldsCase == rightRange.FoldsCase))
            {
                int low = Math.Max(leftRange.Min, rightRange.Min);
                int high = Math.Min(leftRange.Max, rightRange.Max);
                return CharRangeAST.Make(low, high, leftRange.FoldsCase);
            }

            return new AndTestAST(left, right);
        }
        internal static InstructAST Build(InstructAST left, InstructAST right)
        {
            // a || <empty> == <empty> || a == a
            if (EmptyAST.Instance == left) return right;
            if (EmptyAST.Instance == right) return left;

            // a || a == a
            if (left.Equals(right)) return left;

            // When a contains b, a || b == a
            if (left.Contains(right)) return left;

            // When b contains a, a || b == b
            if (right.Contains(left)) return right;

            // !a || !b == !(a && b), !a || b == !(a -- b), a || !b == !(b -- a)
            NotTestAST negLeft = left as NotTestAST;
            NotTestAST negRight = right as NotTestAST;
            if (negLeft != null)
            {
                if (negRight != null)
                    return NotTestAST.Make(AndTestAST.Make(negLeft.Argument, negRight.Argument));
                else
                    return NotTestAST.Make(DiffTestAST.Make(negLeft.Argument, right));
            }
            else if (negRight != null)
                return NotTestAST.Make(DiffTestAST.Make(negRight.Argument, left));

            // (a || b) || c == a || (b || c)
            OrTestAST orRight = right as OrTestAST;
            if (orRight != null)
                return OrTestAST.Make(OrTestAST.Make(left, orRight.Left), orRight.Right);

            // Merge overlapping and adjacent ranges
            OneCharAST oChar = null;
            CharRangeAST range = null;

            CharRangeAST leftRange = left as CharRangeAST;
            CharRangeAST rightRange = right as CharRangeAST;
            if (leftRange != null)
            {
                if (rightRange != null && (leftRange.FoldsCase == rightRange.FoldsCase) &&
                    rightRange.Max + 1 >= leftRange.Min && leftRange.Max + 1 >= rightRange.Min)
                {
                    int low = Math.Min(leftRange.Min, rightRange.Min);
                    int high = Math.Max(leftRange.Max, rightRange.Max);
                    return CharRangeAST.Make(low, high, leftRange.FoldsCase);
                }

                oChar = right as OneCharAST;
                range = leftRange;
            }
            else if (rightRange != null)
            {
                oChar = left as OneCharAST;
                range = rightRange;
            }

            // Merge a character adjacent to a range with the range
            if (oChar != null)
            {
                if (range.FoldsCase && oChar.FoldsCase)
                {
                    if (oChar.Character.ToCaseFold() == range.Min.ToCaseFold() - 1)
                        return CharRangeAST.Make(range.Min - 1, range.Max, true);
                    else if (oChar.Character.ToCaseFold() == range.Max.ToCaseFold() + 1)
                        return CharRangeAST.Make(range.Min, range.Max + 1, true);
                }
                if (!range.FoldsCase && !oChar.FoldsCase)
                {
                    if (oChar.Character == range.Min - 1)
                        return CharRangeAST.Make(range.Min - 1, range.Max, false);
                    else if (oChar.Character == range.Max + 1)
                        return CharRangeAST.Make(range.Min, range.Max + 1, false);
                }
            }

            return new OrTestAST(left, right);
        }