1 #pragma once
2 
3 #include <cmath>
4 #include <locale>
5 
6 #include "rapidcheck/seq/Transform.h"
7 #include "rapidcheck/seq/Create.h"
8 
9 namespace rc {
10 namespace shrink {
11 namespace detail {
12 
13 template <typename T>
14 class TowardsSeq {
15 public:
16   using UInt = typename std::make_unsigned<T>::type;
17 
TowardsSeq(T value,T target)18   TowardsSeq(T value, T target)
19       : m_value(value)
20       , m_diff((target < value) ? (value - target) : (target - value))
21       , m_down(target < value) {}
22 
operator ()()23   Maybe<T> operator()() {
24     if (m_diff == 0) {
25       return Nothing;
26     }
27 
28     T ret = m_down ? (m_value - m_diff) : (m_value + m_diff);
29     m_diff /= 2;
30     return ret;
31   }
32 
33 private:
34   T m_value;
35   UInt m_diff;
36   bool m_down;
37 };
38 
39 template <typename Container>
40 class RemoveChunksSeq {
41 public:
42   template <typename ContainerArg>
RemoveChunksSeq(ContainerArg && elements)43   explicit RemoveChunksSeq(ContainerArg &&elements)
44       : m_elements(std::forward<Container>(elements))
45       , m_start(0)
46       , m_size(m_elements.size()) {}
47 
operator ()()48   Maybe<Container> operator()() {
49     if (m_size == 0) {
50       return Nothing;
51     }
52 
53     Container elements;
54     elements.reserve(m_elements.size() - m_size);
55     const auto start = begin(m_elements);
56     const auto fin = end(m_elements);
57     elements.insert(end(elements), start, start + m_start);
58     elements.insert(end(elements), start + m_start + m_size, fin);
59 
60     if ((m_size + m_start) >= m_elements.size()) {
61       m_size--;
62       m_start = 0;
63     } else {
64       m_start++;
65     }
66     return elements;
67   }
68 
69 private:
70   Container m_elements;
71   std::size_t m_start;
72   std::size_t m_size;
73 };
74 
75 template <typename Container, typename Shrink>
76 class EachElementSeq {
77 public:
78   using T = typename std::result_of<Shrink(
79       typename Container::value_type)>::type::ValueType;
80 
81   template <typename ContainerArg, typename ShrinkArg>
EachElementSeq(ContainerArg && elements,ShrinkArg && shrink)82   explicit EachElementSeq(ContainerArg &&elements, ShrinkArg &&shrink)
83       : m_elements(std::forward<Container>(elements))
84       , m_shrink(std::forward<ShrinkArg>(shrink))
85       , m_i(0) {}
86 
operator ()()87   Maybe<Container> operator()() {
88     auto value = next();
89     if (!value) {
90       return Nothing;
91     }
92 
93     auto elements = m_elements;
94     elements[m_i - 1] = std::move(*value);
95     return elements;
96   }
97 
98 private:
next()99   Maybe<T> next() {
100     while (true) {
101       auto value = m_shrinks.next();
102       if (value) {
103         return value;
104       }
105 
106       if (m_i >= m_elements.size()) {
107         return Nothing;
108       }
109 
110       m_shrinks = m_shrink(m_elements[m_i++]);
111     }
112   }
113 
114   Container m_elements;
115   Shrink m_shrink;
116   Seq<T> m_shrinks;
117   std::size_t m_i;
118 };
119 
120 template <typename T>
integral(T value,std::true_type)121 Seq<T> integral(T value, std::true_type) {
122   // The check for > min() is important since -min() == min() and we never
123   // want to include self
124   if ((value < 0) && (value > std::numeric_limits<T>::min())) {
125     // Drop the zero from towards and put that before the negation value
126     // so we don't have duplicate zeroes
127     return seq::concat(seq::just<T>(static_cast<T>(0), static_cast<T>(-value)),
128                        seq::drop(1, shrink::towards<T>(value, 0)));
129   }
130 
131   return shrink::towards<T>(value, 0);
132 }
133 
134 template <typename T>
integral(T value,std::false_type)135 Seq<T> integral(T value, std::false_type) {
136   return shrink::towards<T>(value, 0);
137 }
138 
139 } // namespace detail
140 
141 template <typename Container>
removeChunks(Container elements)142 Seq<Container> removeChunks(Container elements) {
143   return makeSeq<detail::RemoveChunksSeq<Container>>(std::move(elements));
144 }
145 
146 template <typename Container, typename Shrink>
eachElement(Container elements,Shrink shrink)147 Seq<Container> eachElement(Container elements, Shrink shrink) {
148   return makeSeq<detail::EachElementSeq<Container, Shrink>>(std::move(elements),
149                                                             std::move(shrink));
150 }
151 
152 template <typename T>
towards(T value,T target)153 Seq<T> towards(T value, T target) {
154   return makeSeq<detail::TowardsSeq<T>>(value, target);
155 }
156 
157 template <typename T>
integral(T value)158 Seq<T> integral(T value) {
159   return detail::integral(value, std::is_signed<T>());
160 }
161 
162 template <typename T, typename>
163 Seq<T> integral(T value);
164 
165 template <typename T>
real(T value)166 Seq<T> real(T value) {
167   std::vector<T> shrinks;
168 
169   if (std::abs(value) > 0) {
170     shrinks.push_back(T(0.0));
171   }
172 
173   if (value < 0) {
174     shrinks.push_back(-value);
175   }
176 
177   T truncated = std::trunc(value);
178   if (std::abs(truncated) < std::abs(value)) {
179     shrinks.push_back(truncated);
180   }
181 
182   return seq::fromContainer(shrinks);
183 }
184 
boolean(bool value)185 Seq<bool> boolean(bool value) { return value ? seq::just(false) : Seq<bool>(); }
186 
187 template <typename T>
character(T value)188 Seq<T> character(T value) {
189   const auto &locale = std::locale::classic();
190   auto shrinks = seq::cast<T>(seq::concat(
191       seq::fromContainer(std::string("abc")),
192       // TODO this seems a bit hacky
193       std::islower(static_cast<char>(value), locale)
194           ? Seq<char>()
195           : seq::just(static_cast<char>(std::tolower(value, locale))),
196       seq::fromContainer(std::string("ABC123 \n"))));
197 
198   return seq::takeWhile(std::move(shrinks), [=](T x) { return x != value; });
199 }
200 
201 } // namespace shrink
202 } // namespace rc
203