// see LICENSE on insooth.github.io // parse key-value pairs concatenated with ampersand: a=b&c=d&u=v // look for selected keys #include #include #include // strlen #include // size_t #include #include // accumulate #include // make_optional, nullopt, optional #include // {remove,}extent, decay, add_const #include // pair using stringee = std::pair; // no trailing \0 using kv = std::pair; // key-value constexpr std::size_t N = 3; // max kv items in a string using parsed = std::array; // size of a token and its number chosen arbitrary using filter = std::array; // convert indices stored in stringee to a string part extracted from s template auto materialise(const char* const s, std::size_t n, const stringee& x) -> std::array { assert((n >= x.first) && (n >= x.second) && (x.second >= x.first)); std::array r = { 0 }; const std::size_t m = x.second - x.first; assert(N >= m); // fits into result? assert((x.first + m) <= n); for (std::size_t i = 0; i < m; ++i) { r[i] = s[x.first + i]; } return r; } // any of allowed keys matches? bool expected(std::decay_t> x, std::size_t n = std::extent_v) { constexpr filter ff = {{ {'f', 's', 't' } , {'s', 'n', 'd' } , {'3', 'r', 'd' } }}; assert(n <= ff.size()); return std::accumulate(ff.begin(), ff.end() , false , [x, n](bool r, const auto& f) { return r || (0 == std::memcmp(f, x, n)); } ); } // array -> (T*, N) bool expected(const std::array, std::extent_v>& x) { return expected(x.data(), x.size()); } // explode('=', explode('&', s)) parsed parse(const char* const s, std::size_t n) { [[maybe_unused]] constexpr auto max = std::numeric_limits::max(); assert(n < max); // TODO: fill this regardless the size of parsed (N) constexpr auto invalid = kv{stringee{max, max}, stringee{max, max}}; parsed p = {invalid, invalid, invalid}; bool is_key = true; // URI starts with a key kv current = invalid; auto& k = current.first; auto& v = current.second; for (std::size_t i = 0, item = 0; (i < n) && (item < p.size()); ++i) { if ((0 == i) || ('&' == s[i])) // new key-value pairs starts { assert(! is_key || (0 == i)); is_key = true; if (0 == i) // we start with a key { k.first = i; // start of a new kv pair } else { v.second = i; // end of value // next key-value pair if (expected(materialise>(s, n, k))) { p[item] = current; current = invalid; ++item; } k.first = i + 1; // start of a new kv pair } } else if ('=' == s[i]) // found a value { assert(true == is_key); is_key = false; k.second = i; // end of key v.first = i + 1; // start of a new value } else // collect key or value data { // +1: past-the-end element is a last one (like in iterators) if (is_key) { k.second = i + 1; } else { v.second = i + 1; }; } } return p; } #define INVALIDATE false const char* validate(const char* const s, std::size_t n) { return INVALIDATE ? nullptr : s; } void print(const parsed& ps, const char* const s, std::size_t n) { for (const auto& p : ps) { const auto& [k, v] = p; const auto valid = [s, n](std::size_t offset) { return offset <= n; }; // <=: past-the-end is the "last" iterator std::cout << ((valid(k.first) && valid(k.second) && (k.second >= k.first)) // >=: empty string case ? std::string(s + k.first, s + k.second) : "?") << " = " << ((valid(v.first) && valid(v.second) && (v.second >= v.first)) // >=: empty string case ? std::string(s + v.first, s + v.second) : "?") << '\n'; } } template // requires Callable> constexpr auto bind(std::optional&& v, F&& f, Ts&&... ts) { // CTAD to prevent from type nesting return v ? std::optional{std::forward(f)(*v, std::forward(ts)...)} : std::nullopt; } int main() { const char* const s = "fst=OK&xyz=NOK&snd=OK&3rd=OK&uvw=NOK"; const std::size_t n = std::strlen(s); // with error handling using monad auto m1 = bind( [s, n] { auto ss = validate(s, n); return (nullptr == ss) ? std::nullopt : std::make_optional(ss); }(), parse, n); if (m1) print(m1.value(), s, n); // cannot compose (? -- different "monad", cannot deduce type) std::cout << "\n\n"; #ifndef INVALIDATE print(parse(validate(s, n), n), s, n); #endif return 0; } /* // http://coliru.stacked-crooked.com/a/48b25de00aad3b6c g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out fst = OK snd = OK 3rd = OK */