From d024b92d4e320eaf4ff10bdd73a7fc57264b0264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Wed, 24 Jul 2024 22:09:25 -0700 Subject: [PATCH 01/14] Bugfix: CUDA build --- CMakeLists.txt | 5 ----- Makefile | 7 +++++-- include/zeroerr/unittest.h | 14 ++++++++++---- src/CMakeLists.txt | 4 +++- test/CMakeLists.txt | 14 ++++++++------ zeroerr.hpp | 14 ++++++++++---- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 65b64646..7c63ab9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,11 +64,6 @@ option(BUILD_EXAMPLES "Build examples(ON, OFF)" OFF) option(BUILD_DOC "Build documentation" OFF) option(BUILD_TEST "Build unittest" OFF) -add_compile_options("$<$:/utf-8>") -add_compile_options("$<$:/utf-8>") -add_compile_options("$<$:-fstandalone-debug>") -# add_compile_options("$<$:-ftime-report>") - set(header_dirs ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/extension diff --git a/Makefile b/Makefile index 6e2bbbe1..4375d72f 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ all: linux windows build/linux/Makefile: Makefile mkdir -p build/linux cmake -B build/linux -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=11 \ - -DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=ON -DENABLE_FUZZING=ON \ + -DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=ON -DDISABLE_CUDA_BUILD=OFF -DENABLE_FUZZING=ON \ -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang linux: build/linux/Makefile @@ -14,13 +14,16 @@ linux: build/linux/Makefile build/windows/ZeroErr.sln: mkdir -p build/windows cmake.exe -B build/windows -S . \ - -DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -T host=x64 -A x64 + -DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DDISABLE_CUDA_BUILD=OFF -T host=x64 -A x64 windows: build/windows/ZeroErr.sln cmake.exe --build build/windows --config Debug -j `nproc` test: linux-test windows-test fuzz-test +cuda-test: windows + cd build/windows/test && ./Debug/cudatest.exe + fuzz-test: linux cd build/linux/test && ./unittest --testcase=fuzz_serialize.* diff --git a/include/zeroerr/unittest.h b/include/zeroerr/unittest.h index 5c71aa81..8c296ce5 100644 --- a/include/zeroerr/unittest.h +++ b/include/zeroerr/unittest.h @@ -66,8 +66,14 @@ struct TestCase; */ class TestContext { public: - unsigned passed = 0, warning = 0, failed = 0, skipped = 0; - unsigned passed_as = 0, warning_as = 0, failed_as = 0, skipped_as = 0; + unsigned passed = 0; + unsigned warning = 0; + unsigned failed = 0; + unsigned skipped = 0; + unsigned passed_as = 0; + unsigned warning_as = 0; + unsigned failed_as = 0; + unsigned skipped_as = 0; IReporter& reporter; @@ -239,7 +245,7 @@ class IReporter { * @brief Create the reporter object with the given name. * @param name The name of the reporter. Available reporters are: console, xml. * @param ut The unit test object that will be used to configure the test. - */ + */ static IReporter* create(const std::string& name, UnitTest& ut); IReporter(UnitTest& ut) : ut(ut) {} @@ -284,7 +290,7 @@ struct regReporter { * }); * test(a, b); * ``` - * + * * This will test the targetFunc with all the combinations of a and b, e.g. (1,4), (1,5), (1,6), * (2,4), (2,5) ... etc. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22fefc2c..14a85d53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,9 @@ set(source_files ${source_files} PARENT_SCOPE) add_library(zeroerr STATIC ${source_files}) target_compile_options(zeroerr PRIVATE - $<$:/W4> + $<$:/W4 /utf-8> + $<$:/utf-8> + $<$:-fstandalone-debug> $<$>:-Wall -Wextra -Wpedantic> ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d6163085..26af7c26 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,12 +32,12 @@ if(CMAKE_CUDA_COMPILER AND NOT DISABLE_CUDA_BUILD) message(STATUS " CUDA ${CMAKE_CUDA_COMPILER_ID} ${CMAKE_CUDA_COMPILER_VERSION} enabled ") set(CMAKE_CUDA_STANDARD 17) - + set(CMAKE_CUDA_STANDARD_REQUIRED ON) set(CMAKE_CUDA_ARCHITECTURES 80) set(cuda_files cuda_test.cu) -else() - set(cuda_files "") + add_executable(cudatest ${cuda_files}) + target_link_libraries(cudatest zeroerr) endif() ## Test Cases @@ -57,12 +57,14 @@ else() set(libfuzzer "") endif() -add_executable(unittest ${test_files} ${cuda_files}) +add_executable(unittest ${test_files}) target_link_libraries(unittest zeroerr ${libfuzzer} ${llvm_libs}) target_compile_options(unittest PRIVATE - $<$:/W4> + $<$:/W4 /utf-8> + $<$:/utf-8> + $<$:-fstandalone-debug> $<$>:-Wall -Wextra -Wpedantic> - ) +) ## Fuzzing Support if (ENABLE_FUZZING) diff --git a/zeroerr.hpp b/zeroerr.hpp index a50d811e..1f6e86b5 100644 --- a/zeroerr.hpp +++ b/zeroerr.hpp @@ -4103,8 +4103,14 @@ struct TestCase; */ class TestContext { public: - unsigned passed = 0, warning = 0, failed = 0, skipped = 0; - unsigned passed_as = 0, warning_as = 0, failed_as = 0, skipped_as = 0; + unsigned passed = 0; + unsigned warning = 0; + unsigned failed = 0; + unsigned skipped = 0; + unsigned passed_as = 0; + unsigned warning_as = 0; + unsigned failed_as = 0; + unsigned skipped_as = 0; IReporter& reporter; @@ -4276,7 +4282,7 @@ class IReporter { * @brief Create the reporter object with the given name. * @param name The name of the reporter. Available reporters are: console, xml. * @param ut The unit test object that will be used to configure the test. - */ + */ static IReporter* create(const std::string& name, UnitTest& ut); IReporter(UnitTest& ut) : ut(ut) {} @@ -4321,7 +4327,7 @@ struct regReporter { * }); * test(a, b); * ``` - * + * * This will test the targetFunc with all the combinations of a and b, e.g. (1,4), (1,5), (1,6), * (2,4), (2,5) ... etc. */ From 5f679314b5b4bb77ea49b8932d2105e18bb79b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Tue, 27 Aug 2024 23:43:45 -0700 Subject: [PATCH 02/14] update --- include/zeroerr/internal/typetraits.h | 35 ++++++++++++++++++ include/zeroerr/print.h | 17 --------- test/CMakeLists.txt | 12 +++---- zeroerr.hpp | 52 ++++++++++++++++++--------- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/include/zeroerr/internal/typetraits.h b/include/zeroerr/internal/typetraits.h index c4746715..c4de6e0d 100644 --- a/include/zeroerr/internal/typetraits.h +++ b/include/zeroerr/internal/typetraits.h @@ -29,6 +29,25 @@ class weak_ptr; namespace zeroerr { +/** + * @brief rank is a helper class for Printer to define the priority of overloaded functions. + * @tparam N the priority of the rule. 0 is the lowest priority. The maximum priority is max_rank. + * + * You can define a rule by adding it as a function parameter with rank where N is the priority. + * For example: + * template + * void Foo(T v, rank<0>); // lowest priority + * void Foo(int v, rank<1>); // higher priority + * + * Even though in the first rule, type T can be an int, the second function will still be called due + * to the priority. + */ +template +struct rank : rank {}; +template <> +struct rank<0> {}; + + namespace detail { // C++11 void_t @@ -129,6 +148,22 @@ template struct is_array()[0])>> : std::true_type {}; +template +struct is_modifiable : std::false_type {}; + +template +struct is_modifiable>(), + // Can insert and erase elements + T().insert(T().end(), std::declval()), + T().erase(T().begin()), (void)0)>> : std::true_type {}; + + + // Check if a type has the element type as std::pair template using has_pair_type = diff --git a/include/zeroerr/print.h b/include/zeroerr/print.h index cb4a8e18..cb7d9884 100644 --- a/include/zeroerr/print.h +++ b/include/zeroerr/print.h @@ -27,23 +27,6 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { -/** - * @brief rank is a helper class for Printer to define the priority of overloaded functions. - * @tparam N the priority of the rule. 0 is the lowest priority. The maximum priority is max_rank. - * - * You can define a rule by adding it as a function parameter with rank where N is the priority. - * For example: - * template - * void Foo(T v, rank<0>); // lowest priority - * void Foo(int v, rank<1>); // higher priority - * - * Even though in the first rule, type T can be an int, the second function will still be called due - * to the priority. - */ -template -struct rank : rank {}; -template <> -struct rank<0> {}; constexpr unsigned max_rank = 5; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 26af7c26..1e33da12 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,12 +43,12 @@ endif() ## Test Cases set(test_files - fuzz_test.cpp - llvm_test.cpp - log_test.cpp - print_test.cpp - table_test.cpp - unit_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fuzz_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/llvm_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/log_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/print_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/table_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/unit_test.cpp ) if (ENABLE_FUZZING) diff --git a/zeroerr.hpp b/zeroerr.hpp index 1f6e86b5..2e15193a 100644 --- a/zeroerr.hpp +++ b/zeroerr.hpp @@ -724,6 +724,25 @@ class weak_ptr; namespace zeroerr { +/** + * @brief rank is a helper class for Printer to define the priority of overloaded functions. + * @tparam N the priority of the rule. 0 is the lowest priority. The maximum priority is max_rank. + * + * You can define a rule by adding it as a function parameter with rank where N is the priority. + * For example: + * template + * void Foo(T v, rank<0>); // lowest priority + * void Foo(int v, rank<1>); // higher priority + * + * Even though in the first rule, type T can be an int, the second function will still be called due + * to the priority. + */ +template +struct rank : rank {}; +template <> +struct rank<0> {}; + + namespace detail { // C++11 void_t @@ -824,6 +843,22 @@ template struct is_array()[0])>> : std::true_type {}; +template +struct is_modifiable : std::false_type {}; + +template +struct is_modifiable>(), + // Can insert and erase elements + T().insert(T().end(), std::declval()), + T().erase(T().begin()), (void)0)>> : std::true_type {}; + + + // Check if a type has the element type as std::pair template using has_pair_type = @@ -1242,23 +1277,6 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { -/** - * @brief rank is a helper class for Printer to define the priority of overloaded functions. - * @tparam N the priority of the rule. 0 is the lowest priority. The maximum priority is max_rank. - * - * You can define a rule by adding it as a function parameter with rank where N is the priority. - * For example: - * template - * void Foo(T v, rank<0>); // lowest priority - * void Foo(int v, rank<1>); // higher priority - * - * Even though in the first rule, type T can be an int, the second function will still be called due - * to the priority. - */ -template -struct rank : rank {}; -template <> -struct rank<0> {}; constexpr unsigned max_rank = 5; From bbb813d819599a47b888cfca1955c268d0e55b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Mon, 2 Sep 2024 15:25:31 -0700 Subject: [PATCH 03/14] Improve Arbitrary --- include/zeroerr/domains/arbitrary.h | 40 +++++++++++------------------ zeroerr.hpp | 40 +++++++++++------------------ 2 files changed, 30 insertions(+), 50 deletions(-) diff --git a/include/zeroerr/domains/arbitrary.h b/include/zeroerr/domains/arbitrary.h index b53ee5c0..28c6f1aa 100644 --- a/include/zeroerr/domains/arbitrary.h +++ b/include/zeroerr/domains/arbitrary.h @@ -13,8 +13,11 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { -template -class Arbitrary { +template +class Arbitrary : public Arbitrary {}; + +template +struct Arbitrary { static_assert(detail::always_false::value, "No Arbitrary specialization for this type"); }; @@ -35,7 +38,7 @@ using is_unsigned_int = typename std::enable_if::value && !std::numeric_limits::is_signed, void>::type; template -class Arbitrary> : public DomainConvertable { +class Arbitrary> : public DomainConvertable { public: using ValueType = T; using CorpusType = T; @@ -53,7 +56,7 @@ using is_signed_int = void>::type; template -class Arbitrary> : public DomainConvertable { +class Arbitrary> : public DomainConvertable { public: using ValueType = T; using CorpusType = T; @@ -69,7 +72,7 @@ class Arbitrary> : public DomainConvertable { template using is_float_point = typename std::enable_if::value, void>::type; template -class Arbitrary> : public DomainConvertable { +class Arbitrary> : public DomainConvertable { public: using ValueType = T; using CorpusType = T; @@ -89,7 +92,7 @@ using is_string = typename std::enable_if::value>::type; template -class Arbitrary> : public Domain> { +class Arbitrary> : public Domain> { Arbitrary> impl; public: @@ -108,23 +111,11 @@ class Arbitrary> : public Domain -using is_modifable = - typename std::enable_if::value, - decltype( - // Iterable - T().begin(), T().end(), T().size(), - // Values are mutable - // This rejects associative containers, for example - // *T().begin() = std::declval>(), - // Can insert and erase elements - T().insert(T().end(), std::declval()), - T().erase(T().begin()), - // - (void)0)>::type; +using is_modifiable = typename std::enable_if::value>::type; + template -class Arbitrary> +class Arbitrary> : public SequenceContainerOf> { public: Arbitrary() @@ -133,18 +124,17 @@ class Arbitrary> }; template -class Arbitrary> +class Arbitrary, 1> : public AggregateOf< std::pair::type, typename std::remove_const::type>> {}; template -class Arbitrary> +class Arbitrary, 1> : public AggregateOf::type...>> {}; template -class Arbitrary : public Arbitrary {}; - +class Arbitrary : public Arbitrary {}; } // namespace zeroerr diff --git a/zeroerr.hpp b/zeroerr.hpp index 2e15193a..117e457d 100644 --- a/zeroerr.hpp +++ b/zeroerr.hpp @@ -2442,8 +2442,11 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { -template -class Arbitrary { +template +class Arbitrary : public Arbitrary {}; + +template +struct Arbitrary { static_assert(detail::always_false::value, "No Arbitrary specialization for this type"); }; @@ -2464,7 +2467,7 @@ using is_unsigned_int = typename std::enable_if::value && !std::numeric_limits::is_signed, void>::type; template -class Arbitrary> : public DomainConvertable { +class Arbitrary> : public DomainConvertable { public: using ValueType = T; using CorpusType = T; @@ -2482,7 +2485,7 @@ using is_signed_int = void>::type; template -class Arbitrary> : public DomainConvertable { +class Arbitrary> : public DomainConvertable { public: using ValueType = T; using CorpusType = T; @@ -2498,7 +2501,7 @@ class Arbitrary> : public DomainConvertable { template using is_float_point = typename std::enable_if::value, void>::type; template -class Arbitrary> : public DomainConvertable { +class Arbitrary> : public DomainConvertable { public: using ValueType = T; using CorpusType = T; @@ -2518,7 +2521,7 @@ using is_string = typename std::enable_if::value>::type; template -class Arbitrary> : public Domain> { +class Arbitrary> : public Domain> { Arbitrary> impl; public: @@ -2537,23 +2540,11 @@ class Arbitrary> : public Domain -using is_modifable = - typename std::enable_if::value, - decltype( - // Iterable - T().begin(), T().end(), T().size(), - // Values are mutable - // This rejects associative containers, for example - // *T().begin() = std::declval>(), - // Can insert and erase elements - T().insert(T().end(), std::declval()), - T().erase(T().begin()), - // - (void)0)>::type; +using is_modifiable = typename std::enable_if::value>::type; + template -class Arbitrary> +class Arbitrary> : public SequenceContainerOf> { public: Arbitrary() @@ -2562,18 +2553,17 @@ class Arbitrary> }; template -class Arbitrary> +class Arbitrary, 1> : public AggregateOf< std::pair::type, typename std::remove_const::type>> {}; template -class Arbitrary> +class Arbitrary, 1> : public AggregateOf::type...>> {}; template -class Arbitrary : public Arbitrary {}; - +class Arbitrary : public Arbitrary {}; } // namespace zeroerr From 6b6e17fea6de329340b62a719a46045bca0f6d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Sat, 7 Sep 2024 13:55:13 -0700 Subject: [PATCH 04/14] allow only match prefix --- .vscode/launch.json | 8 +++++ Makefile | 2 +- examples/1_basic.cpp | 2 +- examples/2_log.cpp | 75 +++++++++++++++++++++++++++++++++++++++++ examples/CMakeLists.txt | 4 ++- include/zeroerr/print.h | 8 ++--- src/log.cpp | 2 +- test/log_test.cpp | 3 ++ zeroerr.hpp | 10 +++--- 9 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 examples/2_log.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index cd9c9554..bb42506e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,6 +12,14 @@ "args": [], "cwd": "${workspaceFolder}" }, + { + "type": "lldb", + "request": "launch", + "name": "Windows 2_log", + "program": "${workspaceFolder}/build/windows/examples/Debug/2_log.exe", + "args": ["--testcase=parsing test"], + "cwd": "${workspaceFolder}" + }, { "type": "lldb", "request": "launch", diff --git a/Makefile b/Makefile index 4375d72f..9dc22e3c 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ reporter: linux cd build/linux/test && ./unittest --no-color --log-to-report --reporters=xml windows-test: windows - cd build/windows/test && ./Debug/unittest.exe + cd build/windows/test && ../examples/Debug/2_log.exe --testcase="parsing test" build/linux-release/Makefile: Makefile diff --git a/examples/1_basic.cpp b/examples/1_basic.cpp index c519ca1b..5f8c5506 100644 --- a/examples/1_basic.cpp +++ b/examples/1_basic.cpp @@ -47,5 +47,5 @@ TEST_CASE("fib function test") { CHECK(fib(3) == 2); CHECK(fib(4) == 3); CHECK(fib(5) == 5); - CHECK(fib(20) == 6765); + CHECK_THROWS(fib(20)); } diff --git a/examples/2_log.cpp b/examples/2_log.cpp new file mode 100644 index 00000000..a7f4c924 --- /dev/null +++ b/examples/2_log.cpp @@ -0,0 +1,75 @@ +#define ZEROERR_IMPLEMENTATION +#include "zeroerr.hpp" + +using namespace zeroerr; + +TEST_CASE("1. basic log test") { + LOG("Basic log"); + WARN("Warning log"); + ERR("Error log"); + FATAL("Fatal log"); + + LOG("log with basic thype {} {} {} {}", 1, true, 1.0, "string"); + + std::vector> data = { + {1, 1.0, "string"}, {2, 2.0, "string"} + }; + LOG("log with complex type: {data}", data); + + LOG_IF(1==1, "log if condition is true"); + LOG_FIRST(1==1, "log only at the first time condition is true"); + WARN_EVERY_(2, "log every 2 times"); + WARN_IF_EVERY_(2, 1==1, "log if condition is true every 2 times"); + + DLOG(WARN_IF, 1==1, "debug log for WARN_IF"); +} + + +struct Expr { + virtual Expr* Clone() { return new Expr(); }; + virtual ~Expr() {} +}; + +Expr* parse_the_input(std::string input) { + return new Expr(); +} + + +Expr* parseExpr(std::string input) +{ + static std::map cache; + if (cache.count(input) == 0) { + Expr* expr = parse_the_input(input); + cache[input] = expr; + return expr; + } else { + LOG("CacheHit: input = {input}", input); + return cache[input]->Clone(); + } +} +TEST_CASE("parsing test") { + zeroerr::suspendLog(); + Expr* e1 = parseExpr("1 + 2"); + // CHECK(LOG_GET(parseExpr, "CacheHit", input, std::string) == std::string{}); + Expr* e2 = parseExpr("1 + 2"); + std::string log = LOG_GET(parseExpr, "CacheHit", input, std::string); + CHECK(log == "1 + 2"); + zeroerr::resumeLog(); +} + + +TEST_CASE("iterate log stream") { + zeroerr::suspendLog(); + + auto& stream = zeroerr::LogStream::getDefault(); + + for (auto p = stream.begin("function log {sum}, {i}"); p != stream.end(); ++p) { + std::cerr << "p.get(\"sum\") = " << p.get("sum") << std::endl; + std::cerr << "p.get(\"i\") = " << p.get("i") << std::endl; + CHECK(p.get("sum") == 10); + CHECK(p.get("i") == 1); + } + + zeroerr::resumeLog(); +} + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 820d1c46..1f0772eb 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,4 @@ add_executable(1_basic ${CMAKE_CURRENT_SOURCE_DIR}/1_basic.cpp) -add_dependencies(1_basic assemble_single_header) \ No newline at end of file +add_executable(2_log ${CMAKE_CURRENT_SOURCE_DIR}/2_log.cpp) +add_dependencies(1_basic assemble_single_header) +add_dependencies(2_log assemble_single_header) \ No newline at end of file diff --git a/include/zeroerr/print.h b/include/zeroerr/print.h index cb7d9884..8e84c05e 100644 --- a/include/zeroerr/print.h +++ b/include/zeroerr/print.h @@ -146,7 +146,7 @@ struct Printer { (void)_; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS&& ZEROERR_IS_POD) + ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS && ZEROERR_IS_POD) print(const T& value, unsigned level, const char* lb, rank<1>) { os << tab(level) << "{"; print_struct(value, level, isCompact ? " " : line_break, @@ -160,7 +160,7 @@ struct Printer { os << tab(level) << (value ? "true" : "false") << lb; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS&& ZEROERR_IS_STREAMABLE) + ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS && ZEROERR_IS_STREAMABLE) print(T value, unsigned level, const char* lb, rank<2>) { os << tab(level) << value << lb; } @@ -175,7 +175,7 @@ struct Printer { os << tab(level) << "}" << lb; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER&& ZEROERR_IS_ARRAY) + ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER && ZEROERR_IS_ARRAY) print(const T& value, unsigned level, const char* lb, rank<3>) { os << tab(level) << "["; bool last = false; @@ -195,7 +195,7 @@ struct Printer { os << tab(level) << "<" << type(value) << " at " << value.get() << ">" << lb; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER&& ZEROERR_IS_MAP) + ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER && ZEROERR_IS_MAP) print(const T& value, unsigned level, const char* lb, rank<4>) { os << tab(level) << "{" << (isCompact ? "" : line_break); bool last = false; diff --git a/src/log.cpp b/src/log.cpp index 82a5bb24..76ad126a 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -207,7 +207,7 @@ LogIterator& LogIterator::operator++() { } bool LogIterator::check_filter() { - if (!message_filter.empty() && q->info->message != message_filter) return false; + if (!message_filter.empty() && std::string(q->info->message).rfind(message_filter, 0) == 0) return false; if (!function_name_filter.empty() && q->info->function != function_name_filter) return false; if (line_filter != -1 && static_cast(q->info->line) != line_filter) return false; return true; diff --git a/test/log_test.cpp b/test/log_test.cpp index ee97fb45..15cebcb7 100644 --- a/test/log_test.cpp +++ b/test/log_test.cpp @@ -121,6 +121,7 @@ TEST_CASE("verbose") { static void function() { LOG("function log {i}", 1); LOG("function log {sum}, {i}", 10, 1); + LOG("A: message {i}", 1); } TEST_CASE("access log in Test case") { @@ -146,6 +147,8 @@ TEST_CASE("access log in Test case") { CHECK(LOG_GET(function, "function log {i}", i, int) == 1); CHECK(LOG_GET(function, "function log {sum}, {i}", sum, int) == 9); CHECK(LOG_GET(function, "function log {sum}, {i}", i, int) == 2); + + CHECK(LOG_GET(function, "A", i, int) == 1); zeroerr::resumeLog(); } diff --git a/zeroerr.hpp b/zeroerr.hpp index 117e457d..a834296a 100644 --- a/zeroerr.hpp +++ b/zeroerr.hpp @@ -1396,7 +1396,7 @@ struct Printer { (void)_; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS&& ZEROERR_IS_POD) + ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS && ZEROERR_IS_POD) print(const T& value, unsigned level, const char* lb, rank<1>) { os << tab(level) << "{"; print_struct(value, level, isCompact ? " " : line_break, @@ -1410,7 +1410,7 @@ struct Printer { os << tab(level) << (value ? "true" : "false") << lb; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS&& ZEROERR_IS_STREAMABLE) + ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS && ZEROERR_IS_STREAMABLE) print(T value, unsigned level, const char* lb, rank<2>) { os << tab(level) << value << lb; } @@ -1425,7 +1425,7 @@ struct Printer { os << tab(level) << "}" << lb; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER&& ZEROERR_IS_ARRAY) + ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER && ZEROERR_IS_ARRAY) print(const T& value, unsigned level, const char* lb, rank<3>) { os << tab(level) << "["; bool last = false; @@ -1445,7 +1445,7 @@ struct Printer { os << tab(level) << "<" << type(value) << " at " << value.get() << ">" << lb; } - ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER&& ZEROERR_IS_MAP) + ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER && ZEROERR_IS_MAP) print(const T& value, unsigned level, const char* lb, rank<4>) { os << tab(level) << "{" << (isCompact ? "" : line_break); bool last = false; @@ -5099,7 +5099,7 @@ LogIterator& LogIterator::operator++() { } bool LogIterator::check_filter() { - if (!message_filter.empty() && q->info->message != message_filter) return false; + if (!message_filter.empty() && std::string(q->info->message).rfind(message_filter, 0) == 0) return false; if (!function_name_filter.empty() && q->info->function != function_name_filter) return false; if (line_filter != -1 && static_cast(q->info->line) != line_filter) return false; return true; From 6c917c7f67cbbd19500c45c027673a90caa36bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Sat, 7 Sep 2024 14:23:57 -0700 Subject: [PATCH 05/14] fix getRawLog --- examples/2_log.cpp | 8 ++++++-- src/log.cpp | 11 ++++++++--- zeroerr.hpp | 11 ++++++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/2_log.cpp b/examples/2_log.cpp index a7f4c924..cfe40708 100644 --- a/examples/2_log.cpp +++ b/examples/2_log.cpp @@ -47,12 +47,16 @@ Expr* parseExpr(std::string input) return cache[input]->Clone(); } } + TEST_CASE("parsing test") { zeroerr::suspendLog(); + std::string log; Expr* e1 = parseExpr("1 + 2"); - // CHECK(LOG_GET(parseExpr, "CacheHit", input, std::string) == std::string{}); + log = LOG_GET(parseExpr, "CacheHit", input, std::string); + CHECK(log == std::string{}); Expr* e2 = parseExpr("1 + 2"); - std::string log = LOG_GET(parseExpr, "CacheHit", input, std::string); + log = zeroerr::LogStream::getDefault() + .getLog("parseExpr", "CacheHit", "input"); CHECK(log == "1 + 2"); zeroerr::resumeLog(); } diff --git a/src/log.cpp b/src/log.cpp index 76ad126a..5c93b46d 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -39,7 +39,7 @@ LogInfo::LogInfo(const char* filename, const char* function, const char* message const char* q = p + 1; while (*q && *q != '}') q++; if (*q == '}') { - std::string N(p + 1, (size_t)(q-p-1)); + std::string N(p + 1, (size_t)(q - p - 1)); names[N] = static_cast(names.size()); p = q; } @@ -162,10 +162,15 @@ void* LogStream::getRawLog(std::string func, unsigned line, std::string name) { return nullptr; } +static bool startWith(const std::string& str, const std::string& prefix) { + return str.rfind(prefix, 0) == 0; +} + void* LogStream::getRawLog(std::string func, std::string msg, std::string name) { for (DataBlock* p = first; p; p = p->next) for (auto q = p->begin(); q < p->end(); q = moveBytes(q, q->info->size)) - if (msg == q->info->message && func == q->info->function) return q->getRawLog(name); + if (startWith(q->info->message, msg) && func == q->info->function) + return q->getRawLog(name); return nullptr; } @@ -207,7 +212,7 @@ LogIterator& LogIterator::operator++() { } bool LogIterator::check_filter() { - if (!message_filter.empty() && std::string(q->info->message).rfind(message_filter, 0) == 0) return false; + if (!message_filter.empty() && startWith(q->info->message, message_filter)) return false; if (!function_name_filter.empty() && q->info->function != function_name_filter) return false; if (line_filter != -1 && static_cast(q->info->line) != line_filter) return false; return true; diff --git a/zeroerr.hpp b/zeroerr.hpp index a834296a..9d5a3dce 100644 --- a/zeroerr.hpp +++ b/zeroerr.hpp @@ -4931,7 +4931,7 @@ LogInfo::LogInfo(const char* filename, const char* function, const char* message const char* q = p + 1; while (*q && *q != '}') q++; if (*q == '}') { - std::string N(p + 1, (size_t)(q-p-1)); + std::string N(p + 1, (size_t)(q - p - 1)); names[N] = static_cast(names.size()); p = q; } @@ -5054,10 +5054,15 @@ void* LogStream::getRawLog(std::string func, unsigned line, std::string name) { return nullptr; } +static bool startWith(const std::string& str, const std::string& prefix) { + return str.rfind(prefix, 0) == 0; +} + void* LogStream::getRawLog(std::string func, std::string msg, std::string name) { for (DataBlock* p = first; p; p = p->next) for (auto q = p->begin(); q < p->end(); q = moveBytes(q, q->info->size)) - if (msg == q->info->message && func == q->info->function) return q->getRawLog(name); + if (startWith(q->info->message, msg) && func == q->info->function) + return q->getRawLog(name); return nullptr; } @@ -5099,7 +5104,7 @@ LogIterator& LogIterator::operator++() { } bool LogIterator::check_filter() { - if (!message_filter.empty() && std::string(q->info->message).rfind(message_filter, 0) == 0) return false; + if (!message_filter.empty() && startWith(q->info->message, message_filter)) return false; if (!function_name_filter.empty() && q->info->function != function_name_filter) return false; if (line_filter != -1 && static_cast(q->info->line) != line_filter) return false; return true; From 86f2034fd46d41c6f674baf0d48c94d119032077 Mon Sep 17 00:00:00 2001 From: sunxfancy Date: Thu, 19 Sep 2024 00:26:39 -0700 Subject: [PATCH 06/14] update --- Makefile | 2 +- test/fuzz_test.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4375d72f..6f6afdaa 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ fuzz-test: linux cd build/linux/test && ./unittest --testcase=fuzz_serialize.* fuzz: linux - cd build/linux/test && ./unittest -f --testcase=fuzz_test.* + cd build/linux/test && ./unittest -f --testcase=presentation linux-test: linux cd build/linux/test && ./unittest diff --git a/test/fuzz_test.cpp b/test/fuzz_test.cpp index f95c9d9f..1a2dd025 100644 --- a/test/fuzz_test.cpp +++ b/test/fuzz_test.cpp @@ -67,6 +67,40 @@ FUZZ_TEST_CASE("fuzz_test4") { } + +int find_the_biggest(std::vector vec) { + if (vec.empty()) { + WARN("Empty vector, vec.size() = {size}", vec.size()); + return 0; + } + int max = 0; + for (int i = 0; i < vec.size(); ++i) { + if (vec[i] > max) {max = vec[i];} + } + return max; +} + +FUZZ_TEST_CASE("presentation") { + LOG("Run fuzz_test"); + FUZZ_FUNC([=](std::vector vec) { + int ans = find_the_biggest(vec); + // verify the result + for (int i = 0; i < vec.size(); ++i) { + CHECK(ans >= vec[i]); + } + if (vec.size() == 0) { + CHECK(ans == 0); + // verify WARN message to make sure the path is correct + CHECK(LOG_GET(find_the_biggest, + "Empty vector, vec.size() = {size}", size, size_t) == 0); + } + }) + .WithDomains(ContainerOf>( + InRange(0, 100))) + .WithSeeds({{{0, 1, 2, 3, 4, 5}}, {{1, 8, 4, 2, 3}}}) + .Run(100); +} + TEST_CASE("fuzz_serialize") { LOG("fuzz_serialize"); From 9b087ba629c9fb059c2e578cb8285057911fe0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Mon, 30 Sep 2024 16:47:58 -0700 Subject: [PATCH 07/14] update --- test/log_test.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/log_test.cpp b/test/log_test.cpp index 15cebcb7..dceecfb0 100644 --- a/test/log_test.cpp +++ b/test/log_test.cpp @@ -187,9 +187,10 @@ TEST_CASE("multiple log stream") { } TEST_CASE("log to dir") { - zeroerr::LogStream::getDefault().setFileLogger("./logdir", LogStream::SPLIT_BY_CATEGORY, - LogStream::SPLIT_BY_SEVERITY, - LogStream::DAILY_FILE); + zeroerr::LogStream::getDefault() + .setFileLogger("./logdir", LogStream::SPLIT_BY_CATEGORY, + LogStream::SPLIT_BY_SEVERITY, + LogStream::DAILY_FILE); LOG("log to dir {i}", 1); WARN("warn log to dir {i}", 2); zeroerr::LogStream::getDefault().setStderrLogger(); From afde7f39e84b5f8a0a02f707ec2a91073f387cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Mon, 30 Sep 2024 19:05:07 -0700 Subject: [PATCH 08/14] fix a bug in generated header --- examples/3_fuzzing.cpp | 41 +++++++++ examples/CMakeLists.txt | 12 ++- scripts/gen-single-file.cmake | 3 +- zeroerr.hpp | 158 ++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 examples/3_fuzzing.cpp diff --git a/examples/3_fuzzing.cpp b/examples/3_fuzzing.cpp new file mode 100644 index 00000000..450e63c3 --- /dev/null +++ b/examples/3_fuzzing.cpp @@ -0,0 +1,41 @@ +#define ZEROERR_IMPLEMENTATION +#define ZEROERR_FUZZING +#include "zeroerr.hpp" + +using namespace zeroerr; + + + +int find_the_biggest(std::vector vec) { + if (vec.empty()) { + WARN("Empty vector, vec.size() = {size}", vec.size()); + return 0; + } + int max = 0; + for (int i = 0; i < vec.size(); ++i) { + if (vec[i] > max) {max = vec[i];} + } + return max; +} + +FUZZ_TEST_CASE("presentation") { + LOG("Run fuzz_test"); + FUZZ_FUNC([=](std::vector vec) { + int ans = find_the_biggest(vec); + // verify the result + for (int i = 0; i < vec.size(); ++i) { + CHECK(ans >= vec[i]); + } + if (vec.size() == 0) { + CHECK(ans == 0); + // verify WARN message to make sure the path is correct + CHECK(LOG_GET(find_the_biggest, + "Empty vector, vec.size() = {size}", size, size_t) == 0); + } + }) + .WithDomains(ContainerOf>( + InRange(0, 100))) + .WithSeeds({{{0, 1, 2, 3, 4, 5}}, {{1, 8, 4, 2, 3}}}) + .Run(100); + +} \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 1f0772eb..09cf3e5b 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,8 @@ -add_executable(1_basic ${CMAKE_CURRENT_SOURCE_DIR}/1_basic.cpp) -add_executable(2_log ${CMAKE_CURRENT_SOURCE_DIR}/2_log.cpp) -add_dependencies(1_basic assemble_single_header) -add_dependencies(2_log assemble_single_header) \ No newline at end of file +macro(define_example name) + add_executable(${name} ${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp) + add_dependencies(${name} assemble_single_header) +endmacro(define_example) + +define_example(1_basic) +define_example(2_log) +define_example(3_fuzzing) diff --git a/scripts/gen-single-file.cmake b/scripts/gen-single-file.cmake index f500cb5a..d04a0e0d 100644 --- a/scripts/gen-single-file.cmake +++ b/scripts/gen-single-file.cmake @@ -44,6 +44,7 @@ loadfile(${my_src_folder}/log.cpp log_cpp) loadfile(${my_src_folder}/table.cpp table_cpp) loadfile(${my_src_folder}/unittest.cpp unittest_cpp) loadfile(${my_src_folder}/fuzztest.cpp fuzztest_cpp) +loadfile(${my_src_folder}/serialization.cpp serialization_cpp) file(WRITE zeroerr.hpp "// ======================================================================\n") @@ -53,5 +54,5 @@ file(APPEND zeroerr.hpp "${config}\n${color}\n${console}\n${debugbreak}\n${threa file(APPEND zeroerr.hpp "${domain}\n${in_range}\n${element_of}\n${container_of}\n${aggregate_of}\n${arbitrary}\n") file(APPEND zeroerr.hpp "${benchmark}\n${assert}\n${dbg}\n${format}\n${log}\n${table}\n${profiler}\n${unittest}\n${fuzztest}\n") file(APPEND zeroerr.hpp "#ifdef ZEROERR_IMPLEMENTATION\n") -file(APPEND zeroerr.hpp "${rng_cpp}\n${color_cpp}\n${print_cpp}\n${console_cpp}\n${log_cpp}\n${table_cpp}\n${unittest_cpp}\n${fuzztest_cpp}\n${benchmark_cpp}\n") +file(APPEND zeroerr.hpp "${rng_cpp}\n${color_cpp}\n${print_cpp}\n${console_cpp}\n${log_cpp}\n${table_cpp}\n${unittest_cpp}\n${fuzztest_cpp}\n${serialization_cpp}\n${benchmark_cpp}\n") file(APPEND zeroerr.hpp "#endif // ZEROERR_IMPLEMENTATION\n") diff --git a/zeroerr.hpp b/zeroerr.hpp index 9d5a3dce..e72b3b54 100644 --- a/zeroerr.hpp +++ b/zeroerr.hpp @@ -6576,6 +6576,164 @@ void RunFuzzTest(IFuzzTest& fuzz_test, int seed, int runs, int max_len, int time +#include +namespace zeroerr { + +IRObject* IRObject::alloc(size_t size) { + IRObject* list = new IRObject[size + 1]; + list[0].i = size; + return list + 1; +} + +char* IRObject::alloc_str(size_t size) { + char* s = new char[size + 1]; + s[size] = 0; + return s; +} + +static std::string escape(std::string str) { + std::string result; + for (char c : str) { + switch (c) { + case ' ': result += "\\s"; break; + case '\n': result += "\\n"; break; + case '\t': result += "\\t"; break; + case '\r': result += "\\r"; break; + case '\f': result += "\\f"; break; + case '\v': result += "\\v"; break; + case '\\': result += "\\\\"; break; + case '"': result += "\\\""; break; + default: { + if (c < 32 || c > 126) { + result += "\\x"; + result += "0123456789abcdef"[c >> 4]; + result += "0123456789abcdef"[c & 15]; + continue; + } + result += c; + } + } + } + return result; +} + +static std::string unescape(std::string str) { + std::string result; + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] == '\\') { + switch (str[++i]) { + case 's': result += ' '; break; + case 'n': result += '\n'; break; + case 't': result += '\t'; break; + case 'r': result += '\r'; break; + case 'f': result += '\f'; break; + case 'v': result += '\v'; break; + case '\\': result += '\\'; break; + case '"': result += '"'; break; + case 'x': { + char c = 0; + for (int j = 0; j < 2; ++j) { + c *= 16; + if (str[i + 1] >= '0' && str[i + 1] <= '9') { + c += str[i + 1] - '0'; + } else if (str[i + 1] >= 'a' && str[i + 1] <= 'f') { + c += str[i + 1] - 'a' + 10; + } else if (str[i + 1] >= 'A' && str[i + 1] <= 'F') { + c += str[i + 1] - 'A' + 10; + } + } + result += c; + i += 2; + } + } + } else { + result += str[i]; + } + } + return result; +} + +static void to_string(IRObject obj, std::stringstream& ss) { + switch (obj.type) { + case IRObject::Type::Int: ss << obj.i; break; + case IRObject::Type::Float: ss << obj.f << 'f'; break; + case IRObject::Type::String: ss << '"' << escape(obj.s) << '"'; break; + case IRObject::Type::ShortString: ss << '"' << escape(obj.ss) << '"'; break; + case IRObject::Type::Object: + ss << "{ "; + auto c = obj.GetChildren(); + for (unsigned i = 0; i < c.size; ++i) { + to_string(*(c.children + i), ss); + ss << " "; + } + ss << "}"; + break; + } +} + +static IRObject from_string(std::stringstream& ss, std::string& token) { + IRObject obj; + if (token == "{") { + std::vector children; + while (ss >> token) { + if (token == "}") break; + IRObject child = from_string(ss, token); + if (child.type == IRObject::Type::Undefined) + return obj; + children.push_back(child); + } + IRObject* child = IRObject::alloc(children.size()); + for (unsigned i = 0; i < children.size(); ++i) { + child[i] = children[i]; + } + obj.SetChildren(child); + return obj; + } + if (token[0] == '"') { + CHECK(token.size() > 1 AND token.back() == '"'); + obj.SetScalar(unescape(token.substr(1, token.size() - 2))); + return obj; + } + if (token.back() == 'f') { + obj.SetScalar(std::stod(token.substr(0, token.size() - 1))); + return obj; + } + if (token[0] == '-' || (token[0] >= '0' && token[0] <= '9')) { + obj.SetScalar(std::stoll(token)); + return obj; + } + return obj; +} + +std::string IRObject::ToString(IRObject obj) { + std::stringstream ss; + to_string(obj, ss); + return ss.str(); +} + +IRObject IRObject::FromString(std::string str) { + std::stringstream ss(str); + std::string token; + ss >> token; + return from_string(ss, token); +} + +std::vector IRObject::ToBinary(IRObject obj) { + std::vector bin; + return bin; +} + +IRObject IRObject::FromBinary(std::vector bin) { + IRObject obj; + return obj; +} + + +} // namespace zeroerr + + + + #include #include From 530e21340cab7b3ea35f7dc7dd07f4a3c8b06526 Mon Sep 17 00:00:00 2001 From: sunxfancy Date: Thu, 5 Dec 2024 15:06:36 -0800 Subject: [PATCH 09/14] Add some basic Decorators --- include/zeroerr/unittest.h | 14 ++++++++++ src/unittest.cpp | 55 ++++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/include/zeroerr/unittest.h b/include/zeroerr/unittest.h index 8c296ce5..74fdb79a 100644 --- a/include/zeroerr/unittest.h +++ b/include/zeroerr/unittest.h @@ -338,6 +338,20 @@ class TestArgs { int index = 0; }; + +class Decorator { +public: + virtual bool onStartup() { return false; } + virtual bool onExecution() { return false; } + virtual bool onAssertion() { return false; } + virtual bool onTimeout() { return false; } +}; + +Decorator& skip(bool isSkip = true); +Decorator& timeout(float timeout = 0.1f); // in seconds +Decorator& may_fail(bool isMayFail = true); +Decorator& should_fail(bool isShouldFail = true); + } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP \ No newline at end of file diff --git a/src/unittest.cpp b/src/unittest.cpp index 34dd9eb7..1bcd90bf 100644 --- a/src/unittest.cpp +++ b/src/unittest.cpp @@ -591,8 +591,8 @@ XmlWriter::ScopedElement::~ScopedElement() { if (m_writer) m_writer->endElement(); } -XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText(const std::string& text, - bool indent, bool new_line) { +XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText(const std::string& text, bool indent, + bool new_line) { m_writer->writeText(text, indent, new_line); return *this; } @@ -626,8 +626,10 @@ XmlWriter& XmlWriter::endElement() { m_os << "/>"; m_tagIsOpen = false; } else { - if (m_needsIndent) m_os << m_indent; - else m_needsIndent = true; + if (m_needsIndent) + m_os << m_indent; + else + m_needsIndent = true; m_os << ""; } m_os << std::endl; @@ -659,7 +661,7 @@ XmlWriter& XmlWriter::writeText(const std::string& text, bool indent, bool new_l if (tagWasOpen && indent) m_os << m_indent; m_os << XmlEncode(text); m_needsNewline = new_line; - m_needsIndent = new_line; + m_needsIndent = new_line; } return *this; } @@ -786,6 +788,49 @@ IReporter* IReporter::create(const std::string& name, UnitTest& ut) { } +class SkipDecorator : public Decorator {}; + +Decorator& skip(bool isSkip = true) { + static SkipDecorator skip_dec; + return skip_dec; +} + +class TimeoutDecorator : public Decorator { + float timeout; + +public: + TimeoutDecorator(float timeout) : timeout(timeout) {} +}; + +Decorator& timeout(float timeout = 0.1f) { + static std::map timeout_dec; + if (timeout_dec.find(timeout) == timeout_dec.end()) { + timeout_dec[timeout] = TimeoutDecorator(timeout); + } + return timeout_dec[timeout]; +} + +class FailureDecorator : public Decorator { +public: + enum FailureType { may_fail, should_fail }; + FailureDecorator(FailureType type) : type(type) {} + +private: + FailureType type; +}; + + +Decorator& may_fail(bool isMayFail = true) { + static FailureDecorator may_fail_dec(FailureDecorator::may_fail); + return may_fail_dec; +} + +Decorator& should_fail(bool isShouldFail = true) { + static FailureDecorator should_fail_dec(FailureDecorator::should_fail); + return should_fail_dec; +} + + } // namespace zeroerr From b39e599a8980a6993c19bdf22e7a6db8f367551a Mon Sep 17 00:00:00 2001 From: sunxfancy Date: Fri, 13 Dec 2024 13:25:28 -0800 Subject: [PATCH 10/14] update for decorators --- include/zeroerr/benchmark.h | 19 +++++++++-------- include/zeroerr/fuzztest.h | 11 +++++----- include/zeroerr/unittest.h | 42 ++++++++++++++++++++----------------- src/fuzztest.cpp | 7 +++++++ src/unittest.cpp | 29 ++++++++++++++----------- test/unit_test.cpp | 5 +++++ 6 files changed, 68 insertions(+), 45 deletions(-) diff --git a/include/zeroerr/benchmark.h b/include/zeroerr/benchmark.h index 2d71070e..ed3dad9f 100644 --- a/include/zeroerr/benchmark.h +++ b/include/zeroerr/benchmark.h @@ -14,13 +14,14 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH -#define ZEROERR_CREATE_BENCHMARK_FUNC(function, name) \ - static void function(zeroerr::TestContext*); \ - static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ - {name, __FILE__, __LINE__, function}, zeroerr::TestType::bench); \ +#define ZEROERR_CREATE_BENCHMARK_FUNC(function, name, ...) \ + static void function(zeroerr::TestContext*); \ + static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ + {name, __FILE__, __LINE__, function, {__VA_ARGS__}}, zeroerr::TestType::bench); \ static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) -#define BENCHMARK(name) ZEROERR_CREATE_BENCHMARK_FUNC(ZEROERR_NAMEGEN(_zeroerr_benchmark), name) +#define BENCHMARK(name, ...) \ + ZEROERR_CREATE_BENCHMARK_FUNC(ZEROERR_NAMEGEN(_zeroerr_benchmark), name, __VA_ARGS__) namespace zeroerr { @@ -61,7 +62,7 @@ struct PerformanceCounter { void endMeasure(); void updateResults(uint64_t numIters); - PerfCountSet const& val() const noexcept { return _val; } + const PerfCountSet& val() const noexcept { return _val; } PerfCountSet has() const noexcept { return _has; } static PerformanceCounter& inst(); @@ -164,10 +165,10 @@ struct Benchmark { namespace detail { #if defined(_MSC_VER) -void doNotOptimizeAwaySink(void const*); +void doNotOptimizeAwaySink(const void*); template -void doNotOptimizeAway(T const& val) { +void doNotOptimizeAway(const T& val) { doNotOptimizeAwaySink(&val); } @@ -178,7 +179,7 @@ void doNotOptimizeAway(T const& val) { // Google Benchmark seemed to be the most well tested anyways. see // https://github.com/google/benchmark/blob/master/include/benchmark/benchmark.h#L307 template -void doNotOptimizeAway(T const& val) { +void doNotOptimizeAway(const T& val) { // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : : "r,m"(val) : "memory"); } diff --git a/include/zeroerr/fuzztest.h b/include/zeroerr/fuzztest.h index 6ca0cd82..44101b4d 100644 --- a/include/zeroerr/fuzztest.h +++ b/include/zeroerr/fuzztest.h @@ -20,13 +20,14 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH -#define ZEROERR_CREATE_FUZZ_TEST_FUNC(function, name) \ - static void function(zeroerr::TestContext*); \ - static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ - {name, __FILE__, __LINE__, function}, zeroerr::TestType::fuzz_test); \ +#define ZEROERR_CREATE_FUZZ_TEST_FUNC(function, name, ...) \ + static void function(zeroerr::TestContext*); \ + static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ + {name, __FILE__, __LINE__, function, {__VA_ARGS__}}, zeroerr::TestType::fuzz_test); \ static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) -#define FUZZ_TEST_CASE(name) ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), name) +#define FUZZ_TEST_CASE(name, ...) \ + ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), name, __VA_ARGS__) #define FUZZ_FUNC(func) zeroerr::FuzzFunction(func, _ZEROERR_TEST_CONTEXT) diff --git a/include/zeroerr/unittest.h b/include/zeroerr/unittest.h index 74fdb79a..482cb77a 100644 --- a/include/zeroerr/unittest.h +++ b/include/zeroerr/unittest.h @@ -8,19 +8,20 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH -#define ZEROERR_CREATE_TEST_FUNC(function, name) \ +#define ZEROERR_CREATE_TEST_FUNC(function, name, ...) \ static void function(zeroerr::TestContext*); \ static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ - {name, __FILE__, __LINE__, function}); \ + {name, __FILE__, __LINE__, function, {__VA_ARGS__}}); \ static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) -#define TEST_CASE(name) ZEROERR_CREATE_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), name) +#define TEST_CASE(name, ...) \ + ZEROERR_CREATE_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), name, __VA_ARGS__) -#define SUB_CASE(name) \ - zeroerr::SubCase(name, __FILE__, __LINE__, _ZEROERR_TEST_CONTEXT) \ +#define SUB_CASE(name, ...) \ + zeroerr::SubCase(name, __FILE__, __LINE__, _ZEROERR_TEST_CONTEXT, {__VA_ARGS__}) \ << [=](ZEROERR_UNUSED(zeroerr::TestContext * _ZEROERR_TEST_CONTEXT)) mutable -#define ZEROERR_CREATE_TEST_CLASS(fixture, classname, funcname, name) \ +#define ZEROERR_CREATE_TEST_CLASS(fixture, classname, funcname, name, ...) \ class classname : public fixture { \ public: \ void funcname(zeroerr::TestContext*); \ @@ -30,12 +31,12 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH instance.funcname(_ZEROERR_TEST_CONTEXT); \ } \ static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ - {name, __FILE__, __LINE__, ZEROERR_CAT(call_, funcname)}); \ + {name, __FILE__, __LINE__, ZEROERR_CAT(call_, funcname), {__VA_ARGS__}}); \ inline void classname::funcname(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) -#define TEST_CASE_FIXTURE(fixture, name) \ +#define TEST_CASE_FIXTURE(fixture, name, ...) \ ZEROERR_CREATE_TEST_CLASS(fixture, ZEROERR_NAMEGEN(_zeroerr_class), \ - ZEROERR_NAMEGEN(_zeroerr_test_method), name) + ZEROERR_NAMEGEN(_zeroerr_test_method), name, __VA_ARGS__) #define ZEROERR_HAVE_SAME_OUTPUT _ZEROERR_TEST_CONTEXT->save_output(); @@ -51,6 +52,7 @@ namespace zeroerr { class IReporter; struct TestCase; +class Decorator; /** * @brief TestContext is a class that holds the test results and reporter context. @@ -166,7 +168,7 @@ struct TestCase { unsigned line; std::function func; std::vector subcases; - + std::vector decorators; /** * @brief Compare the test cases. * @param rhs The test case that will be compared. @@ -180,8 +182,8 @@ struct TestCase { * @param file The file that the test case is defined. * @param line The line that the test case is defined. */ - TestCase(std::string name, std::string file, unsigned line) - : name(name), file(file), line(line) {} + TestCase(std::string name, std::string file, unsigned line, std::vector decorators) + : name(name), file(file), line(line), decorators(decorators) {} /** * @brief Construct a new Test Case object @@ -189,10 +191,11 @@ struct TestCase { * @param file The file that the test case is defined. * @param line The line that the test case is defined. * @param func The function that will be run to test the test case. + * @param decorators The decorators that will be used to decorate the test case. */ TestCase(std::string name, std::string file, unsigned line, - std::function func) - : name(name), file(file), line(line), func(func) {} + std::function func, std::vector decorators) + : name(name), file(file), line(line), func(func), decorators(decorators) {} }; @@ -200,7 +203,8 @@ struct TestCase { * @brief SubCase is a class that holds the subcase information. */ struct SubCase : TestCase { - SubCase(std::string name, std::string file, unsigned line, TestContext* context); + SubCase(std::string name, std::string file, unsigned line, TestContext* context, + std::vector decorators); ~SubCase() = default; TestContext* context; void operator<<(std::function op); @@ -347,10 +351,10 @@ class Decorator { virtual bool onTimeout() { return false; } }; -Decorator& skip(bool isSkip = true); -Decorator& timeout(float timeout = 0.1f); // in seconds -Decorator& may_fail(bool isMayFail = true); -Decorator& should_fail(bool isShouldFail = true); +Decorator* skip(bool isSkip = true); +Decorator* timeout(float timeout = 0.1f); // in seconds +Decorator* may_fail(bool isMayFail = true); +Decorator* should_fail(bool isShouldFail = true); } // namespace zeroerr diff --git a/src/fuzztest.cpp b/src/fuzztest.cpp index fe6328e9..c95c3f95 100644 --- a/src/fuzztest.cpp +++ b/src/fuzztest.cpp @@ -58,6 +58,13 @@ void RunFuzzTest(IFuzzTest& fuzz_test, int seed, int runs, int max_len, int time }); current_fuzz_test = nullptr; +#else + (void) fuzz_test; + (void) seed; + (void) runs; + (void) max_len; + (void) timeout; + (void) len_control; #endif } diff --git a/src/unittest.cpp b/src/unittest.cpp index 1bcd90bf..74ba5b31 100644 --- a/src/unittest.cpp +++ b/src/unittest.cpp @@ -75,8 +75,9 @@ static inline std::string getFileName(std::string file) { return fileName; } -SubCase::SubCase(std::string name, std::string file, unsigned line, TestContext* context) - : TestCase(name, file, line), context(context) {} +SubCase::SubCase(std::string name, std::string file, unsigned line, TestContext* context, + std::vector decorators) + : TestCase(name, file, line, decorators), context(context) {} void SubCase::operator<<(std::function op) { func = op; @@ -719,8 +720,8 @@ class XmlReporter : public IReporter { if (ut.log_to_report) suspendLog(); } - virtual void testCaseEnd(const TestCase& tc, std::stringbuf& sb, const TestContext& ctx, - int) override { + virtual void testCaseEnd(ZEROERR_UNUSED(const TestCase&), std::stringbuf& sb, + const TestContext& ctx, int) override { current.pop_back(); xml.scopedElement("Result") .writeAttribute("time", 0) @@ -790,24 +791,28 @@ IReporter* IReporter::create(const std::string& name, UnitTest& ut) { class SkipDecorator : public Decorator {}; -Decorator& skip(bool isSkip = true) { +Decorator* skip(bool isSkip) { static SkipDecorator skip_dec; - return skip_dec; + if (isSkip) + return &skip_dec; + else + return nullptr; } class TimeoutDecorator : public Decorator { float timeout; public: + TimeoutDecorator() : timeout(0) {} TimeoutDecorator(float timeout) : timeout(timeout) {} }; -Decorator& timeout(float timeout = 0.1f) { +Decorator* timeout(float timeout) { static std::map timeout_dec; if (timeout_dec.find(timeout) == timeout_dec.end()) { timeout_dec[timeout] = TimeoutDecorator(timeout); } - return timeout_dec[timeout]; + return &timeout_dec[timeout]; } class FailureDecorator : public Decorator { @@ -820,14 +825,14 @@ class FailureDecorator : public Decorator { }; -Decorator& may_fail(bool isMayFail = true) { +Decorator* may_fail(bool isMayFail) { static FailureDecorator may_fail_dec(FailureDecorator::may_fail); - return may_fail_dec; + return &may_fail_dec; } -Decorator& should_fail(bool isShouldFail = true) { +Decorator* should_fail(bool isShouldFail) { static FailureDecorator should_fail_dec(FailureDecorator::should_fail); - return should_fail_dec; + return &should_fail_dec; } diff --git a/test/unit_test.cpp b/test/unit_test.cpp index df999fc6..e2fe83b9 100644 --- a/test/unit_test.cpp +++ b/test/unit_test.cpp @@ -237,3 +237,8 @@ SCENARIO("vectors can be sized and resized") { }; }; } + + +TEST_CASE("Test may fail", may_fail()) { + CHECK(0 == 1); +} \ No newline at end of file From ccaa3d0e089b07b04be235d26e3c6bb41342b2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A5=BF=E9=A3=8E=E9=80=8D=E9=81=A5=E6=B8=B8?= Date: Mon, 23 Dec 2024 10:58:03 -0800 Subject: [PATCH 11/14] update readme --- Readme.md | 23 +++-- docs/fig/video-screenshot.png | Bin 0 -> 1265041 bytes zeroerr.hpp | 153 +++++++++++++++++++++++++--------- 3 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 docs/fig/video-screenshot.png diff --git a/Readme.md b/Readme.md index e1118b4a..e42767bf 100644 --- a/Readme.md +++ b/Readme.md @@ -192,13 +192,26 @@ clang++ -std=c++11 -fsanitize=fuzzer-no-link -L=`clang++ -print-runtime-dir` -lc ``` -## Other Good Features +## CppCon 24 Talk + +If you are interested in the design and internal techiques used in `zeroerr`, please checkout [the talk in CppCon 24](https://youtu.be/otSPZyXqY_M?si=LLSHh8gkfCiQk983). + +[![](docs/fig/video-screenshot.png)](https://youtu.be/otSPZyXqY_M?si=LLSHh8gkfCiQk983) + +## Header-only libraries + +* dbg +* print (without use extern functions) +* assert +* color (if always enabled) + +## Other Good Features Here are a list of features we provided: 1. Partially include -You can only include what you need. If you need only assertion but no unit testing, no problem. +You can only include what you need. If you need only assertion but no unit testing, you can only include the header `zeroerr/assert.h`. 2. Optional thread safety You can choose to build with/without thread safety. @@ -235,12 +248,6 @@ We can support output structured information directly into plain text or lisp fo 13. Automatic Tracing with logging While logging at the end, we can record the time consuming for this function. -## Header-only libraries - -* dbg -* print (without use extern functions) -* assert -* color (if always enabled) ## The logo generation diff --git a/docs/fig/video-screenshot.png b/docs/fig/video-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..7e433b514b674000709b749b52dd5daae2359c0d GIT binary patch literal 1265041 zcmV*zKs>*RP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D|D{PpK~#8N>|FEfY`_?s985CU*nr5%CWFXHfFvZM1d=Z5 z^5$Pv-90nAH@mkNckg!JT+Q@!sIIQA?wOsb>+!t$+Uv;+FTMgFeDE=n$s|fBJmlOf z9)0Wy1<=j8idup@Jf6bWU{7XN_$?S@359)WB;*TuRe!P%6;qLsc7@Kag?x2R5h2gl zk&)lS{cT~U@&!5*crwVIRCJEOuo*z~(6j2VZ_^svFXv@GR&F8Qtf)SqA4N+Ta@f6Po01*kWOXK1p@v!_NwyzDG!l_Zj@PU0 zo#LIgXLBYvSf)a_s-P8CWWSn7>Kw$IB9lmn?Rx!QMLw#Z&P@T8SDmVonxDi{90|?i z!be-OGl90IHgq+2qN}Zo+7tzi4%$?k`UF#vAXO$gl_+kD2XSu#F<%_sG9M;RnS{~f zMkCH!GA?aZFvr}WTFX%7q)#M*&w0#q+f*)JoZ1?rlt==9M9;=YO zZG{QXW?O8V{Nl^G`m}^XM9zkl%{mPV#7c7!wS>$lbceuP%&hQjV2~v&@ID;r5bY0k z7i1g=eW;j<49A;{Y{;&K!S0+A@;wKQ{2uOa3*E{W@GM~e>P|&I9Rho-I?q-})#q7) zf<>;DF8b^1uqbHd50r4Z@X6=AQsg6mnqTA^D4~QBO2{kZ3YgE!VBXh;3JVoI|PO!XlofNhd`aldC+`SSjol2=Sgx{Ey5eYSXhY|+4n*3+ z+98l#0tdz+P>nSw5w^z@!#a~MAG zRMk$VaW@=AFM-jYp6k7dk0cE5)_0pf3k*HlCCOj7O&BW4hPMq4Yl!^~KmCuiEA`;_ zK@a>(LolyU>M!z67|@G(UuTG1WyqDk$v04K8^Jb0O&h_j>lbphHql?7O@BCOf8<+8 zQI7#>d{Jwlgc3?9Av}O`J2ErusI^F-OH!`9~xW3WZQnUWO`m2&}2LRtQZ0A82uK(qV>m zCW1IqI48dW;RFw&(HO$v2wGd)(A=~Pk!}uwyraEt5IeUxxjA9gnJI&yd!ijfmGE;h zc{Zl%5r@D;Oo$zfya6vNYbsDtQ;v%23WO@kD2{aE_mH*{+L_DnOs&iKoNya5y#umK zq?RqA@ZsL0PN*CvpEZUk!Yp=_YCq{EGQl|}oR;M*ZF2A^QDI&sP#h5DoQN|L+2uKw z&Lq2W?z8#xksbeH?NKaSx(uDo?TB^JScxeoG;s(t)%F5f6%M2`F)~3`#f!7kTbUPu zsu0Fb9E+OKHS8WItLP=!BQFEGRpUVFETh7*mqM!L%HfgrF^@Pyb}nm22OT?FJK*#B z;Pd!;=@4kZT32~(P<;|bDeM54Fzut>Bl6I^&_ic4(ZYkCwMArCJ$%%ivwBgwLhKL|6$M%v(9}CR1u}s?>9EUV9*GBsnxA_cBb6M{ozR=cvP33W2pX)$sXzB%R5l;FLCf{B$2rCN~{Dyh311J{jC9D+D$p5{c>0r*b|f z=Ad}EIkBX3x54u3U{A2va?9vM9=x&R4aX%Yj#nCEVwXQYPaqHq%BM-IM^__MRSsXs zhgdSER^qZYh8BVj%J~Y?zMdU&auvRWfZryLhYjfvrOT1zi?&8kzo;G^P3=I8BB>0!%W=+BIdX-P zDSJtFa--_lG0sopu`-BIO*ux5ABF1LDp@U0#%ec*K<3>OX9np)iD6|ox-!my9(MR6 z^2I!8_^}QRi|gTu(2NmRt3G;3H>Ul@5ibRj5LiO-!ENd(p^qqV zRi=yA^Mp504Jz4KHuUQvJWv=c8O+V=K%xhheZ7$9w;Kkt_WZVNIG_;5VCh;sknJDr zS)d3078{dKNAy)J3%%44WzV(BUUD7;ZAdj_#Kh3%(0VHZr0i-COG85=tnc zg#3YSCi;D^I^0pb3Xql;eg78%^4tl7T797ySfSo;=3hVd3-98E+k5-sK_N80SUMnE zJA0U;oJHNT_CaioNxx__2+wF)~2#m`Lfh{eq zXli1Iz$m<4u7h(h*|Uk~$s6>-j;t)Gb!nXc5($n^ibJ56KlPafJ&9dHVL*B@TW9HR zPR*juQm(g_IhX?KX!hOS7xiQylHHlfgj3@b*Yq@w)ef<;bCM33muNIBBFnl^w{R&s zn%m(|`YBzlCTKY`_5pTn3|nRuQmH(=an$`tgfK3Cm+!z@zdMp zt+c<(Pg_<^k zTh}k-YR#g*KAZkX)_!l>)>kF=6Q!GB>Zy_rN+_X(67q$TBM7y>*i|4XDIs4F&5vmH z7fRy_^?sA|QvgOVqD?R-6t@Oa9T!R%SY*P$zbg&=YVo7Qn9IN-R~_`Y5Cpn(Vqm#Witg5oq@VM{p$nw;zuD5+i#d}V%AjH*N>QAKSPg5^QPlQD5rO7JH= zX;Z5zdJv1pM6b#rP&rMd!czB7_PF4qwlzM`Y3egEb7se8Hx1+D zXIE*Q;_PB3TnJDbUo$ z&@+GyNl>X=g<2sniHgxQ{zg}!l8=?;;ta=5x$Y|j61WX2CJLh$_ zccH7L6K##HXlrUifHoiTmig1n6B&^qRr-s_IP-}!Al1o7B5??0(oTOg+hF{Z@t8Pu zBB**{%PRxQ>|=QJ0oi3wMUS=ghM7U%Meh|#4uQh}eV9iI9{m`b0|Unm)o?^1O5dw4 zr3Msk5A;{E;cZ(*0kOZ~*G~g_vrzz7e&-L{%;-lyWozT`>;4f2^<%0ykh!6Qqk>!q zcVBDIz&{haKF>A1lh3NASH%XW2XaMzYabrS_8VwGeoGW=C-hga-j*K-W%dQb@ouby z5_*GNEIeQ1uY?jxD0Za2aw^ifs5AxV9r?QpRp6JHgMUAo?^YHr(q4V%1@a=hgd#?M z`@eYXkJ{-rB}wzs6YTShMKt<0%Q-o4n27qpULmkToB?@-z<}Q;X{JnSeJajv9#e6S z+~SazFr7rGNv}tER^UzIn z!z=#90l!yPkf zfi%{8b_mP>?k6dz6et>JCq!{XqzNL#vtM2l@Uo(#6wDF zd{NM|WGY9d@~Oz4t4~@OGI**+P06j5C6rJ?!9ZP#3Itk|-cBS#(E_(Ra+kBe5*AP& z6(IEQfc#>xWEc!D?jBh7^+H~_R@z_53b$p2fl69)8Qs`EkBb<>e5pCjdL`8jG_=0|f zD#}n+TZZa!H3(IQknkm>rBa$E7IAjv6Gt=BTjZ=I6QqpJXu3STTsq$p1{cmgv5=u} z7%A78EYHGCidzom;TE4B$2KDN>PWD|G?q0V^(;AET1nBwP)FX!Vl;N!I>jNdwXOxx zwg>_+pA4q)?X0jDFweA8ORGrPv%ZM9{p=8!2m+%gj}fOpe?)d8z&)vAc>2w7G`bBVED)ZY9 zxzs**5(?-1Dxip=6hE@npF`_Llmar%LPOig-pqx$tx{S-p<`tBVt-rJ+wuc^ES$$zYw6{xlx0@>lm&YxO`itoN> zaLAEco_%PCKwcqmSt}a!3V~j|LZE@M$y}aU+V%AS$w)h5X_paK97jm~+rDz@QDG~G z!_$X9UCOKe@oHV(Dj&+nRG@5B2!YBVg2ABVk=)b{GrW3gO6o9FC#gm3Gf7S7mr}lj z!GcnIF3MJ;hVxs!X(BSh%Bf%%?gl3tJFG%%v+N`$B-0(_MUhTo z`wfR_+%~qLv#DKH2TXJ(;Ah7;owv^pflRtA%cEAgGZk})b|gTO@GQp zlSac6qH*Z+QXXENlbr&)uMjAl75gkOBb{qAnYm4XFYZOWD<-P~HY{#HLtP_$-Vl7A z0Ojye9(Lk0D+Fq}(~zxttG)WDJ{N40tPZGY^!U*jHE}e&Wt5esnnW-`s!?;NsyS0l znI%vu?sE48Q_4BN*_^Vq+68?Rq1W(KjhZ?{Rh3Xe;h>kpvG6e58<4{dZGVsp_v{nj z;4668+u(p8Pg>B`4qolqIS=YZHb*9)z~T zN(sdX;{?ITlOc?F`_l~+XAc)xPBhu)D^7Lipvt+H?p7cz!Z@PPB%mxIfwHnP%OS8< ze=?YVwp;w26NU)oup!B$&K$YaiTw8WYKOosEL+Aa1U4be4uM`?g;1?B#+emIeqLzR zs*rZdka~JGiK)C+3DSW$AgZe6AoV((n^;dIKYtpPofH#sRfe;~gK;1P1Z#rw2~Yma zvWGvRN@=ocnee6)W@oWfEldIBvyxrFGMO@bOc75XyE83epkb@MCuKW(lbzXgP(W$U zWCt%R+6quwhN_%XiZxc~ATDuqvGV#=7Mgm=PI-VEL`~ zsy;9)1hR8r+!IIAM=5?_{G@RhKV>{(-Z-M32$Ix(udL!qWram|m&6{MPUtyLI#&e~ zE7QB?qWU10TGQZO#el<8U2+H<7LZQrgB=wj^52XRqR64bFjZG(u~ka{CcgeDr65!~ zO#5}9z_%1xbub`T#sN^WW--z()Y&yN0D7~Ki5F{>)AFg=fox|VK-K42g8ZW=1@z&E zTyN0373vCo7RV%De{|?xcBIUQr4RaL=$Kcmq_*ORfcjK9A&|@&KDk}W`8BF9~|BOQE zgZ#-cGOf_rDm&bLHZU;*KNF>JG$I(U8!Lj3G#BwbL6RK;d4<4A`DAcyEjtAI#n~fO zI=ek4?2HjeJ!y_m9xtL1wL;*sWy{de(1>tYIRvUzuGpD{3Yaofv9n%_m20FyT7-k0 z38misP+0isIB~+U>oqy~`K9F$n4tKCtf`2YH;T9~4qv$sqsNazHR&u=UIu?CpdP6+ zQPVigD}hPfx;~{Fb|T`Vf~l9|ILz)r7MKSegmhR^b>0&4gLK_BIPG6ESMyD8y*&am6~PgSsQTY^t2d zq1z5_TY_YZCeggK2`zQY5NQvit207#fge7PU-F3t*b$I)C^~hkK6~W^H6N(CR%ez$ zPIHVWh9Iv#S{1^m@wG&w#Yr%ljEa*W&z~Ya7Z&imtDKQ}z7l>WDFb>)CYW|9IR(0q z^WBa(Ko>YX)uX2szLX_oM1MN3XG1SxuwYGW!vPLa)8@O3a9(6HL9*n1_UT7IWGvbb zo|+;r^XLY4dpVV?3U%ZjgA|pVnp6spJ)9SM(-E54GuigXQ2M-H^=f>vSphS zi@x_qz#KdK2mKSgH z399h}tAgHgD}Yft3|?Lh2?kmXR@Y`>TvB~OVDPWT1&%z=0YyIyM%*0iY)@*Ko63)& z#7RTuNc$SdpTo5C+wpcNS7gv9M>{>w1bnXI#DOG{KuCWwxTdxSqeqRB6#^Nla-{O< zHix7O&25u7^~vDQt}vRLm!YA)5s^p)9_y2s9k<9lHR2L zq7rK9JIC{n;^w3#- z?Mp6as!*rXeUU-u}c%unK+au^~ZbQ@3Ms&1xQVKf* z^6GeGsAm4 zV=Bv^BKLSXH_HRW<8g%1h0zm7OPhn$L4+#GL`O28NLO)w3b1FQ%ugOE$HY#|%8{9( zv!I7$1#TNrf<2-&cr{e7W-dL(dbHrCiTtDF5LiM9d4SV%c(}khQDl{#5_6eUghyUx zp8?RluowV7dBX6Oe1M?)FE>35thNg3f}2M}P*vwqfV`ss+D;OtgL-dK2!nOFUi^Bv5#gj|V4bE)+nlB6^n_+iu-Sco*wnP%;ti-5MwHQ5m zH2mxkn9<3@8MdMPdN_@L>lz8vF%pTQv$IPa0_*A<5DD{YTkKpT-YiNE&9<-akGtL5 zQIPqX6BAbMhg=WuOdOqX(E6QDx%9cL1*S4<;;eBbl5sjF;0<`-D+{2arV16cRVc5i zK(L|=@KZigqkQ_4H+JMB=FF>*m_kr*hdLjt-2TL~_n4WXPI}I;D*r0C|iUCtBw2rMakjR5dEcRG@Nf1wu7J zL_JY1AW)^96+7z40wuHl4}x1qzB7z2T@VPI*-$^An#^S)E9&Nw%xA1i2Hz= zo?Gh+K}Y$XEd~p{)UD1wQnZox`v|JO5Ue{tDBhMA4oZD6;*nDq4;MS=yPWxKVIf>E zi@p8XVI&-6Nk__!Qwf8Jl9BWtJ?ShLo#y`)08cWQ-y?2N1 z{h}TNs`2?(K@oR?I8lr|$mr4zA_n{#fSojA@tBG>ZO`z2z>amM+x*;Nc+$}X)xm*O z9!@6?ULCNyrW#{LkH*+BW8w4pklAR6SCNvK1Q3oy(B9sGrluy;)iof@ z4uRraLxq^Am^e)e4=U@Tn{G8{u_5P*RVv6kBIN}n)tIWyGfG~mi@}bHESp-rQJqU3 zk%@@1DOV*$?7$TFk~V!nprQ=pCXYwW*jo5Pes}^N@LGcG;3Lj}oR2o)MEs-%;wLUf zT0Dv4XjPScl(0$F`5gI6$PbMExqteCf$6{OTrAlrn=L2VA=@O`y>eJ)w>177F4=p` zgK%>P>VH^*&W2Wi{qMM-c&)KR8r!S3W8rAH_S71kPY!JViUWhAlp~-z^6HvAa(qD_ z#!eWEn(;LVRQchn@FL+=bCVuHW?1(UB)^nPP>XndKtJsR@FcuwtZPPd{W5eeYe#2G zr#J+v@k$$2sp_S|Ql57voCYPHE7$>8bV}2Rng@vm%?}{*@d~4%1V&FDjWJWkAQ9wM z47Kisl-A8rR_xViP7IQC-Ux&36IRu^#I0D7*<8WOT)l3kP>P{fR#~pT`mi^3stV z?eNyuA-MQkcuLNJ3VpGbA?x+-;K5VEFj7KqMRnwNGqV(Sd9lL*(7nD83~vno7W4~auwM6B~T4GZ#T0lMZQz8Dll%SDLd?E^;k))%5~@;z$|BuKw~w84?0g}hss z>4Kt!X?gK#pb(&L14?}`QjybKP2FGE7SK=W3gLp4PX!ly`+I<>M;<5-E(&{$m5^T~ zhr$Gh0s5kZk)b#>4@t0Sk^klog#_8_=-UhATv6Cpq0|?*#RzJD0jQv;d%?O8k2Edl z(WTqx^YO|}KKOlJak8`8D^rAWE>R&jm{_NdGU>~~*O4WaubrSm_IX)Z87j)lQB_@q z>Z&SPU5Y;??BwOfIGv+``OGfIvMTtK!I5wnt!?dSS=J)w$_a)UDQ3#ygrn@Z>`1MX z(JjW+&P+XWNU4uMiy4o_5haR|jPZC0qHKS`$IjRa} zRps(IVRj7U_;`|XvBMKze56X+5wVu!^Qk79h@qs^_C%3Vn?A}LTAjayp+}0|RY#>n zny73T+`e+u^4awpHT-XgP~< zjH9s@`wU3e3){WoqSC}^Rv!~y8d=^X0_6dWpE4e`V{3sRrTgMY2GnYO3>m)ZjB^Fe z4_v!)h4jM{r+F^|Ed8+#^^5Bf57XF=B;ZZ3%Pvc5w@`(YMzRZFmvKU3RbwTroFL;e zmsHkPqGD7zD#uo#YHS5!fjH$6w_(XIPJvXGotAluvsW!-IAoDLJXfW`k;Rm9Ld}VM zRK&0x0;Md^W6I5qKVPi7QKbtU6%+_OHDgv7bgAOCV-T6T5@Hi3%eizKX(ia30|s~`Y*6}A%Y9Pg>PA-@GVyKr31D7 zmXIfy31m3IX=lz!zZ)ni7n@9(8}ovGbb=J^NBKNs_)0!N80P&qV95JKRJ?s4b7%oH zKkHmxf<__eD6bdGL4)~Wl?(c!0V$KE3g?gYynxzY8iDzO*8Fh1As;~~X@6ux9)|UZ z;W7`FthI!JhkmUkp}|Dn?{`WlXcWD9hywbeg$!k^%SA3cDRa?xKz^aRKfj$YxV2~J z-YawMHwy7yD!+YS2(a%(@(2>4Amqp!Mq1m`+oR5dfdB#lKWb`gP+L<2{_G@A3*u0t zf;mCC%rNYFBqR*d>sD}0a#U#9A02!?FZ_N#e7rgyJHV+FRX#;X_qr^|4Y(>Gt}=AW zmm4WXfml3&Xf&#v4LMz&4|3~h)2!v0X-b_LpqNU~z?`Y#P9`cv#h8}cx!IwH+s(4I zwzQ$OwGC};?TADol4cwNjjUXjqgd2LG67l5jF0TjLpoq5z^c*J;v85zrWWN@6^JBb zh&i1CsXi8npC8Dn)F7m4_-CMv@A%eF}7 z3va>?el1I0v$;pp@g)=BSL1Y**$oZIrpzZk&Dh@+tLdXoZDj1Q{K${|om-(3sY^`gcvw}ZvtPm4zqQK3mwCt>396X7i< zJrl)!)Mn1lpLLca@4cYugyvEz&qs0mqj*!P7FC_=6NZUbhcuY-%wG<~I|d(J8@QnK zAKL((@obkMy(fv-#f|e?bvx@z3Fi-k>JXR+l_9+H8i-ArreicjQ+%xMxfc|wMj)k2bX5T{DQP!>VTqw+f_`u zf7r}JeUy_Lp3fHM{n}aV?e7sJq5O%Y1B!xfb_sKsmw0uy7D5SnE#4D55PZ5s+gTc5LDIrIY{f#8;D~AvR4lC!0P+1wu%7Pd>W(>v< zdD(%5NM;7wbuX6<%K1d)O3$0AY)^BLDyJM2+36)I4sIN+O1eKk>i!BTy?#f_*m;Hd zTmBH*vBZFX8`G)8QBd;gq;xn+rJWF53T5KMP$G~{b=%n?FcOKPsi_&w&C9TCSqr*4 z!;+?CVs6|nBd03Q#4a*P#AOx0Xgn$_o0V0Tqq4RNV<(J5_2?RS0zSa+LEJ+*iCBjT zRyjLlF=xeAj*72=4oiLNOb1ipEK6B5{+<9{IGZto_OVXJo(0pGDdx&K<4emY;-VfE^>_i235u2VN#FLr4C!ailYfu8s&|;ke9Q!c*H7Lqhsg4yw6XVT$vC zDc02mUoZf#U+aJ!pA!l2r_h5{<)|1{4FqX!lk1wPfR99)mqLchbC78u;HeqA;-FL& zojeU)tE^3vdw0X5exmpw5b+?CY{RA#IO8+?rGu~Mm?AT!@mW61~ol<>xV`+c`<(n9QQ>-3{!F51jsw2<#}zss=Ro6 zfRJ-16oQuWJzESG23NPDwJcSrhf8@x{w84yg<#$J1GT=m6_96)gnQ`{vO%8t^oZeN z2PJDQVc@|J(oJYEk@x$Z5(*m1Jc`&zdfoIK3iRfZk!RHxanIXOZ#46U!L2D7j2SH}@p*le zlN~E)bGpMQIL{4773a*WLbP{GF(%IXrDDtIJSf@7H|2OV^F3sNRM zQlPjgPzLMSpKuo^BA#~Qq!)3-VUjGoW1VrBi*Z>M&I(h&Aj7jp;~?Wq<&w%7h~%Pl z<@D!8BpgBWvSny)T85_PW^{IRNjy6XS{$et_nqt|N{8jhD+Q|0e)66jl*Wu7gVE#1 zqIz^K%Bm{F@h2wfNyPc9ko2R)uyH!nq&n9)!ROA_lrYpt(SBNYr1w@P3{?+`JX%KO z*vhBMS~}r<8UAJG49k$(CR6^%7Z#B z6I6LwRF4;MqsLFb-iF07Whvs^kr|M{)Y9)EwJCtlx zA-6|Xa3mRxyo_9Aapn;Yv6uwBbnRlWtTF1Na3YFvQzl^QDl5WY6;hey0gS0H{g_{P zY6rm~uyeI_5Ls>o37ZRzft)94V0A3R#3(V1TZK{9S%OsfQHn zB}=4E=FbOvU8S)@1idYZ$#kZHppBd=qDykoR3Y?mUS%laIar~nXTSmgX}{C~ z#SQ8I;bH`Rl#?z-`D|ftubl&qVsC$sAPGfC*_BX2eo*8#*!z~}QX*Hb1u{XUzm+T^ z6hh0LFDM5O?sW`}1s3#$m_WKI-XTCuFO0?)aTieIP3uR77VHtP+9S>*>=0O4fy#;s zj2$~hNLB}w%*wgMcKR|f=gv9A=`zDevtXU*jFTwkt=1jgqPZl?r4rQVd=*qo20q=8 zCIRuY%F7?OmNs@SqPC~Bfxt0VO+7xhOE1k(^`t{ZIrplxxej@}K8jcE4~L_&G9Wty zHa0b(v!hd`OIx_KZiCZ)g{g29;*FgFBe5vp^PsA>TG6N)RMb==P*En1g8Z3HUP+Kw z3v`xO$P{OerX?lh1KG51dZ{NUdm1D#Yb^-dBN;4(s79WDIL;v?+qNG*B-+^-unnzC zTF|tFZNCdae@L9-curu4Kp)vGfBjULiz6U$Gsm1D(hyXBo(rtmhFK-!*kJv2aLY~C zV*L2A;)u(i12%JsLu^+>B4M0369w?j$>A6LCDvLn4c~R2*48ykV>LR z`eW3Q)LC)Ad`dVGh!7|0+!%kSI)WIDxG*a5>^Ea@>06`mMVlA#WCgJxgNRburbbkJ z{t-NhxDum;5(*6F#&S5pIZ>pG-mBL&g}CG`_>Q9`~HHDI^r1q!mlUZ}QL+Q^k}zEw;LC=J8GLT;KJj`!mu z52ZfHCk9jd{7iA=ppT8A>r6h?AeXs33sBVU?-4_DBVs+1E>09z?=XdxgLz zULmlP(tK*QEqRVcJ>;-mIdYQXNPk51BShS%*eR6Kya6gti5*JqgNBcEEhC@2@g%=E8i9y_kA596<$^uY_R(-CYCUYpERBhSaK`lp=O4k>XS) zD`V2BH||AOQzw>uyBKXtTM_Sy(;UYBtZIckW!L=GQgvjT^zdhNsk9n-sdeT8X349C zvXdQ;Ep`Sx0H4@gzEuofx4K<0~HBNzSkQpY;` zaMegU@^4|eTnC3cdT8OuS3m)y80@dWAstZah1}3zJ-L-1DOa-+`i_zfhz?3>$Saik zi?|bd-Sm;AtD^6JLf%{N@`7^Uk-d(=y&Ej-drf(vUDQKBH9oH@C~EyPj9bOgec%S5^pAj#He6o59x0PD(aZ-?4L$oOvXNZ0y*?jz9bvRDURlag)ZOcKjHW z*H$4|S%#QL+y`mn-0VciJcuNo!)zmpud*2;b*hmfND0FQS)IlDhxC5}SI)0&H$>WD z%qyD*u}B=@)-JTyx1sTedW4%g;Y<1{pR#*)LlcN2V4O;*`AfwsUVL;1#b-!jQIzAf zniJPwe{KBd;d>~*THVh2R4|VpT_#sVq7j_&=Zi7#$8QlR59!!c@F#b9^Ta&zdF1wn z7PKr~CP!b~M>$w_N~iI!Mz_+T8QZ*2B1^`@)vNXDh78S@PlYqF6R3bf#X&lkgD3(-Ndmn zpayHo7bL^+A|7bP)mxS0_p7{(HeUomA5|OogIA#}_bfv)T7{^;9SPqUbX9E)@5D{v zAG187bRJB^5p1tR*OCw6U-}H9@p=TjWn`vNG6rCA=QzxGXfo=Tc(~7l{Xn72Aj5nn>mPb-dLig0zjH+Y(6jz}V}GKF9O4#+zO?LI)W9hj?_kujgw#6IXri^{@_<- zBUhc1O#Bj|ggisKpm_VC_bnfRy5g62MT$abxmhJ@aN%CZ&|D%5`a(<~-4yQ-pr#i_ z_b4kE(`fke&gLiuv}hjHWcT^-G;=a zJ>PQ6mmMx8Bud2#_iodfipctUN0%{09V}g-5}bQibdw_uY3Vj6EwAhuq-}Pzu~rB) zj!(QoU z&VUoAOcKXH{v0U3D`UqW;~*qXN95M_i(L#UqCs-#XV?mX-D0nI<(cbAlecJx{~mVLm=Bc*{+^rJYt8+DNtD% zS7OG6%99RLQX$nc*^2ox!1H809>ZI&JuNG#a&{h*UZ0ZR6yNFp4wB=g$DM`g>u)0A zY+$Fr-A_MUJ{|1!vx8qMdl>VK?5yd#DL5jzR8Gaj#w$m5@d5IxFx z}mWvL7 zSOCpGHlg8%21J)d)M};_ubcrL_3FazVmM+mq)FM~s1;b1@<0y|e(3y~=+=rad+AjA_GTh@v5cV7v|PJR`kXeW)d1bneh z8b&e7RD~$ziH%(gUk84M_kXIv{3Tv2Ztg@|XDhJ?pfXf}31h3U{%Xr%+jXX4Y|A@{ zFPsT)`$zCaN5kiD!>qcmou<2sj3W5s4=A7ixjHYQAfS53edrYphI{k% z1uz}COeTfX^!`aA7nI?O;ckBefRr18bXbNwc#0YjpTmA4eS>-I+_n^mndJ?I(Ei*oc#`H*|G7X~K72ZA1frz; zWS@<&q`Q#-)&2SH%fYQZJ9q0EF(fJ|#4D0%cz$glpOBW@yS$-zdw^igLBm@M-AA}` z)<`B2;7`U@@d|-em6$MoyyXy>x|Lye=A?L>?26)pz92j?AleZ{OG68mE?k1PM*ebX z626!h0sX09^{HIu#~a%re=1nbUCL3ALaH*}Ibffsr`Vvf#GIW0&p-E69CqlhD2jOV znDOzaf@wUM!gSpE{|=!*5| zmj1X@90A!$(i5k;Nj7OW&kfv2b_NuOKy{R%DZ1dH%35N$v{v-QFnJW%5jaNrjCtai zFl8dfPaTh7br8X-0OEe`C$5+3l454w;Z05vj0VuWs1c3dHz3-q90ECq%x98E*PB|c zfKyVVyXVz0+96OoYijUHfrlM`BF3&T0V}S*GJLg}RtPK{ct(v|#YGjlBQkM0bgZ^E z^>(Uvsd%jt=RhopdrsIMyO&>zns6DSfiQes<>>N9;HQgATSXP(V}6Z$-VNf;|INXY zwg_6|lqcv#jNMYX!^s%PB2XO+VdZK+t~>cq?6CSu==}Zxc)qy;9Ym)Yxe0Y z20-`L1#EaiNk6>X&@bqwXVyUOy6M@3-FW52@Vg_AcC2d7U;X(9wLOo`A6D0(!Gd%_ zE_xU&T@}bYFhI!Mn#}s^wPtmJk)pZ0k3*_H&k_tGa%tJfZT)b8x(yjw{m|R_X~bIL zfHW)6E=d=AeV+a&A6n)VbT^E9l6gAAtm10LPPSg&86mQ%+oJ>sllv%?h>qv9ztj$2udi@{3J>^&FskNmE;{whh zei8=k*rJ05-HZ1m3!!_nZ3$h{GJ&T*%OOx4_PiAGAj~TSYKOpvh9>pNV0N&PbcU*g zOPZC_*jGq6$x-p-6HXOJLhYcFh_geWa_){OF8^ z5lKYEIglyIJ|e0{VyKQ%v*nG*M^mpeWYSA0L=5cf$uwqU1jZIkJ!(}t0Ev z7*PQSSZ0;h9enMQ`I?Uc4?g|i6@;jNoOtQgS8>{97l}h)aBLY8{0ZB>Ad?^i49}?X ztM<97M5qj{8_%Fgn~cKi=NKXilU~V`;G;?V>^(NeDeJz4(M?}aQkX6re(~-O&>(6< zHQo#S1}EJ6KIS#iD5HrV0h-vO)X@pH0_K~5kJ$SB9z@9M{P9X8>gsUjF+1U${r5t} z(#H||{8Cg<=QMaH;fSZE;OU=6kpS$at4%N-L-hx5QuSRd_6S5@yD)VC3>ETIYk^^4 zuXIC?4C3cviTQ8&KuExsl3@%Hept7l-^i*0zMJSRsK^jqw+9S)x8mVG5B3B3pFya8 z^;hJ`MFWGSrQtSX^Zp9(mOWtGC2XoTo4j3XN*x}>{7IY+6IZVrKsjqDKE zi1v0~AyB0&*2?cfN_upd63kItVnhtus0^3la^ip!r&O+!>W(F22uHi%@dFcP;^@5AteZQUj5(9pNf_73NruPlrk?q3~dOW?*{A=3)S* zKv};mh3!m2l*Flg#x~Ek?@f3SZtgvgv)pzbU*ZtRM4QYQ)Glt4a*{NSb2gG-HQL!4AnJ`HR9%Ju(fFwoFk$L=#Cl^y2XC}0u0JS*NsI15B$!07iw0PTE*_Bzw3V%o%YIlJk3RQ37Iuaa4^_e& zk09XhU0yiCh7%=BX_`bOZ0dF-P zTu_UnURV_|f0+1{kxcxZ=`I8oH=ttPXCmu}1kXabK(b{v{Gosv`}vijgkHe4s~nMz zU@lge!miJ=&p@CbHb_PJf>Ofp1>a0%M&J52ujbqbWY@t^pm@iS%pV2N{Gy)6vUb5x z>1DXh8t!nm!VYKGA*c4*{&eZ}$4SC*W7<7#M77kyK{f(gR;Rk?y zL9RdH2L&Q?A$LM1*@jAKBgjBYC~)L+BFHzW?Zw>u0)ToiRqBM?F|u@}y1%&lKi{pF zm&}6>_d3j-&fwkg6#l)GydW;(A)sE6=NZxkMcfG^q3zTJd$il_5f_YPT&)mTRf%e% zi4!LfO{8o|`IIo{S7lT6kjw96x8L9?wj}A>Gi$`3z;jpCJ!qn)Ng zPFM(A&{%+?hx24&Val^V0(LqtdwR}+~Pc3Mnt1HR)L(S9eGtjpOo`j zj({E}N@OlccKS=O>t7t+fCs^xYCgrr1mUxbHdn_ zj(28tLwa#SPen=?7!1UdX(tEM^ET|BloUEm#>w)cF+@9KXsd6-;%^tDv#A4tq+jG= zTjisV9bkD(Fbg7%OK}KfUc^+bR!0^o6{z~zRnF9>N>WhY^YQRhLzL&iB%;v>o_zFv ztg*&w;Ocxn8Z$oaSf*f%cRzTzHh2P@0H(r3U>IynD?Vvh3 zlUOu=A(oI1yb&)*H3Y8PM;U1RN&_j9bmFD3aRlTA+4#t{Sq_0*lwkLPIXco;_)hhabjvY1b$P8c$0DnWWHXl zWTkno&FtJEb0L4CoQ}8s?N697eN%*jsjC5|fY}m1)&hUp`Tfs7!6om zllQXn_3(dhuYw2P<>es5WK8YwCxY-(M|RN#z!!{=;SsNR4COQkqtv+`Z;${6p_VQqHSh)6`6J+DAR$zJ{673G z%S7H`1yM0~6OrjHiBr1o6;gV;59J2}&T46Zr8X%~G7T}zL^Ld*MRl4^iu81nVpcgq zc=V-UzcB#17dssQKAE7$h14(zW1>ignMUTuW>8q1qJDW zqE~-US7MXO$6PK}Zh11*kPE-UgC%vZY$tQ(zTOLE)n0E{=D8d4KLrk}yU{)%ht`d3 z1@wUYH@+a$KR`nCbvntr4rn48EE#gr$zaUG&ISz#|6DYbxgu3MKRrDd>g_G^({hDE zXn$sC=>}0j7VCjhL>5{5Ttza^y;RsnG9*wcC}KtQsp%s}Uq#>lgnUm!gRB4Ccw6sg z2MzhW8^{LpPAcz4kY7;q?YDC!3^}ZKqlMcCy&b_Sziw3`)gf@wq>0KQkliTcv#|`* z_U^7c1{|MFLz3nw+|8!lCc5-2`Z$ot_1AWaD01Q=FC(Rm20F424d81aJ7zL3cHU!R zM(hySWE=wP8`0L$X*)-8MO>FS2eBOLoR8{Uk_6m-{^Y2va>kB_)aS|(NogkONsuKZ z5KqJqiARw1doXtLIETy8pPq|hl7TQh%6H5}={oR8i{Sy>s> z>VQOERaQx2W4AL8r?@08I@0M7XzIy~#+D`=dfCaSsHx_)|73Nf@Q8a_wkpX<2 z;(73(1NO$rCmn-vW4o^g*bU5q_8;ieC3@NQj*BqZW*MN>&0Fu_<2B0d&5iLz)I zgMu&iYeVdSQoq^bd~ zPch94RLz;{9}3;}6(pPbQxf)3hiYu=le|Dx&K90$L9!q>`#1kx#ps@|hqiS~Hu~tH zoC{Jvb>V~?FO|>CMfrtkbN|u3^_lsalw4~T-aTY?(|$IjvUAv*?6Os6^6CO^73<<^ ztY_lY({*StK|*J3W4_TB*|;NeEJqz^9!zwfH*!U~9k>|+!O}C|OZ26lfkv9#*3k)) zQ|o1LWzN4odNT?|Qv)qBF&!)|42rycfQaB?W^R_(f24CA2n9HK435+#6gLL_(lAhF ztK}T%QNS+_@&&#dC~i>ui(Cb&`GcW^VWik1wt>Ox|J;%3C&a)Za|MHa`B4~}TKWd9 zo5Amdf`#4kJnDd|F7E4bVYfc(2-bq9=CNBNZ&eZrS#_zps>*T*Wa4pWRtQv3N5d=o z{UIe%MrW38VM{2J;>_WWT-w@O$nysIGu)kwNJDh0EWla2TV;+oomIuSaFz%szs!(T zv9x2RS^-cw0fob1G&VM)v7r(5_58_Tdxb#D;fWx1Qk<#VtYE0ld9R)+aWr?g`lP7D z>U2I~4I~jwM8!GE6Y#=c9>l2eqfk3;G*K-oYb%lPu_I9eak3}cGZPYpd8qPyRJCjV z6vcMLaptI?LR`W+s_cUF^AZLZ+GP3&Rao}A!>m;_9bkLlXkXF?U(5?{)PvUg7JN7V zJ2WkALL!=gCr)<9pT`wrwVoKu-aZnj9xF^>$21cuVcBU?i4@{6S*h56671{8)vz6h zX={(P;hT>>L`6k8yu3OfB|4kow8h=d{52I;4uPAyt`JCY7SMbmkqGua?`V`)R-w#S z24BL9=DKBQUebg{q817V5rBurITw=Aq{Jmb=}dSsGZkst%?BQaUJlbSPJ(kC8-gx=R+#CT7n3ag9RtRK=K(6bZ&p*V) z_uha&1?jjqPJt}m5Wq8f!s@-QDqdcJ){V9h8AYiCKN(BZ-vv*s3cId48V{a!06ZW3 z8Z{MBc-lQkRH!8#A8p$fC;y`zO>rNnujoQTN{M+eDq4pxPgsiTBwb)))j0Us712o< zrj&JHbX5{_mQ~=DC1fB}SIie9BMXs|yhy~OICjeoal`)|h~!77qN4s4e2?|9@?V1p zR@NdSLWXFthsCkC`omf9bt4l;CD|?-sU+IMVUX|e`Q;q!(BX$gk2Ukw`YJ;}mu}TYg|9Nwo<}PCjM=H!HOz0|b(TZo-eg z=LMWQy&CxX39Pc)mGYknN>yq}A5Hrxxi=&rDBise)N!h)*) z=-YK5-@518Z96VtUF(Mu{NGV-GFoFq42Q?5Ghff`vHF4QvhKJeo@XJl=2I_!r#Db467`pU}IunT&_)M7-MV148?wsl_qFK5o51$O_s z^UALCL4vd2seV~PzaU}c%0WKUCTWfpn$1F;c{9NRXSDsb`KQ&(sg5m`=zj*If z=y#Dm7B#5(Mico$zU$8`hRGb1`#iz#Q4FYO^$LOF5LnF)fs-(KveO|@unO#VPWqZx&7HCl|x{(F(S^(jBcL{cC^EQodH?4$NzmV z)>wU29h)PVKk?*EIRw(AMGbu7^%rpKQxD4L&%M;2g$B>)32TV{dV|hsDhLBdx{i8M)jqxd?y3bwqZ2F+!Dcp_D#q*m&9_M8l0 zm1q}UKmKb>_P3$2BZz}v8jVYL4Pmpud{N}zK3X2Xdvkes13vk7v^N?h-X2nR5)rAm&fCrBlpHutEbt`>^E@76&K)vFLTvs_e@f9{J^(B^?zI(0?!kNz?oR(msg4`z-l<`+=H>o_y52p z56;)J^-YG1&2k0Hq-(NwA}f-s;!u)l)$d5Ae~?U^);s2d2$q9n=l?{z>_IHpZ$%aT z-E%m6*Gn+V%Qt#%nECYnScjdJ@hu)%>kynP;~iN0(9>|t^55dFD<8ub z#3Or?`=Spm_ukiF*5zAbJR0!IdAs4b$7#;*? zT2Q)H^YWQ^cJ^*qg~s2%*ZnolGY=jNq(2fk>dvQd&K6Zb>-)HB#_6~xJzthKNt1~W z*SF@uzoT*e9qzvRaeOKBKl9);$)xhSLXFoW?|c$xZ%%ULj5fS|%`b7(gM)dEXZck~ z#5aw|*k)%VQyc8O2N)>WKg)WC)NjOBhXI4R_Fn$@%QChXJhlyfd9nL^t88;W-23K9 zm_e86p0MDtZHX7@)`eGZ+CLz$`u$`az5MsM=bD)={az@@Q6|%-gSbuKQJbD{w8_Q( zb^gS~cVX_{^dsb(r?J*PH&FldP7h);iG6VQ>nG%72aJ|?Z^jnKKPnHIM3y{2+3&tN z%iZ$sEp$)(7>P^vTab3rKd2`4qTls#ATiil9SoU!1J%CFt(4yr1B!Hh{nfBr`JQBU zrVn-xmoVHh+@{vSm{anr0{dQ}I8oG%7pVRp3_T1Z-4?MKaMNiPP$&690Zc;0e77R~ zU&Ld;G(N8?kjjg=6ZqMJ3dp{Y(AT*^mD8=#|3t~F16Eg8WAdblm^^tBrSh{0cH~m| z9BySjAag!R(Ly`J*~)NIPwmwHhkXQb@2P?e{H^F71=PnrfGUe>9kEOFIQ7J^B;E^e4|+bydm{P$hBLhAk({E@Z-zVjoKw zT=f3;aC(963{>B#cy?kFJD{UK;X}B&3-#aCp=EIsI-1%MjzqLGAlbhZaPsT^+&;xF z#L#q%F*^BvupTGsTfz<~hd>@i{2rn++KE^whRU(!`1HA#txpC!kzW5EXCG=M zaNzcPV)J!2(lH9@Wf)v$w{s3dRe3E!i6D|4NqqC=H~9YR?@3x8dp%#}$;NF~%B3hLHMn4JI(u~VS5mySHF6Y)4A{EgW}1o3zmrmVISR#<%n1S+ZS z$^hcNn5rrb(hI~x)nam}Bd-wH_#Mqt-!4UjR|srtmw0~fpYohPjWJ)F_&x9AkKBtj z*2v-v=q3<@dCT>i+UF+?`u%wF%@=X&GktUh%v8?+!#GOzieh8O>*BVYF;5hT_%I~| z?6|=ih}ORhPpFJKx(i_*ap55De6teGzR~c+Dv=0;5lz;T0A*A zAe{80Hr@trJP2>B0zoe^4kXDWd2$cYprLZ{5Iof3OO`FhYjeLrV9Zp+%f^vRt(dVw zjQH})JU zYYG_zBCrT5LHv#L+l#Gv|__+)I4uoHoK-b_Qgqyt2)>5Pv;)KkRwf8Myuu=Nzx+3g#hvWCm7oE|d*wTqUsoE^9ztZO3Bc zJr0t%)ayV$VNc9MM+DQpZZ<{X6aV-JoV5S$IO*Kq<4{iQ^Bg&le*|KXu#LR)btia z_}`{ncnu7(>`XMy5UXOASC)H;^pJr$eGt7 zuHTZR`zGmbD|<+bUce2ZG=WQjypn-inoTTza*K1QO?&G$(VQTiqsMQ;wtv-DT}HwRlv9V?>&Q$o zL|R<>+)0i(fPdq<^BG!7xsT|giM5lTuYIfAlskxb-JaW`_yZ^$iwH|h&? zS+_cej%e3I1Je7X?_}4RTYRph%k$PFFSm3PmyMkHOH@3h`hQp<#csQk4EGG=mfw(D z{uCNixb;nOQFn!xsDEVOmR6wT5>-q@BO$LF4aB^W%mXY#5tD2bELPGxt&RMVdu)+V zdZ!d3k({}21}bdsHRQP%g@wEx<$Oj*6{Z~BD4BuW@+uNOT4aecVXiY1vGWVd;gZIV z6HG?V-jix0JEmkJ(Tn8kPD)2al!tagZgOVh(IZiL{)(2!>*OP{QJn4Sqhv5)fr;lo zF0aCJE=L_O@g~tChC1?|T1*=|4ta$TPaqbKpuMFP&Gk)KR@aD@hGxV&Bk(7@2*Qt` z$1k6G6^AZ9@<01pZD8jn;-e|)bCSm*Dr}LZQ=^j-4FT!?%9+y3+UHh3%8~4f(%F{S z)y*6CBHj^0=dyORG&YM}B@%J?^goCz`&IUC*#Sj~aWZL0HY-jAB(K>dEU{KzD?btY z2qrv9Ay2@AU|A3~wYBKz>axma!@n=P9Ty&cisb!j(YN^H12^H!zg~v-KL6NM;fkh~ zX7~^wwfGR}ilD8fU7q{Kqj7QBupGrS2leDwpR-&#S!L1jI?Z~Y!+Pg5NhhAfgf*(7 zIS)U9Pr@JcBT!u-p8~HQRgGXp89X!=oqcJOvyX{SN}7X+zaq*n8M#tbuL?Vfnw(7F z*1ukx-5HQUc`Y(^;MxCuL}hE?DW8O7EQSeH z5z3d4Nj=Jg%d1`n@#l`Y4e9O5jcl~McN5lJe}*`+t+gQ$9ar4@YCOA;Q>(G%5vO8L z^NPvU=(5vWOBz8f2I?2#D~Zx+7VNy;#I%TZL~8JAD1jtsgCj}SegBNBf4wGZ5Jugr zci_++H&*Q;ryVxoqXN+WWM;tSKIenIfw;NE;jgB?qhXtCyCb{V_n?&)^ooJVVR5eK) zfdeABsIRxuZ-x=#~u@lzYd`GOi*$!Cu zhWF9J72z8^xyiOzZ?m6EI@1Qz55Wl!^Fs^%q2lB>FeGg!#a%x06_Wo|JbelMz+?G2 zh4Z+yIh8n{-Bv3iMYsJW+S9>}(!o(yS~ltpq>LKF4s#>uGbdj|409wbczRo`v-!`l zF8BKuJ1ZhPU|N61F4*@tz~HZ@`>i2A)QrQa!*cEg(9X>!=I8c%}czPRU& zmoVp(nYiarhy6>U0grPXOtXS);3_=cCvn1VyJ3SFzYtn~`mPqS9jv?AVYqV+3q}af z!@n<|Np*8U6_pq8Wa?<_;>~fIA99TxLQ1oV;v!Z-nm$T2 z^u&FT7FASa1q*Mpgp@w&JBf3K<|9rC5~qpZ1&hasBJtx8(s^CUna`aln|4Csb2lDD zl$ev#A2IA3aaqHM&NGr0)`g4=>>Ox02&NO)Mat`5e-32I6R~5Ur4J#F*J0`&1q#tQ zlRi+4$-_s*ixYU`)NI6?agOv+8Ft>1f7p&E;G;JAlU~HTqG)Sw!IDLb@#D8YpkrB^ zI0S|~euTV1`P?h7UTB;Gl_Q|?HL#rlJ+=d28g+9Dq;sS95)A>dt>jF$y(&_1rk|aC zE`7;eOtuj82ILdD9nI}%ZCHlRwsu4#QL#cl@$U|L`A#_Q~^i2Gt5UQ ze-uspzFpSB^d-d+u6j(ZtbEzp(MCl$Q+9*`L0J*-- zj>eW|%B3n(Pr(@;G`F^pTGTjcZ|y{5T?68g7|KIs2>8^ef<>RYgayTlIzJpG6_{gn@cd7;nA1@v3il`c2!wgCMX6*$ zRt8LXtKmtEhc{A*WW0*FRUqW^Q`rEbAfpHd;UnXT`Xcbfn-LELXxMbY7aIkSzZOqC z^AsW#K_r6B2zbJ%2$Df0)i=12ZKuIIO2Wnu~>VJ&9L%Tr{iw9kg2>*Smk+Rqv4UId5MFRIXaQfgdtgz zIDGf@l~dH>*YSsAZ^hd`P`iB8F6sx*4=>`11I|+pkNzpx{>VdBT^3Az?9gRM#W}%_ zAKcE6UNN(!rB2B&8&4BVYGamC-XcePt&Q_IiSsd~=W}ibyowzi{A*$RQ#MmkWMj^a z;Ym)$rPJ4xuyRUeP^!#sc3xW+BZHBnyDa=CL>YJ*R3wEt(uT?BB*|&YC~{P!iSsx& zM;-F=+fK2!4E!{l?0KiY7r#3$K6*282JHIyPV9H+O?YA{GNHb4i4FyoU1F8bFi`22%cG4q1B;w_}}q!E`$CQru`AH0GuKKMH>5=XVv zJei4hLW+M9M`DMy`5VU#`0#ypMhao=ZBL+hqc3M!saba~A1P0|&2t$iB%hN~%g~N9 zsk|9MmH+8@th3e(th)6XG$-&laiX`pIWH%ha*9+|#aT{i{^p~B;>iK6kn2dMwYS8o z+nlN9DJg4+z&srM6{#dUpUTVeHsw%8=T$N@9BC=!KnIO^I*i|RFPun)?Ob}38x&%= z@6Kw)NNc<+$o%BF83%0qGpxG79ysoPzJ#jFC`l=z2n(_=a|6V&)Emm0oHi{mCk~=Q z!so;-u{Jv)*MEu+>*-jr2DU$0)gg>s%hI3d%Sn_ZNYBsNc{9=}5+<%=j~V5XUvW*( zXOr3lESwOBsVLd?Q)e8E)z;Y>>+N$5-jQpc)jPsopHn0+A8mdLrat09w(IrqwU6(c==vu__fC?JK_R|R-04x*g8KAR+(z~zYKI;Zlc z!bLVi&g?XHrn@$FXh6hAi7rhh<(avZgZ{#M|P!a81WhyRWxWyL-$=>YpMIuKLZEPnLOP|9Yy;!lTMVD?tmgr zaZHYUR$Q-1JvTDXfaF}l=9v)1$htSg7!k`{R^qDnQ{`BQaJqCqFpf8UAvRvbF*K9>IE|IHW2PU6q+ zQDgMi5hc56K}%zc*pnxbpfq`CN%lu}WDHX%H*gurEyk^(4c*wa*~`K^MEt4iXd;FL z8Fxi(CB}>!gR0sZy!hVhlFtbT*KT0Pz~AnB1pfTni?GJjm1R6HSoj^@`s@RpgCL^N zj{cr|?^XEyA;e=zSsk#ct_ji32&vC6V^7ArDxk`UYE%o=W~J(L6=%R|rlF`c$zzho zG9Rf+R1PW3{Zd&|g>jR{OZ(whaolgN_Cd1H!O`bwr1MC5$EGTykS38r!X!yDffIjo zq{?HFjd}Ct;~x+G2L~T^JPtnec&xMjHbV6cytqng-h2P?Ph4}$%?MRih%=zE#oki) zb`K8mUKEil#VV_HQ&W+QCUD3vw?il%K&btDL=sW>s3AV8Vp$@Hdp?Q7>#IaE5<|pK zMqd`EV-Sdwl4yX&qV@3kmmm@7ppI;#F&aT9Pv-GB0*Q7cl2NQ%-G%*@`w2&_I|_j) z<@1%%@F5nSD58-77B`Z)`6rVw36up$k4Z{p;ujU9SA$qpjceO-A_H@-JIm_4uUplM zS*XGIy;OdlS9s;K)XyVwntEZSUYKy2dU+!8RvvRSVFO1TO(a}i_{(SQ6eajGSyp`v zUB=lSi-1+ftG?}8`V(HJIAtX2g|)o6_Pv7-7q&{CnhC4eWeF@dd-tk#=$Fp&B3fU>ImuG0dAIk(#Qyn7i5 zivT>`sv3lceu+Scfg)hm|t)SUn4Q`3RQ+sRzVb`dg(mVr}}gWDicJA@`oyd zD08&knKSFgS1H!pAN1;v^RTz&VL1%yE)tl~H|@a$Vzm zJm2X&sYkX=Et|nrCeGW z$*;=fOiFt;-14LoLCJ2$C+nJsbVG_EB1!0vd&X=coQw;Sll#VyS2{CCWlM@UPV#VmD$K-Ce7PPwuj*Ty7elE;mFtEvk(OK_rwrZqN?vY*vmG3#ZI1FQhYLD2M}g`u%T5@?N7*}D80SqRbFXBGooF~- zLw=+>(}`#&Y3cUS%}yCok4Pr=otjtW7rWOa_np#KUEb({Pj=K(G!*$Gl9Rjd0@6DPCus$MQ{D79YBF5o&TFXSJ9v<^6b zta}-$lY%L8l*mfuxn0D|2Fu0u*fQFr>o+pc?;Q1zj~zZ8#OK`4l9?-3w#Z>Au9=v= zSJx1hgU^kOF~l4b^HQkGY3cgRjfMGsO&wTt^( z@=<=#{g(#AaXJI@20mRx>+T>hI)_*KtR3+O$;Sud}C+d)F8>gGRDg3P*ecPOTdquizemAyv zW%HS++uYD+VqV$EtxPV|8(waCb0s%^B^ueJ`kr>fha96jed9DC%Ga%JI!|9V;mPFW zH^5}%EmopkQInfa28P_my&Jie9Xg_&JVk&;)bA~o$<2LbS)Qr)cL zY2&<1vT>Y{%Ho>9jbd#FFqOc_Av2E8#(`D~84;^;q=n4`^Q6gh+cB zhys33KpdNRr9l1%T_)2X(hh!JC-19?zeQ9}%6TfO_uC~JDv~*&mn3D4y;|{R9QxGf zq_y32F6+QDb|!6VK{B3zpVArp+|<~t7;0b0lqZr+qIH^1Vms$IGO=VFkz^EcZxS_Q zYcYPgiKre|i|^_dArd8HrZT3e705y>OIn6YO=8qWv7D+v0gs>hC*Vaz zO$EkI8i!CN`*)F6*wLHDqp<}yvgW)LQhl!GDz1QQQ1BTs$G?5{KN?qDeG_JX{uO3_@fFqKSoFgR01rO&A6$6(weW?4@C5@}&c0E4dk2fs z{3d(e63)*BJCF#6vEk}d5Q-4vwg!X}J|sL5#7XJjEDmCBT{S5?48NZyZB&Ig*hI_@Y4s;sHtx&|oAZ;+K3hp~n(Q?7LwCmu=gH>65yUXlq4vdn5d@ z4oZ)~OUBd@UPcDQ7m&DgxV-#O)+`ql18#$}O}*LDA@9+cw?1yU)5>an zzl5*ny^r%}R152x8E4>|uinRkxi8^9)(!LGNnqN~an9`z;iFGUZ>$HR&)H42k1-@XZlNdt(uVkg-LbiV!uW|N6w+*;Us zU)B}pS7oF$g=ZYN9;R{TuiwKn@*QUfBp=HY$6AM-gZrO-5ubmqW&ZqieDvzQxc;zJ zESVK=lAGF@NNj<#Z+i$IenRrjc~|J8SMS3O2XkHnEi^^&OYB5qRUCTdy?FawyUgr& zAH^dV?1QQD;+yiZ4io3#`7dW-KH1!TXP%7vUV4eb?_mCy58+Ch^p3jcMLM5_5C483 zrW|-NX1?;0#c%#6FX8zckHFfRpMsXdBBh%>*IiD1`gzeDJn}Z%2$MZtL=IIm-tq&m!R4?mDs^T<0I!K`74Wzv0k)A&TDd(@{aPjmtIDjACe-Ka3 z`VT&5-z)!`*!Zx?IuGrr9c;nLMCfeE8J0 zIQth9iMg_QrsQQ0Q2w{@;oZN&?q^+#=ihu;q*SKVsFwlzoa&zIrKx2IBwcy z4`RU=T;?x0S~`aE@qz1@O5^302l3%YZ&|!Qck&kL9def?7{zuQ_xm!=d?$hK_uXs?A#NmI&2VcBNeEy81rtOXE{`mwxG_rmA zGCp|fk2qm%C6{glwz%X0x**f|ec?jvFDb-`>vtmq-;a3wJ>{Ib&K74-TlJV1(2;eS zJQmxp#Vh)>;=>odQArHqBl0GHkLPB;f%%_5i~A1aHYBk2*>~de*{@^%>?d%?UMwfY zGdphOVK?Bz*`$Nn|Hb3ytS0T{`8Kte4YUl?PQ{GX_{ zqS$SRyk2RdDDPci`=}IKQIVZ~Y7ZI&Uwr86#8ju*DhVaNPkH z;Q2THi?3K;Z`^>t-9Pn`)cd4Cj)KeKq?T{QXeXj-&2;n$BOtyeH0*7MhjX zSaz2A@;mRv2Oqpfd0rQq{nq1n?D9RaCdsVyp!wYK6y|;QD&D_yKP-2^d3gLK=Bvtl z^}*A4_J%{SCOZTfHA;WvN=~??+oWVColo5!r`~Wo=}*Z%kK6dcQ+Vd)FMa{0uzvDVKQ?_Z&((I`kU6M|$|`^OLcq7;80Voc$Kr)GK)Rj{RuvRsTh1 zOOd+K9j+edPSEwyidUP`A?YQtG7h=yR=oKp$@eMgkLc4k|3Px@fhlCCA}8yPiS)SE zf#>3}7oWnXAF8}_KYRi2y?8sWJ#c0DQJYMhN$q)s3}sLt~zAoXi}-jfi8){%4a+ zPhcM96MFASJo~rfu=NRl!Dp=7$4{etTnXum%BixvQ>4Z(+0q)j5%n|4`s^#uV%{gZ{x?|GZPomwYhhWF$%&-v zTkzH!+~2B>Pw8Imj?*?1z6!QnnaRK{%_dQG@^n1?@qaPr?*m%qClPrdvMz9gP=NS|N6{s{hY&bC-i=2;P1+K`~@&Ow*ng4bWy z`6&O#FW-Y(PuT>&yN}9$#Qf~`rt2j+?KC|3(M$N^qx*67RAuLqm3eP=K3@FjMfxrA zH=HSsXDmA>((#v<;x(F&IPHACpGcgBr&xaN`aqtDNz{^36QFTR}hIG+5=vDn(0BUs`bH)+@e9Cg9rke@o?dHBa! zx8p~CctF3Kcy^FRaz9Vm250`|4!kpq`j6`+`uKl$;?Ku!OhbzFMq`=Vd*rSEAzi+R zFCYB_4!-0r$V6$zv1=Q6fb8xemRBor^k+x(h1fnCyC(pMwz7L zl9|pAz5=sY=O5jPtM~f@p8Vfa6sF%i|Gf$aQT>-Z%4sCiZO7uw+wQ}x_o(kE|C8sj z-4Sn5BJ(RO>ec2_-bv26J!MPF*IUZSx2H{)HGJ|y8XU6ZO4+Q$0U+9CCATuU5bc=w zq(@$~>od9WE78a%+7S(_EyZS2$;>&tQG`XZIr8;|4Bx@p((5ZWD)x<(Ks1mrJaZ*} zRc2n7<%wMEqF6}gCpVo8A*G2lq$DEfLvfOOY%!5+Vi2y$Lqh_$gd!o*zufB-Akj{d zo7^s>Hv7!PDIV&%gFJc=zFs;Q@^uSydSA%!kuW=nWFh1DCeB1sPRT6LZ8!0TFOz!z z$>h^y$EiHd_AqgIB3%zx!Fhf9ABwGXRb>i!GmxE!6iO_6M>Zp~%9e2q6uoGNDmo$l zsv_PfK*UgC**J~!aGW`4GUp09pM#8UI8B}$>Ab?zY!pe-AmH)CpY*{O^B~+B#2wkX*>jErNs7S?dWO`BT9ORvooNqq-NSfCAc9bMox7cN9(ZZ zGb>G!xC94uHriytIGRt`*Mn+Y0O^5`@01%gB&ng`9-y?FjmUn6qjp@$#E zzg`o%j_li%MJ1gNbZ4SH=V*EeE!CrDo%rWN%d4_+dn>xN6z09TdrJUUcj29z z55hEg8OQlqbndgOR2#eX*>CMW6G7NYl1MCnAnttS4qUSL8W>-p>mVwtnSzZD`8{U- zo%PJlA!cJ#W8=dQ$Cgu-117^xqpn{4*Ld>cU9rx%prvPosC*uCQ52Ac*lPa&# z36o5K6u<7|v7Cgi`fo9dFQTkmu@63~u-xp{^#fheP_yz@IOg&{;fih5m=XQy5;Ys0h^vm;3ae08h_2b6 zg2%{SXW{;P4z&8tDWZVFJeL_hD#WDtC$>8Yk34iSj@f2Kd%O^ZH1B`SJR94vEFurt z>N$5|=ARD6c4Vuecq@MRY5=4_TfY%#xFR%qMQnHExwzvp<#fCBhi_$E&i_uYiW#H4 zdaN#N*8Qha2oz$yi@lIc(+Mb0tDSY|Vo&t~?)ie!_!iEVeuGa%{_OVtvQAz~2s2&Trv4 z|G~*uenla}lVS5!ycPec$8Caj{1mll9v+rSC4%P|ZqP;+QLM!+l4xqbg~SW!vKSc>ey=vFEyD#Ad>jmq~19&ol6^ z$9_*XLwQw2NK8Edcl`HeT)ZdsC(S)}{^hXIL1*CcJAZ{K++Hq2QYlQv&S&o;@(8Fs zU2Zl-=hpo1D9vHEGWC;?`hA--@!Wl<;(#rbo!c^uA(>Cdzy5m_j+(}UDxu3L)U19e zp15#ltTQ&K%5XQa9q#xW+<(UbR1Mi7!J4viBm=iJo0R;?Y5U>6`!2*uJFYI{!N@Lh zY<)1Ud*}{aur;-j^-b3`8AIIO>kh(pE73S5-AS2vHO8&GGcLY9LD{#!3x*td$(GKmnoC5LSb@*9U(+AaS;JAM%8wt@^6ka?QSK1Dz z-l`oDxzDL@65F1RXRkXD+pbiVSytgob&y_qMFwtZh8*Bplw1l^w|N48J9>u9yCRp& zyJN_1|A5;r+(3;7(Yd7zQB1OVG1=vJL_ea1%Me<=V_W?WU&{>)Rm`2-O$cY4GoF!_ zri4Xu>i)Rx#lPZ0?z?iOYo<^&jjsbw!=rcXkL5_t1m`7LH^1N>JbccMSbHMro9;K1 zzNv2}u8uv<`5T_TgdN*{!hDhcTh#NasD$uhCI5o2f27+AZnKJKL6pwSQDN%VGx67S z-BpwB4kX=Oz`CQkfptf+E%)nd@%Z^WVjYr+A^A}y_Wie`SJU@n%-?X=IxvHU!1XV8 z#2@k2y{BLgo}18y`ft8NeJkr?4A$P`RQ&yped+3}WtB1tQ@5smKFUSbU;Q4pov&r3 zx)X_sIP&&eanT;6YrZX_34!yYdIcFLd#c+#1$!X7kj^7e(#R%yCHb^IEeVaF0!rcX z#E0{k&l4z6n9e*57y@d96`FgaF5lLF0DCd1#ElNWjJ~b)9XC9a6L}_DN26-GUoHOjjk6f zA7FIGNMiclt70k#{i|V%)9q_NAD7!=8f089ex^4+?>Y( ze=7K5T)RkrI-2f@O`gQoC*nU3o{OV@wt{_4qq;+*SHR9EU5uwL+C`>70KfQ=Y_>eQ@_Z=i-E)tG*Ck+!wkX*FJa)&fA)Q&k--HEgCeJ%kNmTLZJ}l zCF1)t5<+E@J!2AfK5NI6YkNoii02L;iJgNaq8HeR37(q=EFy&5`KQd=YfD90l%u7 zPNl4w$E6+Smz|>*X**?b)h% z!1J@+Bx3Aq<4dA;{3wi{IzgNQy=63K1<2ORf_QcIyJ%`@mXf`Ot|}c(%bIcT3y-0^ zyaJueI?=ea3DK??RT-qQ#kydZTatk#HsbObw@dAGn^NZkrEmMFs%E_^-BS0+_~m(y zqoRo@LRDoLH)$LyM^(WeqB$l=SxF!A?3nvgvF=8^OBursqMg*Q|5RAOGO4+ap?V;J z_19kutFOMQ#J&3ZTiABTy)pZXxit4F2SCLo71Bn-i15%u|G`yP-$ZjufD%~_mF{k% zIg;Tq$V3uS3wl9G{>(4K7>1^mCr`pOqIlP0BqGf;0NB8JW!EU4UFbv16T+%hejK_| z3r^c?F&^IbTm1LnrFibw9k}wB39LQNi>uz7gwtMF0cXD)L^&Bq(9bU=XwZ5)vC}Fo zc=^aCEIcEL7Z2{jZ#J2a8RNdiSYEv$#-AQe&_yAJM6?w#pC6Gf7LYB8q@xP}Y1NW7 zNu8S_d~ZlYt!(!JzMl7<&^IsMg7Xes6XVf<5AVJhN1yvPWv2p;GU<>(qDG1XDk3$y zTP6klVrRx5Pr-pJ^NX2Qs{34Qv))!%d!wze)-TS(eP2XSW?sr<1j|YKdF32jbmyn2 ziveYmHpj28d=Ov1{TLp({dAnTJ+E@7U&yKKB306}J1#zYD~zY=TV~yi-PhY5Tm0fz z*nHEiu-lz;zzgctO#d}5-ZLRx@#ICn!7h9J3ir)Z4k0b`{)wG;{}sjk2ItJ=K{_3O zJMRFj!%jqBKZ{d;!EN~&)?RNfocj1T;^4B%erMtO>FmT|UeYl_C<}jsC$2dW+pV!V zR^8%vxLXFJPIkhnPRj7N=RZLU1w(8945!IUE!L|h^GV!}Px0JkKS|qda9)yOpFiR1 z{Z>R7!ua8ZtFgy=Kf^j3ZJi?XxE+qcApQZYyX2TtDntl`<#;I?AWdB!$3sCO;6Av#oNxWxc6$JKoVq$z$hNFXSER zY0%w_87FT7nf@2e#e;9F9}92&Yp!w_TX|EQHbd)EK(bqJkjdW2Chj_8AFQ*1hGBK^X|E+kdp5klPz+u0?2-{F!bHnUbR%5&; zwaj+cDEr3f^|9;8tRrkES9UN7&&30${S^y&90&&@)}J~!h~?M_r*E<%9Z81SkBXzM*ZUD3oUI&HSJ~t@ zm_fFse!OXUW?X>7w;n@*R($lA!?EtBq`S?w$GUr5geSfxBk+&Kj029t4Bgg~PTC2p z@YwzCIh?jT*~_Lo2<>^(`)V#+ZD*WxJmr;fHvy+!c`UY@NCleS!}SMK{>@VJ*KF|{ zTz5Q;J>oAmAmBOqTRicn-(iP!w#Mpo%{=Rpld z`{W^yA~|5grd`9bn16>aT9M z$HQ6YV;i2wc-}~Ch%3%e?M*4O$=0g9Jee3sd#TK!XX3Y8C3u!O^(> zopzL2MMx;8el+A0>I^Hb8sjh~a#@67_d{LtK8k zk=4$>{-)bvhqGR^Wfi_;^K|4fkn3i{8EbFdL1V(vzR`ims`djRR^)~oD z9{ENlYOFG2Z*0MBP(o>_u;=MN;o$v_!Y}rD470_dt`(m@d>DSY-;p@@kFU@V8TErh z$_a7UdF~*Cw0L&JN?-y`x#DPSE92{Z+;HfQB`(IVdjwuyLeYeUZyieVAA#M^ zcv#D?;w{Lz9ck%qBdxm)9lD#oH>UIa&DZPXez^R&jg{jwkIw_Q!Mf9Tp?PRqX+x{e zGQMumsvvN`?ShMby#>Zm_Lg_=z}}nfj;;1O5?gJ#3--Qy9$7Z&V)KJ>!7t;Km*+ma zqeJ5=m8=_+oYQy3x*IuV-3!y@zJTb^)9_pEsPzNq-)tAGKVw&{xAAef;q`XVPZbMB zHaB9Jqq=9|dl}oIio+5oI$}8;M#*iZy(+J-hlL6&}yWc=0RsA402aa1`<7`HUzr5xcE9 zS>hS1Y{+SRJyP@Cp{uF1g>(LkMQPU!ZlCAr>v8b@$B@0x#OIwH*GA*_*Vt|U<7oW8 zMp+y`@N24pPydNC_xS}j*pl?O`60OKT^>u6wCT@r7GKx64V*m-u?BfWC#LR=%Z}3H zVg5_F_J%qdZu}sXGI3s+<2S*Lf7lCaQ`O;bUZT25*HZVPIOEChNTVUFy!Y>L?dIy5 zV1bkx{^~_secYi~Yonbo{rE?z&WSkd+M_Uo|39bcbKHIQA;N1zqBRb_3vcsnX7y?~ z)6kGF5#Tl7skM>mELihPHsu0I1iZ@3fI z-sWul-Ps4!@AdCKit`WtIsHc28GBwvKWNhIZrFmGkcC7Ih8S+~eWkv@jZZgH9n1TH zCIud|I_NGyj^u<}I#W+@E0YUpq|7zxc?|UjZv09#vdKUX66XLKN>aPr;MRW2;DqnOGS-O)Fj>Gzy zoJr$<)VJb#BAXwdTXpGvVcFs&laPFUhT~Zw)CPH`#cfgTv5$7U)uv$*hT9tem1_@*&PCq8UEsvdv)c^$6UWDb75dK->>tR4H@9mB~_CGpyP zKgz1gvD4~FT(DIf5APho*Z{9)5kM#s!D@&7R;CCwCbf(6C-i^o2BHdIcsY@hTMlvDo;~lX33B%jxW_Xg=By zX_%y_mWj4rvr;Evrds$&-%8cDy7=Yz`hd z{sKJt15MD()3*dq{NYs06i1#Z*yzAb(=4z7&;9-YoOaJgSY$2d$>z{zsAItUK;AdzXky8lP=+35vezJmo{%)RGbz$TIyt3b#dPvN7?1_mx^u?4D|~oxyOOnel@m>MR@t@bMfGpEDQ4|dH!`i z&V5O*cDTinip_@Ahtqh*G0Ay9!J)A zQRimKaOqE5Ayzm!<5=u0pWG!|{nK%{>iIev3qkn!KXT?gh+kj$in6a2w!|@qr#Ftq z@p;GMs^_?j($m7fUyWD3K@Eo}uCP+>LCnE-*t9m>O@4{*K&X)e4w(AKv zciVGt?yhWKGzK3&7gsG}J@Y*4)SE7EB1j#{=|+Yh@x>z~gV?1PZ$F|x8@$4LWLrFs zICQt_7}WAH_9JHB@)fb;exv$+_#aOH*N3R5>r;6Aa@a}6Ajzj!_No8weVSv5zs$jY ze0ke7czzDAEW}??-%au)ryq+wH_-ig-S2P<*%;p>ahWe}Jq3Ri=Q^_At$(BPTaBZ> zJm(OcaqlN|O$x%p|7F4Ta9PtYoL65jXs6DdV14kIIDR+fP#2qvM@~2&56xjaCwn6M zSoGW#IQH(@q-A!T+8rlOOLM?k{4!2I_*LDnGjR0bEIW?T=c&C9D(5Hx+a6VQ&R&~{0}8%*0q37S6LWQbpZ{CjddyXLPCJFMj#>Tc$8I(> z;Q8}Upt_B`r0aXHwZ`JqHGeKQ^@%+W=q9Vub3kN0&M9ji5T5-gjy&xN%w?Hm?tLHU zJ~~@DHcp(3ZK;pU<;@0LY6+y&uj-~s_@3CP1hxqqh4`Hszs^tCRhL`Z6 z+?I~P)Xk_)Rt1xW^z-RYVGhel^?Lmu;@rm^vdT3xfwT8ej#`-WG>$mqDY6T07q{;_ z-1^(g@rqoJ8BWs$e z>UV1n`4Hzn_IWp5{@&>Fo0o9GnNML}Af%Sn_kV}mese9JPnmPvh{u)}PrdRL_!d00 z(mFVb=RfY)w|5_ zwtu;QAsxs=T-L9SNB!Hl`taNF%p$e}{v<#H?z!w8H81)LuCDfPuUxj-=XplLXF08;7N9k(}%^`pOKm6$hPNR1CsXyla3rAh@ znrh4P({bb>`X1A65Xq@l6YNeAEbEL?9lDLWnOx5c=N*aPKQJ3VQh)Qkn%JC1c=qEs z`1qUg*jLJaCaWLvL?lD;ZVa_AUMM?on;(XZioFj=2L(^kOPM!L?@kQ$?8}_< zqX?lSJL4r0i?ibyhdmjI%kyF>ZfQ(vAr%KX@~5WB;}wcmL{&@OOffmK;|f2Mq87>X zU`4#b6_e!*$VdHL$tLxz$-MXJX5;eEie*sGGbu)#A;aM)!jUNAu>=z0fJd^$nUu3+ zDzVJ!Xc4zXhowEeB;hOiP<6!Pal~SAL?RKPIE7hWY6ShMc9H0EE=P630Xyi35;((d z5Z>xY-K3&qqx$4oF~zg2G149Xk87M_*(s2=GG7n@IyTic;oEuNV#yDS#rZ24NubReg zt4SuqNi;5QK*QpC#JZwlQDRe)opxjkNz1M^sYG>fL}w=A$>b=cHWN~iVdVFsys{iM zV`?#G!dQ%&Fb2VDl9L~)3V9GH58|8pg}C6}>-y{zXlmv1_q_0LENZGlAQVJfb1T{! z+UVGZ1dS0tzen(>FWOK?h>XHOI4CYger~MNv*E5_g{iQ5S4|q0d}>~a#3JbE>Y!m5 zgD2>d&m)UNU{yK95s)1M`Kx6@stz}zowgSqgQ+BTVDd`MyytTra^ardt#;pM~H zamAiZICrfdu-Am|u}Q@Oj2pFpChi9K`Ln^nG9W+~BX1I2{&F>E;P=8D`Bq-9>6CfL1Jf8b-@*am^ zmp%RuSH49u@xUia|16o2p6fAt1y#4{m%}&VmG%hp-ou68@=X#G-w?3_%p$z?japG{ z+6r4)WfWS_^0Ja&-JsCGaMY3!VyI8z&ROa$@H*R^h#68B)w}!|*i}2ieE7saC?E55 zf@?nrGsGba_M9Pdp3KB65s zf(VY{qhaVEHNYwC0G(TtuPf;y5>Q2QgD%*sG6Yj$Iw_4JxhM6VI9~DRg5Re!4s)LO zt6Z&-pn`NHPgLarMJM5zTSzg!k81@S$|}6inS(m9t93BUr?FwXXT-^Sz6! z-dA;nrmTTI>FB6Jfg+6lEbs}RZ@!#zMqM-)|9CZBo&er?{4qT6#EW?H!|(Akj(_SJ z9JtTn*!93maW^v{5!D!DD@q61d%7?@281|1EselDrtYz>w$(X!;xVd|9n)l7YI^l% zy+U6#R@-7{s#}#)XhG{MNau<8atEt59Cn!%sMO zf3@P+j;B2*)h2gY-RjSo|KT}@`D({ClK0Lpjs32UJ?NZTBIi86abJp)1GQbQN&KH# zEQ8L$0d?IW9rE2sioJ2$l$MU(Os$B9`5)r4MapqPevFHq`$+GvYUlG6vCe^X5H^}u zXB%D@#}Ll1X`BR0@y7M#KsMadg_tQ}+8*m@{msRbGu1pHavOT}R?HIT z4;l~DPcjxao~%BH*)kWeK0rE?abS<5g>|hdPIk%U-dJm*3jgrg{g_33mE)x@<9!dU zmXkTlWu$X}WVT2^{r4J`C3&TOp`{43nGk~IW7Q9JBWvqiy!IgHH+dB;tk<#{w$6Hb zBwdEVVYhTX8kloOT)4e~=3T81X$w(Yr}HO}tuiV~0(34n<2<%3UL4QohEzVAGR5oS z3`e|rAjacsHG6zLrsrZJ`OhiZ?wX^=*K(_2cMfAbLa@ldDyN*V=W(g0|uTI_3ft@K2)2p@N z$FFefJIbz=6OgL^HM%cXP~*b6nL1_6c>jst9dImux&QBR^*gL1)`hyyQTLRZ4THr< zmz89c@vV>PvI6!vS@8Ld@{bsnO>lniCulLhwY6NCN#|o^bf^lWE9xj?!4XHK<_jBY z4&iGG%f$1enlq2S?R*@t5?@rl!+);11aIiMgk{e?uVelts4(yC*l>p)GctGRqrL`< zF1a6)ds(_%IGcVK2yf1->!JH7Gv}z*btltK&&*Ho6`M%)O4x#(@2Jn0J7`tVRS7@L zz8SB6MKegns+j&8eb2kws+h)(jt$@8rFY2A`CdhM8fe}ePe%*f4)uhE$f1YUCOpRa z5n^4^(Rq!a`(VXS^K~Hxjap1O{8Z~x!Cyah6E1v@osgL))yMgSk~nnZ<@Gh~16=w& z+qSv3GX03Rzf;%fsmpI6M@v?^XMdgfvE5R8__zGj-Pcl|R&JPwr=L)1)^&!DZ`_G@ zFKn{)Uiii7&r%(vH*Hr6d~Hxp%Y1() z=?;DJSp1RV`O_?N-Iub3yQx0OoznY$D2R6bA~&AtM7seei@&w8E%b%-X7O^%!_?E} zRwftHTTX51&*sLjL?fFDW8?AK8?wfBCSslrlFcn&FNk)7O2xgAE%LRR^-!clMo`ow zn~PqGg=m*|%I8J`bd#sJNdIOmVv@07Y$0D{Y1G#PA8M5%2@YX6z1nq?10$b)+u%V^wSd31J~auR98pG zx+V&z6QwO}*3_hE36qJ-g>*TqEb(2!b!z>HE~rnI)S;oF0WGbq=;{iK6OFM|3Q!vq zsW$OZrUkto>ljnR6p^NdH6{8ej)0gghxIh-PzoM*18sPfQv=qVvGEzb{wYi zavz|`*eu(jq~}P&SH`%EbhR-rP8D7pNW$;;p=NXq#!eWA>e1B*R+fpAT*6NVPWC5l z@%s^~slVO6GJt5@gP*!8asS*2IOoktIQID!u=jr^V~;z0nA0*2o-PlL*?m1ET4&P){~cOm zm3aNT4q0qc`ksxzA*_e5AZhK&nJjSUlG6EQi&>G+_ci`@#Di*ew~F<#%Wo!;BB)-6 zp8AHAic_&=m3pa!dMv!3yOT&{rJV$F1;2!&0~evbx0FW<#2SDu9fcG?Lm@ApUC z_r7w*8@JVo`0F3|O&}K_BK@v?_$7Gce=p(lPv60UxlHfko3Bp8R_ZORf{4RICsi(W zPIVHib;j!z_|`sLeUh2@ESU3-9KV_OK5qQEb_fbpODQKOxe4Ko+MRu~!DsxkYSv#q zP^+V@upSPO&%`D$^*5Vh9bR?vyZ3O{qtw?tK{z0Mk}G3^w6PV-785thqlOjjP59oQ zu#~gVBqP2jTUJ|rw#e!;Gx^B0DX9u7eApD-?r}fEpXYrbtLm{{ly2U`H%G6j(t|Fwk`VbWJRGa7yA@Vl zdj?inYdTh5b2I!?oWy|n|JekqthqT>U26uVZ;91roQk_?tev|D=igG7*#axCF;n#! zzQMoN*i_;;ZMAi_!Y0Q&LIS1hQh_Dpm&ynHat)=cg>&%0oBW?JdYNuIzWL8NYDJZC zYhssuSbi#{?m``)bJmCSAM1sWucN+6rIuB5{?- zM;p5cx8ZH(Cw8RlN$8z_NfT+BS4#h72G97Nc`kJ;Fq z_(YE#U5AiPW!N$>Ir4NP&a3hZUjZFOj{EW4XDmYqYj62$ZJP&Rj}7>f;|bB*#;yU5&dwY$O@0vC2+I;-bIZgRee*2_HOp1Fkq~V@zcsnK2#hxo0cf zz$hc5|fq#4}(Xbfz@EGDUqDxgq*)+jA(!Cek8(oJM&RMMTNf}*-$tR#NpF5)n zd`dN>L`%I|XDXalh+$-{UyQ%=%`0!FTuK?chTJzB>da0_-3+?NNF23S$b@x@g5s;l zfWxG>w48FT`KUA}8RQzT(hgnDjIS+mJ&moopS_AXpT3N_pT2^5pS_0pN35>WX?#r( zoxXxoE`J0c@seuQD`J=9PQ&dFzk)gMK8dIAI1@*0#?E;xA?w}^srB1%i|4SiB|fS? zl>`MoA)?=|f|kYethvszK{={)fi7dH`$o5a2c3h5pPq?N-uW-)d_wf;|1fvM?Rgn{vH#tUsI=c-^zgDtw8Fj5O=LV{dY@5M?XMT?Lr}HOYcO%+eXhWv0 z_Qd{IX@@$U*TCjs$cfZ=zBgI!*H`1j={yg%Vb;y(;)2%$)Kuop?d)w{CrAqJ2ks9G zd``O1{iw*1E@3n0v|-`gC30QieCAruM;mq>sl<7y2fUBs6Zs_Z7_2g#ep}FW@6e4` zqw_|5{n{tEan^jgDu=N0rUyvc^lg_HC*=AsU&qt>9)s99pm|FjI!c4%s7lv(E~D%{ zEs=F5*OUyzw}*6(n4Gc?Ex%j5t0?bLk$LYz;R6PUKKoa$fQb~G!Ox9~NFZB3;s;_F8p7Sx$*%or)-gg9~f z0`eflCbiP0aR{eOI=2S*ukCKI23b~aX z0HR$QxmVva(T<5vvhtx_R&pzo3(<~=PkQ7v1M5+KY{`~YDh!)84EuL zv7Wa$iAGZz@pCb84$%tXXOFxh7lZY{PIzC*~HxYq~5Tdk2JaNQ52_(c}g7R~Fs4eBA zD)C**5?pxypYi^jPZ6Ou^$4mb5{=@$xu4?TYfhE=ctyT=B!-s8W*QrHh;>8};!g#8 z{3MZb1{AjkB`F;hA49qY)CM(XcpUOxHfgj9GfbMC67dIH6Qpx)Th*8vEVsfGRF1Aj z+?Pb0;&^31b^;8B_?a|6->1?k#ADDE{~4bA{M?%Rm<%MHa%YvbFvuZ&ZsaASQkKC7u@-@cIxxu>NN+ija(6-^O2$vK zv}E#OsdqGf4v6V-R_4cR3V*DmlOoUU7|O?e-t_s<@l^+0$RPJ;E#9_OjKd! zoGP69Y!z<&bTXd!ZX#wkmmyMCL!7IS>`Y+S4cA4-_y0mTFc$N=reR?duV54hJJ=Fa zmPUh2J;np5d)sm&15%)LK6Nsayp-Y>FMr4U#VW^yDceaAZEG4_&QN+9pX)SzF5rAp zPM-^OK1Un}`JCe&Cz7V1&%2T8Azs~rJ?&){5dPdAKTWIwc)xrWmmGF1?)#GYgs{$5 zM?)9WP!rW=ZE|7wzO>uwmfPNYS$(>eN**W-_KvW4R2GPZsmlR zJ{6d_G;(H=I34Zn=G)RymRjgqLhfUOMs$D!jU=0j7*l_a2iWoSOa6rMTUan>7QVS>2eoPw#$v|4 z{QX>-?aWO>3eqDcgRZr`LnYmq>fpdbx0}H7Cz6}vipTHA^+#-pRmKL<`op*Q{GC_u z#LO3nK0}@4><`!m3I)>HDwqPE+q}5@$=T{Ng&S;#3zv^0al&@knBU~re}G4Bh#u258DwN%W`Y{+2NbT;m+xN_ketY zn7}I=9#8W%1$q4Pcv|$xMc84JV{z^sFXHXl-=e-Hj8M%Oth3{hxZ=Sl@b{B8RAZ5E zNWJgj>Ld2S4oCkD54=7b3l_DB!$r+Xo8o{oZp5=spNIYATF`y&NnZiWq87T1yUYa!>G8Rc6B1{RZltma9PuW}naoI!Gv`inB3qo3 zoKWYqw-PvPNJpi_ImiNso#z@UdHJZK1KC{gomEI!lRnCyS*bT~?5m#^)h3SJ3!!9ao0<+D`3~)imF@1BdK*7>>N* zF}(WWcXYqYp1@UDW%J$ehdb}Vi&ySULs$Ld3(I8SCaZynW8zhmz?ok>sZt=NR8YWW z^(K5wdTO2&(g$^Y8jmAyc^HqK{c~(Ir3&Ht@37#*S$Oivm+<5hbbWT-PZ}0Zq{pSF zr$8r~GKQQ;PfI5QzE9ToD?Ux?Tsc$mk1bbFw5=m2RGc`O@johTeJXA{NqwHTW!C*T z`u=({@l;2%-19P=^{MiiaI;Fh1sz%9*?`VDpCC-N^Cn7Z2D^xNVDOy~bjXqbnm zo+jPi@+B6;=st6;EpTKaj@?&gr`I;jpY=joy9^BPbeQyOqmz+;>D}%sY|7PtH|5!-`KM3w+K8XPn%Rvz?}q{04N> zFGILE$cgl{;XpaUt>{ouU*N_kjk?d(14s5|&^=GDNDMa{8RQz@OX5R%TsJasOUpzC z`n<<>78#9beHgf<4K?Kmx1yT3tAyy(2}zb+KWWh5R#|b8>%B@5kpc%wRE#8dD&3=p zmRuu;kZ2&Nd7wB+sX^(XXo-}d_IV<+D^{|=;lia%Tjsnfq}r%Hk;|W??da@8Q&Y2| z=4EImYHVCa=bAX&*kVy*57gA8$qE}DS%~vA>%1nv9GlV7(u(%>4%-P%o{`W|Z`~HL z5aM$cZ>MYGXSo*nfTS0(SOT4$5iDEQO7*s&*|dR3+N7zmu{D(%D1Sqnrgov`MhfRl zRK6RT`kIM<6V=m*#-=9em$r^}g!$9K3HI;cZq)s$y4394Ve*(PR#{DyAWlds2+Ju@ zG7~snj!MU*NX{fA&Q?V7>0pl!K~E6;@nA#}vNB*x{W3KCv=j|X>d{=^glI=toB@5h zZ-k^jxIc*(ABjB^SEHPIxN5Ff)vw~1RDMM!KglUa%Ab;6qJbpVtxa}+7^gsvj>lr? zYVAPtk|wk)ZAPq}#&L}LhHZy!jt+`wr#p{78?4TmBwH_F3MjH10qNAlva?%dRV7A` z8HLf~Mx$nQEqrAG#JmZ_i4tNzT!x5`+#V_utSrY5?e)0k@jG$RgMY^DPd|ijfBFGU zEz895k9qTv>HWE%;g)9}#(~$LfvacUg|ga8l-E`v5sM?*6-Gx(JKCFDk?cy)NKp>5 ztcV2FX~v!qd+1oBJC^riIKd2=4h(I6QG*P{HWwl1y7!UU`++JJ4w?Z zWuiPj(|4x*8Oii_8gU(eU(Qa-g2 zIBZB{;$v?~mlHm0$EsZ^?d(R*^1UNAw_d>;b}R|zK%Bn>%^mKmpiD&4^Kn#$cs%n4Jwk6s0g!B*sgeDM>VgXb@kS6M{0%F153s?XvHi|S0 ziiIYKf{Fr)f+9ufy@nFf8|h^?yF2Ya-?{g`c{BT_Y?*mm=9`?k@7;3RE${7{ll$v)(&#Zb-^ zbL=lua;2=RXZgfzmfiBqDl`73?dt8UNaVI=rBBj0?#;*H_~Vbn@o(H&{ll%G^~Oth z=pv^d3*W+L%2}4H>&R(iP*A3AOnDxuN}{6+{t|e>ls?ityG%6h_Ii9u`q6hS$w4yo zqG?OA*cq*MvzCsnk|$-|8(A=4X!^8q<~7+}NvzfSg$-;)KUhb6=#7iUQ8>7uit#>o zm}B4=Q}O#J-jCz?itF|l@SFF(2?rm0Do*_HH*xw|-^G`{w%F`cs0fg$kUTP0`0LnX z@%`;j}}n*#q&zGmg+#1$W3FkZ9Zy zC!BF64wdT=gX8(yOVgZua%7h4EIxU}o*KweDoDV*PTcNnC~erg#*9lMms!Urs6N5emR+plX zn!@vCySq{AXt~zV1`a6oH*IJ7E=;q5l$|>7%yG|QJlPXa6@&bCPK7(%d>A!e=7XJw zs=vw3Vh8Lw#q1W50aPR&vwR#B>L3U(p?z4S;g~0>MfiOK?ssV z@*}^DpDVrjbw55Pb8GK|j>0|%zYY7m&XPUe_n2?udW|BIwrcQdJao-v_~eH_f+ODa zHq1Nrbe#JyUXL3w{peG1{sEjP%GpEiG}wYt@LB|z)Mdv&>$q7`k2P)oxO;$HYd7>f zl5fptmF$5Zz{v-3XS9|0=T}d{fyaCp$I12StKU2yU;g?Nc-7mWoNXNWVSH-VcysK5 z!lE1Ob$z70U(*ufPM+qn1b!LC$br`j8$hKG3KZGr&LhL^QbI1y5@|;q&e($@tkBk z|LM6OY!g8IBbGO;k&B&HHMc=KpZ0q z(epZ%J(`T&cd*wO-gD`g^g(=~AEtT4qWnCE?)j{2%Ji_Hd?1s2RZhlJHUCg<6O! z9it!PiGf5Of(m_<-=*##s-(aF+5a%f=P~hk0!_`$XliOvYG`aiqM;Fqgrx?1u4#FS z`_B`;W91RV$L2d(R&HFW!4UtmuW!Q<{acfExO4Zrrbj%NKD5N&?96q>lsjJH5bv1{ z&l?(>(I}+ytB=!7^K5=0X`6ujN~pp4OOE3)W48%f)PHIN^>=@3Q-AGR_tCEQ z&dgvaI5y|Gv+nShNNVNiFKFaV5}o+UU+SS{*$6U1Oc`Lw*9UXAz(hoBkWW{87dEcl zh{Z21!t+l(hfQlYXb4n1%Tl*fpzBxiH^d#Qs!( zJhCMI(%=6BiRIG9mzrbx+2nB-&xG`BqzmZU(t)iEoL#d4+cs>|*G9)9GOq19YepGU zL)<0Lz?5mKKfoEPg+=Mwsv%G??kFIg=|V@md+dbr%KX?W^VCUUA2SRe$qrfQr`=B zv_;;r=xX|qe1$oWxpQM<0?jQg7&*EXqsNUwLlgDlE6Ag=-cYVI;HEU>qkU=m(@?>= zewdPEO(jV_=RfPIaHEW_U%I)I=%Qw_z$@=gtX~wgMLqyeK(N0i=EO8n{>8Mc*piY+bW^rus)gfIqb26$yvMNZuw@L4cMYPo5%!aw1@se z!$iEt<8COF0jPIRK~6W_{|%#la9F(=l_lw3qxKK9!LlHji?Ceit5NL{#hEc+W?7 z)7Qy%KKUPfXDQ{ePJUZs>mTsE`XAWipf8IrtiQBhNju<6NA6{Ud{!*RU7U2$JMfb2 zvpwH-toqvYml+S)8RLC@M(srY!z*<+Gfba{_r2Np#O(TFeIqwTGiJ%$QXL)n;ma?u zL(lzG2+7laBe}(fd16cC@>_AgDmD#=ee@(uS6hTQ4wW)8_uhm5+t0lIY~AyB;TE>_ zbTPiw8!2WHR*sSB^SvXLZ^xKvAbG4OWOXY>=6aXK7Z2Il1mQh@BaUJ~pXj8l^GY)cEu@&3O^ejW&!}ynEusY z;>D-$#*4R|1DVx0b^dG(dc*1$@Wbcn!yI9@{2{NqyeHO*? z2bLP0ChUsiPn7V6*TxFX;~x>xCveGQj0gmFI_Ml6#y1p7zukY$Z8Dr}J1wR56NLozw^+7O?qgc+))X9X$K{Q)C<-fjtj= zEB3O)W9A+8`xmuN&ZK%{_pVPgCs*$lKQb%@xWEz#BZN40c@=k=1P(1_;7BFz3)x*8v{(_ zM%wPi$@3Yrr%Nj9Gb!KMMaY&HqM%H>UUa*1!77t6cRze%CdalJO?r)zhii`6tKICyq4pSinDAB9Xu12}W`X zcG}|$n5K1Y8zJrt+S;PU7S!5r2X(c+vF8ex=O6UxMX47hMk#U5$NAMOQiY z>uPxL$ok3ps+$b@S^7OHb9A{nZzT43!~dyWhA;D&3eETsj@rxImn>g!r?e6OvAyOP z2*-T>1*LhXgw!$lmMkdNXe{4a#aAbYy7A7HQ4e0XB9E-G^J^EJ!fQMAxARIVX0GkJ zMoXJ%-@O{o@ztV#|0WKkGU_Xtiz}YOm0$V`9@FVN3KQm<>z&cVLHuPcFnamQw4P92 zeSz>-8R}C~u3T*py6&l^GI|BGcg8oTr=?%)IIv^z&`~x@USPcM`$&6!_zy?SHTRQS zSK*0&tTF3}TT^mk71~bQI@ohv{|n1p{af(Fax+hMf9>Zm9sIk%j8|PxvitkyiH}bp z*=h=xXZ@5{&z?s&+P?$j58+ljl0-K9!(1aB$k>!;G86HE%fE&rH305eT>1G6aj%RA zSI2#*e`R&;X`$}kCkJz$>+`%Ons*DHTyFkr%beXmhiU9ePI^YIzDXXb+y5y(djbpd7m#f>YAq2El;D1`CBW7l@Bd5YsmOraP0Bc{~XsU zQ_kP`5$tQPF)NmPnLhs*uhl>H!)s2y zuZ9x!b#C+%sh@j5K|F-Kd!NFO&1apGBz22XwLgLS3>%UayW8`V;YPW!5Y9(*$iH&d z^dp0yU`z}@^471g$n`^w$@_>7@-=L!TYn5QlCQw=QBzV~{OfX0sk>c+jpR$n-5b`L zf_2KH!aggY+*1}q{iMHy@-B*#kboX=T2 zVm)6f{Q0|v;UTFF03`7_j6SE*)I599fD_R>i1arVOYS^ZE?0P_{OwD3!E!`?aealb zr-PMmbdb~|R~GY94X{+6?zs2l@ntZem2Fbt@1DEae$#=3+CS)W}MjH}R zhp(26iH+ttd<@bVhIk%lKK;wSl0I~)?N6_)8_}n}l=bQqNN&z}B>pN!(ovF{exh%+ zzmz0DB4alrl#C<0HG^%dw_(fb&1m1e9m!6<>6CZbZX7dpWY#~EPs)um8AM8YoJVfW z%qKrD)?*-Anx9x2kw&0#R06G&S}}Uc7&NptNQUrA2>+NJTh>l*T*>$|B;}-pjMZcC6?JXOr@@5TK;_29Knc@qcCamWQ-X-1}#m^Xp}Xd{lQlj zGYB~rk#qK8M6L(2)*0ffpA$$#8$61aB57;ZO6gaQC8L7KH_x-xI;S%;0w~wlrZ4l! ztT$eJNlks8iq%plE|d^E8f0uSEu^?5wOj3zd|7AXTzmC;VERn|F87TG9TVM1WCrF} zNLKTTDc;Usv`m*e(79WaDRgXaLyAmC?toNQSZ6!Yn9bmr8Faf0Ix(a@x*d=Kn9eky zQ*!U$HWJTnYr+`mxR39$6^BoYV7knUXto3G*%Y$PNokixEG+_4 zh+Ko8-e&mBIO-hy^V}1$oA@|GqHXE9c-MEX#{2i-Py3yC_J3EK0l9FU*;TA{H=OXb zw_*l=Smh5JKrBIH1`*ko`I=6H{o_`1K+bH=hufa!tbuQkw2Y+0) zG;?2k>GI2Q+h1cff4iS!5D`~-LFX~f()-1Trh?*H>u z#!e~Mw%SHu`S1er!)xz1KgR8LJg&Z!e&Zs`aWNUke&hQ%b#LRd$N%;#Jg66($8nwX z8Gn|ZaLDIz!|4a;7)qyQOw4$5exF&RBDeej|9Ocmrs1d`{2E_7da~&krJeAuZ~q2= zx#c&w?YqbLlyvBpk%JZ)89CAt17E({Yd_LkN|ESo_|Daj=on}{@DsT4=O4jA(`44k z_*2^LX#DVxA2j1Y=ETkC{#|@&7B}VVcyR!D5FcrtD=#Ed=I=-LgQ-tG{@tT%kKuO@P$y@HVAkvKldr!8(_}2OepcrHDHnbPZ`I(qt$6Gozmck3lzsXN z)Z?_*o)h$s*1hFMJ1(Z;u&;a{r_7UiBI774<7xU^&&IFbwTsqY_|Q#wK-%2B#_mQn zbASBomR)__SWyQKPTy!;o6f; z&%rytc{y&n>2mz-+efPz6~Xy%)CA0701oggUG=hABoSM^A((b&inCBtBCeb0Oh~{ z3hM7$2ROGm-syj-8u{B#?R*%AL%(<#zVcQ#AB5(;7r*-Yfp$JTfS>=Gfgs}Zhku2; z7ci4;ewJ$^_YG5zIupM-c^9+J2=qo#+FbglC(Lzd-Z8lP!s9Vd>atBHy(2#P>#yUj z94dsf!Ec6w8IByJZeD(V8Rt;s!F*lMIC3J=(l5N8O?~Uv@JpYrY@^-$+7ofscfO2s zzjh)RDyu7ros&7@P5eFh?rksW*ls=GBe?Ry7|=qly8cXKx300;h8T%Sb2;{9^6A_%>tA>3{>j0YtGuPJ|NbZ3{j!-~ zM}CLn!^|&3JK`N@{|tZp)6a0rH;>dnrsWUY0Mn@l;v3&R6w_p_SDmE(?jJu8d+7A- zLf2*^$G?6~ocez{ZyAWHL)@<#eIPoSwa$d|L?z_ZW67c_qx?*GL(xMV5g z?aci{nr(TX$#WTxmDJ^Zvl{J_sn!a?p; z!53eEyV-lN^UmuVk-3 z#Xj0z>h*~{Zb2W8d;hh!f62^Gk)!0!{gl(OG&uy7RYIaqV1A|KM^JeHIok>-*cbVE z@>M5G#mCN2pn+mvuq#xnkjqE3=O}|l~Pijfl*3gJvD9jr^ zBwu3vKbm&xD+BNJ-;7nPhqV?xqCLzW7vMge17Noqt;}!2#-t>G&Tnd z_5EHyY$Ag4YeT#a2Ic!H$nS+DUmm%8K>KuE$jIQi+1-uLz9irNP@St!;xn&HiIK=teo2CaW(H&4dR@^x`vK2B_FT73y!~u$;o!zzTK8dO%3M!jJee|6`kx z>T#YDS{90hz`OlF=kG(G1n_>@@3( zWZ1MDsUM7tXk6A0p?G71j#~y68vmF&(!i^$^`*TTyHb~Vl{$*~3j zMkEcS#=LXZSAJYq#Xi+R`<=h8NNP?EJRS2%*A}&pX+G(PhlH1H%ft^{D|F2i|8m`F zka6dTYmKfq!dq>Z`Wiea`Xx0=N=7*_RQdEdXutki|#~zAHaq}}d+n#;q zDxCZ7UG;~!bq`*NvoCm629PYOzr6wfe8tG#<3r!Yf1bEoWZ#9C|9A$D-=)EO?P|k_ z*Qy*NQ~aX2ggJKtK1y zgz}Dyk`L5LN!p1I{&@AHW@jd1-2&Wx6E74jqt4{*V0^}j7yIj1@wGoQz^@TAj`%YE z`ULeApFQzMTznFJ)`@lhxg4io_Nw0O@uu&=Z(rv1*}fmX7%x8|_1Wgu?}*?0eJ1=V zBl-l+{@kz3ZhT|+!iO*Xv#GCguEG!hPvoSNSpLdmq8G=VX>Gu9V#bT1eE(}1kLU(| z3ft;;chVas;kELrFX2np{t0jV5Po}$jG2e-RC@V_v+(ZSWWIK;$K5~uI?h`rKBIhD ziQR9+vJNSq-(B=woN?AUIPVV(5~Ge*uK?2Ci2McTN&PdgF%s6vC z_F(W=`_s7RTUcyW{kI}%xobM7($U6{KUPU8+MzICK=!t3y*8~!JD{!8iQ zpPht#^+(~wxbedC@c{WyvTKu)Idal>aOb0}e+Qm^>;`=Mty48vX3K+DF*q*Jso8GNTJ5aIeip+3TOZLFL-<-=gVRE*6;p}r$raiofjObuMXU@;LkYkTTkfN zTk)H};eP%I9iNJ~o_!TwdQ9p+dau+y39p+Vs_VFGK+|Z-6#l%hT(?kPBMZ#N|4H9I z_25mo{Cyd{n0C`iFGOo`-s!*b#_DMo-011_%oM=tJ98k6c5K_%V=F>hX^bWDmfFXB=Z*)%emKIRC=6CV21XKftvN1`~fDf5ta( zDD_k}Y)k%3c{l~%I=}>Pc0Tt2zPEyZ81g2@tQUF5D}IK%pEEk|KL5R#F1}lF(dD?} zF}coSBo6+}Z}8$Hx8vyt|0!$tA8^s}CP;DNtykazj`is6_}EYWZC(w$; z?$Zz6j+buuC|<`gyzx=|=A!?YKaAo};p<Jx;Vj;;HBRQ&Ky(f{vw^1i=$^aSaDx8P4_atrJ=IR7i+KL$*V-2e68jgJqB|8)G29{dHqr}Im$?Ten424dGO-@+xg znO6hPIqvJY<01M(>dN)@rcck;d$cVN-GJ}^O)heJ9g};+Mf4woDnG5`^YQDyAgY@` ziagL>ohG7_Q&RJUbQ}oDv5o}?mT}FG{R`jy?c*9~r}Oi-qSOCw#gh;IOLb~27<}ZQ zT*#X8I2uLI==FcW9gFGfk=XtHV%r0pN4H|Z?>>v;=H|w5!-)CXM*U~91j@{$?%2~+ zQm#E*L$Ciae(uK85uXYJ|A)1%Xju8`0_C`MN2^{f%%`fM*7aQdHmkL;_-X_g2zZ#e;i1E zxT3m`UiESL(0fhvyt;kp8{sc~^WaSj&2FQDEejsTHFAHTvboMgWh^YTg;UjuQ|*!eV+r%OnHt3ymVwgi+Aa(f(5Z1 zaO}DNz~kcgM@7yf;`2xDxyjJoq(9@~o4$$nkbC52eB`^ggMU6X&U_=zmTMZ@JbI7N zAJ4_dUq2R&UF-46b5Du5eE&OLRZrR&S&s|9c8v+j9lJX|_=9T1XQxLuIW$ zx$9|IpCG>s7gGDXt;fTK%r%SGYd#y`NDPC9`$sxKyTcyVoS1aW9EKE}TN~2vyt!Fh z())I+$zX-ip5KF%Tt9?DR*gcPEV)lmS;fSEdT0+kp}FIn47oOWX)j2BGm`d}G{7&9 zH-r8RnPW;eS0n1;J#WeK+q7f3exzJ~)6jZ3(mee=OvvU*rkwjo>q)kJncsI<*FKw} zt}4suM0t8_uhID=*_O0PpGm$nr7WWpNuT$VB#-4rrpVWi?>u)R0j+DJ`auq(m(o!CF$)<7tpqTJK9!nL))4*bZzQHG{wNP1h~7W8`r{B$B9U@ zX-_`E^&L;@CW^_iZX1JiB#lTSixJ~mFnUrennpJu(gLI#q%4-!fI!-*{ZHeqA84~W zqkx>xx)Xil{K#Yaj%4yZ;+SS0e=~Ds)Shdd_qH=ggS(G5 zHZ`GT#0Y(?ylq<>+J&}n+m7vR+vT`J(oRbq=-AdNB&i&4_tGvQ=69mAt;-OPeZ;m& zz7CmhR+EoH^x+Z7X>7i_!<73AT?<2n&n?wv!e()YzpSh$U7|;)*w(pqJ36+ui_YzG zZqg3gyp^`MtNrb5?Pz0uM<=prZtP?DY9DHVFp1|uByv3pDlbhk=H#FU6z}XryrV;u z^hSze)w+#%dF5I((G#Oyg9e$9nKXkKy6~FWS?n^Z9r0`<8fAirkHk~-n7A{Zm0{S1 z5m}i8sTNVF1skM78=BD(8I4YHMmi-O8f}uvA}e@gE1JaEcxD6=Z5do}L>qQUtwuT# z!E+lX;?AWBnIvXVn*or!>=W_r+r@^CZgy2fc2XHejc97gC;s}i<(2>8H)nhh@44t9 zQG*wFUPPlEfp7nHF}CQhjfQ{Mx)*TscYcA#^fhH<#Wox%g|?9g`{h+!^_lnJ=rgXu zKc3!zE$N(XO%U5wJdK+#_#EE-;p_0cy(mVb58)z|}rT2UU-@V5yENth% zbIIU=O@m1XzZ{xIW)Wl?_2q-V!QIcBqGb>M0e=)bo&SyROquE}Oth~==X<~T0lfPM zx8nX+wxTm;^^J+o#1FUp;1hWLXa9tiykJ>*?6b4q{^`6vW1CLxv&+3c(|)!TS^h_S z;M8+)*&Q!p-4>r;uodf`xgF=6dICOn)vMCT_|t$kTffLLKYE;3ZtT}QyD;R+oKJ&E z`%HXv@3%jIqrdWd+_gyT7mb{5vI1h^gSX(E_rDzjC( zi}@)jIJ>j9)iu*3R05?Cg0i6xj)|B-3qWf{h=^wy1?ny{p+A929lcoQ}m#&gE zK=`SD`0M@F4S4S6i*e#d|E%LfB#VyJ$25@>!>1s?#qB|2xF z?>jdx!QI!KgA?EVQ`{>zKpFtI{LlE{`!2;TqW2aC|8d-LoKo*IzmB`skbfg)?0KB} z!HlI};;Z2xF_e2EEi>0G}Wi&$TM?Z|g7WxDPtcrN-W zf8j0T=JLPTU5?tH#U1CbQ=N2ZzV!tFn~2oH$A z^#(Pz4(DBbrwNvtc{sjx!GUaK{YI;JBsB9B96XPCop}Dg+d+?oSIRE^ z??MeM!Mua<#e?air*P?MpTz0cJYcRdc0JL)UGpMtIrpDN|c(I~@x;tM4 zP*a1^KeqBOIPJ6_;unSf<9zt<4fyi=K8+vzw?XTg>#DK$Sh4q(m$zx4uGViz%36O1 zzVoYR%r0jF>cz1dld*R7$3KNLe)|}f^Zz{9F@UXH2hPPm&D;0|tS<)m?H~{OMb{ZG zQ?ltt)?==e>#;4>V~)MJ==x*P^{2SCo38)L(^c0E-aAenrGabck~+dK-^V#pKg8DK zx1T-*?>z5LJh4*v=Urd6;gyGO!}m`+8K1gtqxg<%idk2j8D=gUJ9J&QsV$Z9V8%=S zn4n!!k3Ny|ADwkI?pbMqid1eonWtwU>AF@ZOY~5`Tc29~u@yJtBd48-mR=hlcn-2DdKv2iKx`Rxz!?oZx~Rjey(CCAg>zj6t# zdzOJ)mN&!}l`T4GE+Kv((RUW+x?|=qYf20Gc90zDdmXc))2eH~fp?zyN8B%cK3*_Smg4TKzXNwW&UJx4Vn0#8JYv3(hf;Ng zbls8uSoy0j_Nz_slU1u6J?NMD_14SQnhOvKAEPob<07bqb4y`$wG6%_nuL9qGM+`HLnTlC{X)pVTQt3h^TYF*g?L6b{K; z)P4wo%vxadFwfHRshh67A^8|t<~x`2$lMe0m9M#bqu$>mF{sPW$K7ix6|Q;w$hlX~ z_Cv0oscW7Mllc5ID9tB*4XAf6{A}ty@8`2xKF^YTqpOvreL)>^$A;V|A8KO1kmJv1 zY*rrg9Qi&{+ZSo!jvaFO`Q#8#W@Wh4FVodwu#$$MFqOn>XP+f8p3?9zy6f{`J|oUb zDimXy0#e(%u;j%>c;xNk`D2JCVh;jT;ZKcpQm_grvfgeX9J0JNuT; z3-x)sEwjhkze;i}{jAU4DkP8BFd#QCbgty(akjUPx2v zs|`ZyS6Je)qZJ!$nMw6n&n>xgN2}Ih+s19^>PRBZKwqD3{J210&0|`Eij4>PGDLpL zM-J&Z$)nB5jxKC?bpzI|5c^iFQ(A9n-AZ37`fgagQP0;bU5!nzZbWi>ihJLgwM+X| zWO{w>j{U^^KG3A;`U50AV(a%CNI+7iEp0zPJH{6qmb>~fUW;K4I{c% zA(n`XZ<=w*QP)} z-n$w*jNXh6BysJt^YF>rCL%qy3E7MYZEM3thi}3c=f8q%G9@*daprw$x zIzwN9NZs_99hAX{RjV*!rS~gUkH8&a$WwQQv6rBHNQZDdaDvH!&NFq8Om?|)X?sm` zZFN97u#IU%VOpQoBE!b51^1w;=Vcp32YaYT6Q(pO6kr z$Ge+sTc0xIPYTTPxOjDoEwtbF|)s6M|DfAo5tuE5aw)6F~+G|pO8z;lalM>%p$m1bU9&D?6 zO1nJ2`E}~vHW<{$kTmw%8WieQicj^Swu_!-WQsn#D0;RR!1~$Q!xq`hJ8|{@K81r@ zx8nZuj>ZSB<;}9RwRR~l`vW$?*RZ9(eFa`RaTc)X7VLc71-iKB{E+jH^Rp+@*FQuq zPjacZmFxGr)78N8W&c)Cm;cmAtxA22Z(Lpfyx<=$tf;Tv3($#qUOO!}bqJ>u{lptR zma8r#SFYC%{4Y^eG-vNb9!(mGSgn`3YpiD=a&gHEq8j6l#}(N zzEyY4KMPswpth-QqPSksmmKwcDj@fR7YfK9cH%(Bx&l_!BHRb!TAdzj4~z&&NaZ$A$9DgOTgK z($#1j0P5uAa;^xdlSet-<;%<`t2<%rqYP7@a*ZhFXOQ{Em3Z-8vZic(6z3muCa#e` zY}nqsa?Ebz)--i+hE%X=j1W$HRzRf5%#&<7OiSL#LC_{BjL{ zr<(zFFnw>x`A_O;{hXe;GLrEV;ev{&FJJ#m7+$80%2xY?bUd+K%B18*hbxHvuW?yqdGg^NuHHW_ zAC;8DkLqJ>Wn0aaZ>f9oWjEm)2ag06--f;3^;7x7(9SJ2hYBe>%b&J6pE*BB5BYp% z^m4iyD%#JY7r`=KdkJHo^+}-&o>C>RkI645TVeIo@gnt|K7n;TNE_E@OnL28NINE0 zZ{;N)KG5>~Kx4W=Do7i(NmAo`HQJW@Ve1z3mzD(fd!a8a|2$tZk8Rv5s4176nTU0n zx8aIAKV#OMzaE9R|DZ|Q*&wn!(C?C1`L0ilPyISjXE!daEgS>1jq=p5W?UMZOk1a; zX={ALwklN1M3&r#=-jmYa&7!R%IV^e$6Na}$oDz0J^2{jDTh4lco05bbFD}FDc9Ps zbwnRI_v>mvJ;=-I!FtRf@^~43Il27blai|7eA^sUXYB`jy^#8>o9`3hAo*rK6!e{z z7l+Z(=wA&u>7})(tX8FPrueCpB znsu`32y+mGJOD#S_3lFk3-n$vbSDhoPEgCm|6zGR11)SiJmCxv&jI1}z7L|pfY772 z80`8B!qQ&v!R$34QWY(Pr+1_0_~-8YnA80d)WC&Dm*nS1(nPyHBQ zXn%^I&95+o`TfZ}L)Xwv`Ig>Irjcz+W8>0wSn~8DY+JDjnGVSlU&iBRqXgC@(tK8G z9`kw~Rk_EV9xnBRYlvvbKv5CUk?cS!mO{g*1jbArgB^F<5fi3QLOPLEO7c~~Qq>Dm zQ-hf$e0&P!hMCE9&xgB#d?cYKjW;<5C9^9l^C>3% z!+=SVM_Jx)vm)HI7ouM5HgYu}mA%PJN;-tC09ubm7j4 zDY@1pQ(YK6b}XjMm?AzJfmjRU+PIGS6)B=r9}t-|qvrvE>82*E-)(m}G99A-gv;)u zTJYEv7bD(~!q#VhhEYp@ifn8wVyR9nOisf8-831GZ*G;!DH(V&G25HP$PRGVqGT+C z#P%)Xm=VatTQM%S7Pp_=DU0xG+_!QvZhkt7d)AE-2R9+o34CtRoZ1mC#W~n{d<=@!XC0U4+F4lpG{>Lsp*YWc z+g&}!CWu!dd~Nj0-==&S7f8JT73wX8{1n3UYe8LwlhwVALr;8dM7X-Ho)hikA%{9Q zlt*DZqD0%0V7o%Sh#(nZfjWvFstWs%X$if=9?v4VamZxefvf)aNgOdWpy&CJ@9zy4YXcAEc|@(WHa^gVW}-EJ%;`5{dFbB<4usauZ;Em zicx?^UcdIfU@x2;JCUu+o-cC@!;c~uS55`<@k7z<4krwF1h6#967ZU&;9lz z_`gfm%k&HiULoiQY47I|1Wzx=gY8V}wY6oYPEM8vYfL)aj?bkj3eFW2YU@j)8nB*^FZJj2bFGaG zbDZ;?spl1_JpMC(*tjY{mU!(Ke@QC*}um9qQvCj>^z)xh&@pxzDzGwf_ zar@8Tgz2*8Jb2N2@Ua^ji_CNXc`2Xzd?zsa`PxchcbjavDPeZ_Gw5c!2V1WsFNa*+KvoK$&XSl1 zOF7Plr-jONWo*JSA^Dff?G95W-=!+`1AMm{0r1(;5AZ~htvx9xA8j0pVl&E>vzpzMTa@!Rd#6W@IV;fyeF`S!xF>Yeu55g zRC}n@VcZAC{;(Y&@`rIB7=-D7aQ(0M0pY69F9`L1rms-dL)71jHcxu`8^Tj>vkG}j zrex)QXDLMQ@69T+BUgu#*hZgkGazu(C`=eX7E`89(SX1vzSc;}NFHx{1Mg|>smmPm z%+x$|HQY^55`Tw``HX+*5fKDWlPV)_wY{wuT{>NM48|5=FijqfA>BOR^ z7Gw1b3JX+Ii|fjc1Tj&^bJzj{FC zQdn(W5~p47fC*r6;sW%{*H)mQmGWWx;im&@?`9|>uj{{FdB*0 z24tJ09)m`!h{&WF^K;tw0GoH*5uIbk+B1RZ8Qsu^zyAEYIChU&$abv5w!2S4OJWPU z5?zR;TJYBwCgCGDO~sZmF(jljlATGQftSQ6GHFpGCY>h(HIn5kl{zqMWCyl%#IYgS zA`a_7D&Bx-XB5XyT#6sRUCJA_pi>6c;_Z|0p5Hg&$+lUD$F|CVw%wBJT|H^sv;m_R zEwUNaf^(;&PIk@V1eZ{tSG{gv7NF3K3mt>ravESdahSp_?dk|Uss#M;JCivPS871u zHVp_o?fM3BC2!hUqktDLfVxBAZ}6fG2%LAq#U>b{Fd%N2!41En!_ZwZd(c0@C@R3F z<>A#7R+Y4>q)vZXS3Oi;L!E_TJ3!>~<~O^?yYTbxzZbi&xC$qK;wG%j3s|Z#xNc;3 z#@WCBAx_>2SpM%{;B%+njORF6r0kF{{1`v_;O=OZKT|b$@zV8ThurGF`&+o@%df?R z_0QqTAN>I5{e!O{72BpCk01Z+<9O@LMqvE|_{I_6!cF?>XznU#`=Ph6x}JIc_l>T1 zUXIV7ehZ!_ODTKf=P$(tR@djRHG!6R>lbk2*A7GDo!`ar-+NrQMKA7ar<-5L!Wq^F z{d)w;tz>rhH)vle8~r-v*V7Wo%GGd3rk;LRbT8oQbhlsUPv%Jc?C&4PA!9b;iR&-N zS1$e!R%qD{IO)7|@s%T{pb-oJJsF?*n=de|tmxvl^&e^>BM4Tm_@VS1?C$e?_}liR zZY6N_3)@MbbMC+MYq<0D{bbE~9#>s>0e*D5dF^p_+A+BJCm+F)GaF>hc?jP+`g{0m zOzxHBkKQ01DjI_Q0_PvUKsnaazA919_YTG6_}lkY7b3{kAn<`tIrKpvX=KeZ-Qal1kUEuA(@Hh~N`2*n6(5s+WDN!c zlCqJs{shaQKpKi}oXPBr6>t-5EC%l|`--0}xaw%p)1y z{EPuootaKFjBCd1y>`N+IaAR%rWuK*1d>@9b6FWv8W1QtDJ%J6GxL>)+%q9lreiG# z%7SHPUg(jIeHpo_v<3ujU(ykdMC%Lt&yE>X@q~tVG z(tix#W6GdN`kDTaGhxgC+hiswZMZ)kgKZ5Xv(7nE;quS)l^$yzk>lMRT3Nwbrk;>< zp0_SDU-&UQ*Mt={D<{*Y{yG7mr%g(5jd9kv6~mMpw&skvx|Y!2;+v%OO=njprc9fP zo%h-qqo<8QVq62#EoluftR^Cpdl?Xz95DhLcG<jK~xhF z!?O(2W-CHR8@-l`h%$hRD?ay6Rh9%5} zp=UnO|0$GyJFfh%4G6s8Se*8|2J=5dg8V60RlY(k{moz7fWW_D-ia5p7LG%?lb#f0yqEb>_jK;1!Ik9Y}88Fvf!`y>M6g#e>Jd= zH{AQ;oZp{~cg}6l^3HbN3ke#ejq=!ldw%`}eCk@a`=PlJ?wgfP*K=^)r%UMi0(HF{ z|Kn>XMOXS;Wy?Bf{%2h=EnI%zwCo?anO!);tBJnZUruA?T9mcFz#Rv0PmoQ=5m@-y(6>o>_kg3|IO>w zvT5Joi6T3DQjT-j_k-2)|6qWzy?SG?5V_G-y3dFEy5xuU%@7}?VRkoN>t;Y0Zg%T) zm_AV1p-npH^)S5ez`uj-;V^}$}GsmV2Y$H}&hJ-NxYYihD> z+qP}{?B{pR=llcrt9yOdrL~sx`3mPEJnyYgK!tCNA}h6ju(*ZL2&x{)DucZN+)6rp zm}aA?C87WNSe4A>jASyN4DJsKh&MHAGisERH*};t5&y+>cTF|d@p8+}6jXBg0Js_6 zj94YN>$7OBHnV?61(%k4g{&&p#iR-Or1VFoAIPSbY+Z-G^6_F_(*vG2NN~ua@@84V ze$$x0z#T!zaPitlKS|hy5eW1sQ&^9r?AdiuMl+TT7*Qm{t$GiKc34b?;bc>*V5wy@ ziYmB`p+IyLcLBjmW8t)((-15dq;d-4RZOmAV7%QFVnUst-jEK$4sY{P8dc{*K^m3x z9SZnTfG10hIwl?Z)LU?4Bu-UeAc2%}&s3__4ygF{z*gVdj32|4qAzuXc4Tqxh8vN> zecNO@waQseGgxufpDgnhU4?<2L&1|7lC5t9t63P{b-QL_I64|S$Q6ppMmB>FNG=Sh zz^yk8`OUE6buJGpRUo{w3#w#EF}eLz#dqLGlGt~jHKK}aZ3P36xqpv#~cfj&sg6kNgn4{3N=2Zy%B$dzW523msb{+VD*GY_)n{(KAzC>K}qeaQKNKk z5OV#cspLC#oem(Xl!8xfmd)_jOb`lQn^6@B2zNXq76)Ib&Bd+H?zcboA=icnq7GRY zJ~t7|q8#b<8)&_LHOe;OMdX8JFg=9+{H+lHCurC8iRa99^(+dv7)r2}>`LOs_6-Sn zT~Jx~qG=^G=@4yaKcOjMgv6rA3+1RRiq3kWrms@nnpwBE*>El!&NH%WT@%EAtJ=eM zChFwpu@N`XWD(i@uAj+bDMyf^6WRY{nh`F#ANivMrTk>I5i9EB#Kh~Qwd17Zx14Zw z?bHXD9oqJ#o91%J@V+a(idquE;iL9^Zvb-Ox;(aGQ{kuNq0_}YhTFI3=#ap^NIULHA8^ddhBOnnf*)vRIR<`=OgPb_b6| z{Li)Ql4YQE7}a|^a0$7o!#|il{HbT@lYwW5zc3Zrj))mUl6#T)S(hbxL9ja`0bWrc z>~AKn`U}wvSn>O85*Vs@Qmpx65AtKP0o^;B;D8RczeKzW_Ts5E%^jewVe6s5dOKw_ z!3`U0>p2hrLP`6>@y+(jC2kcH!3>2F60!Om+ylTD7GGVNe^lSL*xh^{(O2Q%Y?d3b zN0Jht`kG z&4S=%bc?y!34$n9WeGy11ah3>Jrj&4Q_jEtC5lL8At;OM*WVwnVeuX~3gHmPunxb3 z?|$AYw+Xjo^``tbD9^Y2TDXOEpzbgL7xi|~r*EOA=33O%ww@?r)jX7Wj*5q!B~f77 zo40KWfbEZo_Fzkj)o`-57)azKygZIoRezrTY_(BM%y?zeTVrpe)E~u);cm{ofM{fRkWQu&AA!LtVljtVqpGpf6CbDSg*e^F0 z#^zLGlx*!|G^A=MSzeC2Lwn$hE(%{7$mSbm05Kc-vKvJ!6A;+j zP8c+J53qlnacvKyCAwY76y1+eo?M;_sW(F!D~>t*oJXQ1gk$Et^=cZWK`yArHh(Fs;<9Y(ZG8li2I?PiyCycQ2Vp;#DH6;SW~`m80GMGc=thdU_Ezmvy9in zwBrmu4p7OWalK}EALvtMC|n-Vx*jvi{`sEiWgU=%6A3MF8u6MF2)1mWVNe&D+s_aqcBSi3sxM7zk;^vXe*;y!x?cZ5j`C8E+ydGE?#)l`epDhghSwg>((I&SWedOnr`?Y@8iBGs`l|b)W3MUj?fyT1dhk zSiPTOhGk!SgbLQGGYgy{s>*SbKHn@6{PcMof1vnK!1&Pbk+ZQO{i1p zA~FoWa7sJ4WIsKNX^dt?4m+Bh$Io}|iu9KJK64NU6p9Jphbt`5eL~uj^uLvJgo!J> zAmjM7Qy4z5IAj*vx;iXKR0nRogu|OcZSee_6`)JA8#EsUtt}; z+;Nhzr1amHS)1vuf%|?kbV~!^hG}qQ$l~^Iyov`*9}L1UZ1<(f@|ouHkv#rw@Ri)= z5owc?|D9DlyYivOnkI&M)AR9(1n-&a8(|4oUoSB22=pk2skZy?kQK9wu|NJGy;0A8 z(4`5VK<1&jc^CaS#55uHf)8k+Yq$*_^J7`q~_dXi}VjTE)AqKqA5!^SfmxE~h% z>{=@JsO+lE3&Aw!8#ii#R;1jhTBf7uQy3Agax^P`@|$_V#NmJBKhVWF24Yq(p_Vfr z;WXnIl_Us;{A7NlPAzfZJTQ)JGf(tgZN@UCv``wuP@#{w7!1S^yPYs~Zt?$R@!aL& zFj5{Y%>qjTQ@6hm47EfjD3 z?|@0T2YcN5#~NriDq>jCyyd#23M8tIQ*u_Z(`AA)Cl-ED=3FPL%i)BIydQJ&V(_bs zOAOqe3_^CZ432h;bJpG>95N+1!9r-EUNPg!!D zKQ>MeREP7$Tz^qZwA174yrS=-4c$aQM^U}}BK0~7HF39dhhOSJ+D-=B`p}}BrRMpr z-i$iacPw4V%4j9iqY!}Rf*w0+Jw!<@d=t_Yg36BU`GfvJVKYC(70eaIo(YI7`rAP@ zW=HY`33NBYX&$zv71kkS#aLRzUZOAaowkx}LuH?`^r1l|zeI1Hz|{Z(Ogrp9F7G{0Qa=I(?K6?Opif%;`Ie0(K>?*SbcU%nvFF>y1y zY0A9_O#Mz*L7g~&(qc1|OnajT#!H8%}OY(CLU9uj0Gkgq& z^(TMS?uL7-t8>+#>1R}L_ewxP;C=RI1N(1bCXK9AClXEl zA!SqHr2vDxb|1|}R+gUw&Y2!2r^i}NN;X7+T>|z@hHk$OBczoE ztDbvKAfonfRp!#}kXAoo%!vYMFp&=%uGk;HoUiyyQG837YsJcLmrFtizb%Ty?aHN& zi+i7W*_MJNsOXU_-F~`QD=kXbsFEJr%DA6*5zWY>bTSmhS?>6Zd_|c{lWXiBJnqV1 zkYi5_A-zj3KBWAzKK~8BFtyM~&$Xa=myaEr|Dugw^RK+Hl@+_vn3rlj+kCy62oSEH5P=I8#>#JZEoW`jVI1*~PMuEUz1k?*gna^x;{XrRU+f$T{` z=OBlVgod(M(&Ca(Xx<=;PnI52Cnc^3vs$F8%jK`%kLJF1O-}?4B$#h#tyqM%AF3}S zr