Jelajahi Sumber

feat: implementation

Sam Jaffe 3 bulan lalu
induk
melakukan
ae138c29f3

+ 74 - 0
Makefile

@@ -0,0 +1,74 @@
+SHELL=/bin/bash -o pipefail
+INTERACTIVE:=$(shell [ -t 0 ] && echo 1)
+
+ifdef INTERACTIVE
+CLEAN_ANSI=
+else
+CLEAN_ANSI=| sed -r 's/\x1B\[([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGK]//g'
+endif
+
+CXX := clang++
+
+CXX_FLAGS := -Wall -Wextra -Werror -std=c++20 \
+	     -isystem include/
+
+LD_FLAGS := -L/opt/homebrew/lib
+
+SOURCE_DIR := src/
+TEST_DIR := tests/
+INCLUDE_DIR := include/
+
+HEADERS := $(shell find $(INCLUDE_DIR) -name *.h)
+
+CPP_SOURCES := $(wildcard $(SOURCE_DIR)*.cxx)
+CPP_OBJECTS := $(patsubst %.cxx, .build/%.o, $(CPP_SOURCES))
+LIBRARY := abnf-parser.a
+LIBRARY := $(patsubst %, .build/lib/%, $(LIBRARY))
+
+TEST_HEADERS := $(wildcard $(TEST_DIR)*.h)
+TEST_SOURCES := $(wildcard $(TEST_DIR)*.cxx)
+TEST_OBJECTS := $(patsubst %.cxx, .build/%.o, $(TEST_SOURCES))
+TEST_BINARIES := .build/bin/selfvalidate
+EXECUTE_TESTS := $(patsubst %, %.run, $(TEST_BINARIES))
+
+all: run-test
+
+debug: CXX_FLAGS := $(CXX_FLAGS) -g -fsanitize=address
+debug: LD_FLAGS := $(LD_FLAGS) -fsanitize=address
+debug: test
+
+clean:
+	@ rm -rf .build
+
+build: $(LIBRARY)
+
+test: build
+test: $(TEST_BINARIES)
+
+run-test: test
+run-test: $(EXECUTE_TESTS)
+
+# Actual Definitions (non-phony)
+.build/src/%.o: src/%.cxx $(HEADERS)
+	@ mkdir -p .build/src
+	$(CXX) $(CXX_FLAGS) -c $< -o $@
+
+.build/tests/%.o: tests/%.cxx $(HEADERS) $(TEST_HEADERS)
+	@ mkdir -p .build/tests
+	$(CXX) $(CXX_FLAGS) -c $< -o $@
+
+
+.build/lib/abnf-parser.a: $(CPP_OBJECTS)
+	@ mkdir -p .build/lib
+	ar rc $@ $^
+
+
+.build/bin/selfvalidate: .build/tests/selfvalidate_test.o $(LIBRARY)
+	@ mkdir -p .build/bin
+	@ rm -f $@.done
+	$(CXX) $^ -o $@ $(LD_FLAGS) -lgmock -lgtest
+
+
+.build/bin/selfvalidate.run: .build/bin/selfvalidate
+	.build/bin/selfvalidate
+	@ touch $@

+ 5 - 0
include/abnf/abnf.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#include <abnf/forward.h>
+#include <abnf/grammar.h>
+#include <abnf/literals.h>

+ 16 - 0
include/abnf/detail/iless.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include <_strings.h>
+#include <string_view>
+
+namespace abnf::detail {
+struct iless {
+  using is_transparent = void;
+
+  bool operator()(std::string_view lhs, std::string_view rhs) const {
+    int const cmp =
+        strncasecmp(lhs.data(), rhs.data(), std::min(lhs.size(), rhs.size()));
+    return cmp < 0 || (cmp == 0 && lhs.size() < rhs.size());
+  }
+};
+}

+ 6 - 0
include/abnf/detail/overload.h

@@ -0,0 +1,6 @@
+#pragma once
+
+template <typename... Ts> struct overload : Ts... {
+  overload(Ts... ts) : Ts(ts)... {}
+  using Ts::operator()...;
+};

+ 39 - 0
include/abnf/forward.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include <iosfwd>
+#include <type_traits>
+#include <variant>
+
+namespace abnf::detail {
+template <typename, typename> struct in_variant : std::false_type {};
+template <typename T, typename... Ts>
+struct in_variant<T, std::variant<Ts...>>
+    : std::disjunction<std::is_same<T, Ts>...> {};
+}
+
+namespace abnf {
+struct literal;
+struct reference;
+struct char_range;
+struct repeated;
+struct one_of;
+
+struct rule;
+using rule_part =
+    std::variant<literal, char_range, reference, repeated, one_of>;
+
+class grammar;
+
+grammar parse(std::istream & in);
+grammar parse(std::istream && in);
+
+std::ostream & operator<<(std::ostream & os, rule const & rule);
+std::ostream & operator<<(std::ostream & os, grammar const & grammar);
+
+template <typename T>
+concept rule_part_like = detail::in_variant<T, rule_part>::value;
+
+template <typename T>
+concept rule_like =
+    std::is_constructible_v<rule_part, T> || std::is_same_v<rule, T>;
+}

+ 110 - 0
include/abnf/grammar.h

@@ -0,0 +1,110 @@
+#pragma once
+
+#include <initializer_list>
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <abnf/detail/iless.h>
+#include <abnf/forward.h>
+
+namespace abnf {
+
+struct rule {
+  rule() = default;
+  rule(rule_part part);
+  rule(rule_part_like auto && part);
+  rule(std::initializer_list<rule_part> rules);
+
+  friend bool operator==(rule const & lhs, rule const & rhs);
+
+  std::vector<rule_part> rules;
+};
+
+struct literal {
+  explicit literal(std::string_view lit);
+
+  friend bool operator==(literal const &, literal const &) = default;
+
+  std::string value;
+};
+
+struct reference {
+  explicit reference(std::string_view ref);
+
+  friend bool operator==(reference const &, reference const &) = default;
+
+  std::string value;
+};
+
+struct char_range { // TODO: UTF8/Codepoint handling
+  char_range() = default;
+  char_range(int val) : first(val), last(val) {}
+  char_range(int first, int last) : first(first), last(last) {}
+
+  friend bool operator==(char_range const &, char_range const &) = default;
+
+  int first;
+  int last;
+};
+
+struct repeated {
+  friend bool operator==(repeated const &, repeated const &) = default;
+
+  rule rule;
+  size_t min = 0;
+  size_t max = std::numeric_limits<size_t>::max();
+};
+
+struct one_of {
+  explicit one_of(rule_part part) : rules{part} {}
+  one_of(std::initializer_list<rule> rules) : rules(rules) {}
+  one_of(std::initializer_list<rule_part> rules)
+      : rules(rules.begin(), rules.end()) {}
+
+  friend bool operator==(one_of const &, one_of const &) = default;
+
+  std::vector<rule> rules;
+};
+
+inline bool operator==(rule const & lhs, rule const & rhs) {
+  return lhs.rules == rhs.rules;
+}
+
+class grammar {
+public:
+  using rule_store = std::map<std::string, rule, detail::iless>;
+
+private:
+  struct satisfies_result;
+
+public:
+  grammar(std::string_view name, rule base_rule, rule_store rules = {})
+      : name_(name), base_rule_(std::move(base_rule)),
+        rules_(std::move(rules)) {}
+
+  bool satisfies(std::string_view text) const;
+
+private:
+  grammar() = default;
+
+  satisfies_result satisfies(std::string_view & text, rule const & rule) const;
+  satisfies_result satisfies(std::string_view text, auto const & lit) const;
+
+  friend bool operator==(grammar const &, grammar const &) = default;
+
+private:
+  friend std::ostream & operator<<(std::ostream &, grammar const &);
+  friend grammar parse(std::istream &);
+
+  std::string name_;
+  rule base_rule_;
+  // TODO: Default Rules of ABNF
+  static rule_store const s_default_rules_;
+  rule_store rules_;
+};
+
+rule::rule(rule_part_like auto && part)
+    : rule(rule_part(std::forward<decltype(part)>(part))) {}
+}

+ 44 - 0
include/abnf/literals.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include <abnf/forward.h>
+#include <abnf/grammar.h>
+#include <cstddef>
+
+namespace abnf {
+char_range parse_char_range(std::string_view);
+}
+
+namespace abnf::literals {
+inline literal operator""_lit(char const * const str, size_t len) {
+  return literal{{str, len}};
+}
+
+inline reference operator""_ref(char const * const str, size_t len) {
+  return reference{{str, len}};
+}
+
+inline char_range operator""_range(char const * const str, size_t len) {
+  return parse_char_range({str, len});
+}
+
+inline one_of operator/(one_of lhs, rule rhs) {
+  lhs.rules.push_back(std::move(rhs));
+  return lhs;
+}
+
+inline one_of operator/(rule_like auto const & lhs,
+                        rule_like auto const & rhs) {
+  return one_of{rule{lhs}, rule{rhs}};
+}
+
+inline rule operator+(rule lhs, rule_part const & rhs) {
+  lhs.rules.push_back(rhs);
+  return lhs;
+}
+
+inline rule operator+(rule_part_like auto const & lhs, rule_part const & rhs) {
+  return rule{lhs, rhs};
+}
+
+inline repeated operator*(rule v) { return repeated{v}; }
+}

+ 132 - 0
src/grammar.cxx

@@ -0,0 +1,132 @@
+#include <abnf/grammar.h>
+
+#include <initializer_list>
+
+#include <abnf/literals.h>
+
+struct abnf::grammar::satisfies_result {
+  satisfies_result(size_t read) : read(read), valid(true) {}
+  satisfies_result(bool valid) : valid(valid) {}
+
+  explicit operator bool() const { return valid; }
+
+  satisfies_result & operator+=(satisfies_result other) {
+    read += other.read;
+    valid = valid || other.valid;
+    return *this;
+  }
+
+  size_t read = 0;
+  bool valid = false;
+};
+
+namespace abnf {
+using namespace abnf::literals;
+
+grammar::rule_store const grammar::s_default_rules_{
+    {"ALPHA", "%x41-5A"_range / "%x61-7A"_range},
+    {"DIGIT", "%x30-39"_range},
+    {"HEXDIG", "DIGIT"_ref / "%x41-46"_range / "%x61-66"_range},
+    {"DQUOTE", "%x22"_range},
+    {"SP", "%x20"_range},
+    {"HTAB", "%x09"_range},
+    {"WSP", "SP"_ref / "HTAB"_ref},
+    {"LWSP", *("WSP"_ref / ("CLRF"_ref + "WSP"_ref))},
+    {"VCHAR", "%x21–7E"_range},
+    {"CHAR", "%x01–7F"_range},
+    {"OCTET", "%x00–FF"_range},
+    {"CTL", "%x00–1F"_range / "%x7F"_range},
+    {"CR", "%x0D"_range},
+    {"LF", "%x0A"_range},
+    {"CRLF", "CR"_ref + "LF"_ref},
+    {"BIT", "0"_lit / "1"_lit},
+};
+
+rule::rule(rule_part part) : rules{part} {}
+rule::rule(std::initializer_list<rule_part> rules) : rules(rules) {}
+
+literal::literal(std::string_view lit) {
+  if (lit.starts_with('"')) {
+    lit.remove_prefix(1);
+    lit.remove_suffix(1);
+  }
+  value = std::string(lit);
+}
+
+reference::reference(std::string_view ref) {
+  if (ref.starts_with('<')) {
+    ref.remove_prefix(1);
+    ref.remove_suffix(1);
+  }
+  value = std::string(ref);
+}
+
+template <>
+auto grammar::satisfies(std::string_view text, literal const & lit) const
+    -> satisfies_result {
+  if (text.starts_with(lit.value)) { return lit.value.size(); }
+  return false;
+}
+
+template <>
+auto grammar::satisfies(std::string_view text, char_range const & rng) const
+    -> satisfies_result {
+  if (text[0] >= rng.first && text[0] <= rng.last) { return 1UL; }
+  return false;
+}
+
+template <>
+auto grammar::satisfies(std::string_view text, reference const & ref) const
+    -> satisfies_result {
+  if (auto it = s_default_rules_.find(ref.value);
+      it != s_default_rules_.end()) {
+    return satisfies(text, it->second);
+  }
+  return satisfies(text, rules_.at(ref.value));
+}
+
+template <>
+auto grammar::satisfies(std::string_view text, repeated const & rep) const
+    -> satisfies_result {
+  size_t count = 0;
+  satisfies_result result{true};
+  while ((result += satisfies(text, rep.rule))) {
+    ++count;
+  }
+  if (count >= rep.min && count <= rep.max) { return result.read; }
+  return false;
+}
+
+template <>
+auto grammar::satisfies(std::string_view text, one_of const & one_of) const
+    -> satisfies_result {
+  for (rule const & r : one_of.rules) {
+    std::string_view tmp = text;
+    if (satisfies_result result = satisfies(tmp, r)) { return result; }
+  }
+  return false;
+}
+
+auto grammar::satisfies(std::string_view & text, rule const & rule) const
+    -> satisfies_result {
+  auto visitor = [this, &text](auto const & part) {
+    return satisfies(text, part);
+  };
+
+  satisfies_result result{true};
+
+  for (rule_part const & part : rule.rules) {
+    if (satisfies_result part_result = std::visit(visitor, part);
+        result += part_result) {
+      text.remove_prefix(part_result.read);
+    } else {
+      return result;
+    }
+  }
+  return result;
+}
+
+bool grammar::satisfies(std::string_view text) const {
+  return satisfies(text, base_rule_).valid;
+}
+}

+ 53 - 0
src/io.cxx

@@ -0,0 +1,53 @@
+#include <ios>
+#include <iostream>
+
+#include <abnf/detail/overload.h>
+#include <abnf/grammar.h>
+#include <utility>
+
+namespace abnf {
+std::ostream & operator<<(std::ostream & os, rule const & rule) {
+  overload visitor{
+      [&os](literal const & l) { os << '"' << l.value << '"'; },
+      [&os](reference const & r) { os << '<' << r.value << '>'; },
+      [&os](char_range const & r) {
+        os << "%x" << std::hex << r.first << std::dec;
+        if (r.first != r.last) { os << '-' << std::hex << r.last << std::dec; }
+      },
+      [&os](repeated const & r) {
+        if (r.min == 0 && r.max == 1) {
+          os << '[' << r.rule << ' ' << ']';
+        } else if (r.min == r.max) {
+          if (r.min != 1) { os << r.min; }
+          os << '(' << r.rule << ' ' << ')';
+        } else if (r.max == repeated{}.max) {
+          if (r.min != 0) { os << r.min; }
+          os << '*' << '(' << r.rule << ' ' << ')';
+        } else if (r.min == 0) {
+          os << '*' << r.max << '(' << r.rule << ' ' << ')';
+        } else {
+          os << r.min << '*' << r.max << '(' << r.rule << ' ' << ')';
+        }
+      },
+      [&os](one_of const & o) {
+        std::string div = "";
+        for (auto & sub : o.rules) {
+          os << std::exchange(div, " /") << sub;
+        }
+      },
+  };
+  for (auto const & part : rule.rules) {
+    os << " ";
+    std::visit(visitor, part);
+  }
+  return os;
+}
+
+std::ostream & operator<<(std::ostream & os, grammar const & grammar) {
+  os << grammar.name_ << " =" << grammar.base_rule_ << "\n";
+  for (auto const & [name, rule] : grammar.rules_) {
+    os << "\n" << name << " =" << rule << "\n";
+  }
+  return os;
+}
+}

+ 137 - 0
src/parser.cxx

@@ -0,0 +1,137 @@
+#include <cassert>
+#include <cctype>
+#include <charconv>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <variant>
+
+#include <abnf/forward.h>
+#include <abnf/grammar.h>
+
+namespace abnf {
+grammar parse(std::istream && in) { return parse(in); }
+
+static void append(rule & rule, rule_part const & part, bool is_one_of) {
+  if (rule.rules.empty()) {
+    rule.rules.push_back(is_one_of ? one_of(part) : part);
+  } else if (not is_one_of) {
+    rule.rules.push_back(part);
+  } else if (auto * of = std::get_if<one_of>(&rule.rules.back())) {
+    of->rules.push_back(part);
+  } else {
+    rule.rules.back() = one_of{rule.rules.back(), part};
+  }
+}
+
+static repeated parse_repeated(std::string_view token) {
+  if (token[0] == '[') { return {.min = 0, .max = 1}; }
+  if (token[0] == '(') {
+    // TODO: Can I just inline this when is_one_of is false?
+    return {.min = 1, .max = 1};
+  }
+  repeated rval;
+  size_t idx = 0;
+  if (not token.starts_with('*')) {
+    rval.min = std::stoull(std::string(token), &idx);
+    token.remove_prefix(idx);
+  }
+  if (not token.starts_with('*')) {
+    rval.max = rval.min;
+    return rval;
+  }
+
+  token.remove_prefix(1);
+  if (not token.empty() && std::strchr("123456789", token[0])) {
+    rval.max = std::stoull(std::string(token), &idx);
+    token.remove_prefix(idx);
+  }
+  return rval;
+}
+
+char_range parse_char_range(std::string_view token) {
+  char_range rval;
+  token.remove_prefix(2);
+  char const * const last = token.end();
+  auto [end, ec] = std::from_chars(token.data(), last, rval.first, 16);
+  if (*end == '-') {
+    ec = std::from_chars(end + 1, last, rval.last, 16).ec;
+  } else {
+    rval.last = rval.first;
+  }
+
+  return rval;
+}
+
+static std::string parse_rule(std::istream & in, std::string const & name,
+                              rule & rule, bool is_one_of = false) {
+  bool expecting_return = false;
+  std::string token;
+
+  while (in >> token) {
+    if (std::strchr("])", token[0])) {
+      return token; // End Sub-Expression
+    }
+
+    static constexpr char const s_repeated_chars[] = "0123456789[(*";
+    if (std::strchr(s_repeated_chars, token[0])) {
+      repeated tmp = parse_repeated(token);
+      if (auto pos = token.find_first_not_of(s_repeated_chars);
+          pos != std::string::npos) {
+        std::stringstream ss(token.substr(pos));
+        parse_rule(ss, name, tmp.rule);
+      } else {
+        parse_rule(in, name, tmp.rule);
+      }
+      append(rule, tmp, is_one_of);
+    } else if (token.starts_with("%x")) {
+      append(rule, parse_char_range(token), is_one_of);
+    } else if (token.starts_with('"')) {
+      append(rule, literal{token}, is_one_of);
+    } else if (token == "/") {
+      // See Below
+    } else if (token == ";") {
+      std::getline(in, token); // Discard the comment
+    } else if (token.starts_with('<') || std::isalpha(token[0])) {
+      if (expecting_return) { return token; }
+      append(rule, reference{token}, is_one_of);
+    }
+
+    expecting_return = false;
+    is_one_of = (token == "/");
+    if (std::strchr("\r\n", in.peek())) { expecting_return = true; }
+  }
+
+  return "";
+}
+
+grammar parse(std::istream & in) {
+  grammar rval;
+  std::string name;
+  rule rule;
+
+  auto push_rule = [&rval, &name, &rule]() {
+    if (name.empty() || not std::isalpha(name[0])) {
+      // PASS
+    } else if (rval.name_.empty()) {
+      rval.name_ = std::move(name);
+      rval.base_rule_ = std::move(rule);
+    } else {
+      rval.rules_.insert_or_assign(std::move(name), std::move(rule));
+    }
+  };
+
+  bool one_of = false;
+  std::string token;
+  in >> name;
+  in >> token; // =
+  while (not(token = parse_rule(in, name, rule, one_of)).empty()) {
+    if (token != name) { push_rule(); }
+    name = token;
+    in >> token; // = OR /=
+    if ((one_of = (token == "/="))) { rule = rval.rules_[name]; }
+  }
+  push_rule();
+  return rval;
+}
+}

+ 81 - 0
tests/resources/uri.abnf

@@ -0,0 +1,81 @@
+   URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+
+   hier-part     = "//" authority path-abempty
+                 / path-absolute
+                 / path-rootless
+                 / path-empty
+
+   URI-reference = URI / relative-ref
+
+   absolute-URI  = scheme ":" hier-part [ "?" query ]
+
+   relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
+
+   relative-part = "//" authority path-abempty
+                 / path-absolute
+                 / path-noscheme
+                 / path-empty
+
+   scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+
+   authority     = [ userinfo "@" ] host [ ":" port ]
+   userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
+   host          = IP-literal / IPv4address / reg-name
+   port          = *DIGIT
+
+   IP-literal    = "[" ( IPv6address / IPvFuture  ) "]"
+
+   IPvFuture     = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+
+   IPv6address   =                            6( h16 ":" ) ls32
+                 /                       "::" 5( h16 ":" ) ls32
+                 / [               h16 ] "::" 4( h16 ":" ) ls32
+                 / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+                 / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+                 / [ *3( h16 ":" ) h16 ] "::"    h16 ":"   ls32
+                 / [ *4( h16 ":" ) h16 ] "::"              ls32
+                 / [ *5( h16 ":" ) h16 ] "::"              h16
+                 / [ *6( h16 ":" ) h16 ] "::"
+
+   h16           = 1*4HEXDIG
+   ls32          = ( h16 ":" h16 ) / IPv4address
+   IPv4address   = dec-octet "." dec-octet "." dec-octet "." dec-octet
+
+   dec-octet     = DIGIT                 ; 0-9
+                 / %x31-39 DIGIT         ; 10-99
+                 / "1" 2DIGIT            ; 100-199
+                 / "2" %x30-34 DIGIT     ; 200-249
+                 / "25" %x30-35          ; 250-255
+
+   reg-name      = *( unreserved / pct-encoded / sub-delims )
+
+   path          = path-abempty    ; begins with "/" or is empty
+                 / path-absolute   ; begins with "/" but not "//"
+                 / path-noscheme   ; begins with a non-colon segment
+                 / path-rootless   ; begins with a segment
+                 / path-empty      ; zero characters
+
+   path-abempty  = *( "/" segment )
+   path-absolute = "/" [ segment-nz *( "/" segment ) ]
+   path-noscheme = segment-nz-nc *( "/" segment )
+   path-rootless = segment-nz *( "/" segment )
+   path-empty    = 0<pchar>
+
+   segment       = *pchar
+   segment-nz    = 1*pchar
+   segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
+                 ; non-zero-length segment without any colon ":"
+
+   pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+
+   query         = *( pchar / "/" / "?" )
+
+   fragment      = *( pchar / "/" / "?" )
+
+   pct-encoded   = "%" HEXDIG HEXDIG
+
+   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
+   reserved      = gen-delims / sub-delims
+   gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
+                 / "*" / "+" / "," / ";" / "="

+ 27 - 0
tests/selfvalidate_test.cxx

@@ -0,0 +1,27 @@
+#include "abnf/forward.h"
+#include <fstream>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <abnf/abnf.h>
+
+using testing::Eq;
+
+inline std::filesystem::path resource_dir() {
+  return std::filesystem::path(__FILE__).parent_path() / "resources";
+}
+
+TEST(ABNFParserTest, LoadsURI) {
+  std::ifstream in(resource_dir() / "uri.abnf");
+  abnf::grammar grammar = abnf::parse(in);
+  std::stringstream ss;
+  ss << grammar;
+
+  EXPECT_THAT(abnf::parse(ss), Eq(grammar));
+}
+
+int main(int argc, char ** argv) {
+  testing::InitGoogleMock(&argc, argv);
+  return RUN_ALL_TESTS();
+}