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 -- 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);
        }