text: // This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
  // the main distribution directory for license terms and copyright or visit
  // https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
  
  #pragma once
  
  #include "caf/config.hpp"
  #include "caf/detail/parser/add_ascii.hpp"
  #include "caf/detail/parser/chars.hpp"
  #include "caf/detail/parser/read_ipv6_address.hpp"
  #include "caf/detail/scope_guard.hpp"
  #include "caf/pec.hpp"
  #include "caf/uri.hpp"
  
  CAF_PUSH_UNUSED_LABEL_WARNING
  
  #include "caf/detail/parser/fsm.hpp"
  
  namespace caf::detail::parser {
  
  //   foo://example.com:8042/over/there?name=ferret#nose
  //   \_/   \______________/\_________/ \_________/ \__/
  //    |           |            |            |        |
  // scheme     authority       path        query   fragment
  //    |   _____________________|__
  //   / \ /                        \.
  //   urn:example:animal:ferret:nose
  
  // Unlike our other parsers, the URI parsers only check for validity and
  // generate ranges for the subcomponents. URIs can't have linebreaks, so we can
  // safely keep track of the position by looking at the column.
  
  template <class State>
  void read_uri_percent_encoded(State& ps, std::string& str) {
    uint8_t char_code = 0;
    auto g = make_scope_guard([&] {
      if (ps.code <= pec::trailing_character)
        str += static_cast<char>(char_code);
    });
    // clang-format off
    start();
    state(init) {
      transition(read_nibble, hexadecimal_chars, add_ascii<16>(char_code, ch))
    }
    state(read_nibble) {
      transition(done, hexadecimal_chars, add_ascii<16>(char_code, ch))
    }
    term_state(done) {
      // nop
    }
    fin();
    // clang-format on
  }
  
  inline bool uri_unprotected_char(char c) noexcept {
    // Consider valid characters not explicitly stated as reserved as unreserved.
    return isprint(c) && !in_whitelist(":/?#[]@!$&'()*+,;=<>", c);
  }
  
  // clang-format off
  #define read_next_char(next_state, dest)                                       \
    transition(next_state, uri_unprotected_char, dest += ch)                     \
    fsm_transition(read_uri_percent_encoded(ps, dest), next_state, '%')
  // clang-format on
  
  template <class State, class Consumer>
  void read_uri_query(State& ps, Consumer&& consumer) {
    // Local variables.
    uri::query_map result;
    std::string key;
    std::string value;
    // Utility functions.
    auto take_str = [&](std::string& str) {
      using std::swap;
      std::string res;
      swap(str, res);
      return res;
    };
    auto push = [&] { result.emplace(take_str(key), take_str(value)); };
    // Call consumer on exit.
    auto g = make_scope_guard([&] {
      if (ps.code <= pec::trailing_character)
        consumer.query(std::move(result));
    });
    // clang-format off
    start();
    // Query may be empty.
    term_state(init) {
      read_next_char(read_key, key)
    }
    state(read_key) {
      read_next_char(read_key, key)
      transition(read_value, '=')
    }
    term_state(read_value, push()) {
      read_next_char(read_value, value)
      transition(init, '&', push())
    }
    fin();
    // clang-format on
  }
  
  template <class State, class Consumer>
  void read_uri(State& ps, Consumer&& consumer) {
    // Local variables.
    std::string str;
    uint16_t port = 0;
    // Replaces `str` with a default constructed string to make sure we're never
    // operating on a moved-from string object.
    auto take_str = [&] {
      using std::swap;
      std::string res;
      swap(str, res);
      return res;
    };
    // Allowed character sets.
    auto path_char = [](char c) {
      return uri_unprotected_char(c) || c == '/' || c == ':';
    };
    // Utility setters for avoiding code duplication.
    auto set_path = [&] { consumer.path(take_str()); };
    auto set_host = [&] { consumer.host(take_str()); };
    auto set_userinfo = [&] { consumer.userinfo(take_str()); };
    // Consumer for reading IPv6 addresses.
    struct {
      Consumer& f;
      void value(ipv6_address addr) {
        f.host(addr);
      }
    } ip_consumer{consumer};
    // clang-format off
    start();
    state(init) {
      epsilon(read_scheme)
    }
    state(read_scheme) {
      read_next_char(read_scheme, str)
      transition(have_scheme, ':', consumer.scheme(take_str()))
    }
    state(have_scheme) {
      transition(disambiguate_path, '/')
      read_next_char(read_path, str)
      fsm_transition(read_uri_percent_encoded(ps, str), read_path, '%')
    }
    // This state is terminal, because "file:/" is a valid URI.
    term_state(disambiguate_path, consumer.path("/")) {
      transition(start_authority, '/')
      epsilon(read_path, any_char, str += '/')
    }
    state(start_authority) {
      // A third '/' skips the authority, e.g., "file:///".
      transition(read_path, '/', str += '/')
      read_next_char(read_authority, str)
      fsm_transition(read_ipv6_address(ps, ip_consumer), await_end_of_ipv6, '[')
    }
    state(await_end_of_ipv6) {
      transition(end_of_ipv6_host, ']')
    }
    term_state(end_of_ipv6_host) {
      transition(start_port, ':')
      epsilon(end_of_authority)
    }
    term_state(end_of_host) {
      transition(start_port, ':', set_host())
      epsilon(end_of_authority, "/?#", set_host())
    }
    term_state(read_authority, set_host()) {
      read_next_char(read_authority, str)
      transition(start_host, '@', set_userinfo())
      transition(start_port, ':', set_host())
      epsilon(end_of_authority, "/?#", set_host())
    }
    state(start_host) {
      read_next_char(read_host, str)
      fsm_transition(read_ipv6_address(ps, ip_consumer), await_end_of_ipv6, '[')
    }
    term_state(read_host, set_host()) {
      read_next_char(read_host, str)
      transition(start_port, ':', set_host())
      epsilon(end_of_authority, "/?#", set_host())
    }
    state(start_port) {
      transition(read_port, decimal_chars, add_ascii<10>(port, ch))
    }
    term_state(read_port, consumer.port(port)) {
      transition(read_port, decimal_chars, add_ascii<10>(port, ch),
                 pec::integer_overflow)
      epsilon(end_of_authority, "/?#", consumer.port(port))
    }
    term_state(end_of_authority) {
      transition(read_path, '/')
      transition(start_query, '?')
      transition(read_fragment, '#')
    }
    term_state(read_path, set_path()) {
      transition(read_path, path_char, str += ch)
      fsm_transition(read_uri_percent_encoded(ps, str), read_path, '%')
      transition(start_query, '?', set_path())
      transition(read_fragment, '#', set_path())
    }
    term_state(start_query) {
      fsm_epsilon(read_uri_query(ps, consumer), end_of_query)
    }
    unstable_state(end_of_query) {
      transition(read_fragment, '#')
      epsilon(done)
    }
    term_state(read_fragment, consumer.fragment(take_str())) {
      read_next_char(read_fragment, str)
    }
    term_state(done) {
      // nop
    }
    fin();
    // clang-format on
  }
  
  } // namespace caf::detail::parser
  
  #include "caf/detail/parser/fsm_undef.hpp"
  
  CAF_POP_WARNINGS
  