From 53da57768aa93f10fbc2bacfeed1115517b2327d Mon Sep 17 00:00:00 2001 From: Zach Laine Date: Thu, 3 Oct 2024 17:43:25 -0500 Subject: [PATCH 1/2] Remove official support for VS 2019. --- .github/workflows/windows.yml | 2 +- doc/parser.qbk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 11de0a9d..87b70a01 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: cxx_std: [17, 20, 23] - os: [windows-2019, windows-2022] + os: [windows-2022] runs-on: ${{ matrix.os }} diff --git a/doc/parser.qbk b/doc/parser.qbk index 1253c947..d732fe97 100644 --- a/doc/parser.qbk +++ b/doc/parser.qbk @@ -270,7 +270,7 @@ _Parser_ requires C++17 or later. It is known to work with these compilers: * GCC 9 and later * Clang 11 and later (and XCode 10.3 and later) -* Visual Studio 2019 and later +* Visual Studio 2022 and later [heading C++20 Support] From c0136f315f77053d630713612f1717f4d03de934 Mon Sep 17 00:00:00 2001 From: Zach Laine Date: Mon, 30 Sep 2024 16:14:22 -0500 Subject: [PATCH 2/2] Add insert/erase/clear operations on symbol tables for the current parse and subsequent parses both. Add better tests for the added API and the previous subset of the operations already present. Fix errors revealed by the tests. Fixes #183. --- include/boost/parser/parser.hpp | 205 ++++++++++++++++++++++++++-- include/boost/parser/parser_fwd.hpp | 9 +- test/parser_symbol_table.cpp | 172 ++++++++++++++++++++++- 3 files changed, 369 insertions(+), 17 deletions(-) diff --git a/include/boost/parser/parser.hpp b/include/boost/parser/parser.hpp index 8973abef..68fe3731 100644 --- a/include/boost/parser/parser.hpp +++ b/include/boost/parser/parser.hpp @@ -1609,17 +1609,66 @@ namespace boost { namespace parser { V base_; }; + enum class symbol_table_op { insert, erase, clear }; + + template + struct symbol_table_operation + { + std::string key_; + std::optional value_; + symbol_table_op kind_; + }; + + template + void apply_symbol_table_operations( + std::vector> & initial_elements, + std::vector> & pending_operations) + { + auto lower_bound = [&initial_elements](std::string const & str) { + return std::lower_bound( + initial_elements.begin(), + initial_elements.end(), + str, + [&str](auto const & a, auto b) { + return a.first < b; + }); + }; + + for (auto & op : pending_operations) { + if (op.kind_ == symbol_table_op::insert) { + auto it = lower_bound(op.key_); + if (it == initial_elements.end() || + it->first != op.key_) { + initial_elements.insert( + it, + std::pair( + std::move(op.key_), std::move(*op.value_))); + } else { + it->second = std::move(*op.value_); + } + } else if (op.kind_ == symbol_table_op::erase) { + auto it = lower_bound(op.key_); + if (it != initial_elements.end() && it->first == op.key_) + initial_elements.erase(it); + } else { + initial_elements.clear(); + } + } + + pending_operations.clear(); + } + template auto get_trie( - Context const & context, symbol_parser const & symbol_parser) + Context const & context, symbol_parser const & sym_parser) { using trie_t = text::trie_map, T>; using result_type = std::pair; symbol_table_tries_t & symbol_table_tries = *context.symbol_table_tries_; - auto & [any, has_case_folded] = - symbol_table_tries[(void *)&symbol_parser]; + auto & [any, has_case_folded, pending_ops] = + symbol_table_tries[(void *)&sym_parser.ref()]; bool const needs_case_folded = context.no_case_depth_; @@ -1627,7 +1676,13 @@ namespace boost { namespace parser { any = trie_t{}; has_case_folded = false; trie_t & trie = *std::any_cast(&any); - for (auto const & e : symbol_parser.initial_elements()) { + if (pending_ops) { + detail::apply_symbol_table_operations( + sym_parser.initial_elements(), + sym_parser.pending_operations()); + pending_ops = false; + } + for (auto const & e : sym_parser.initial_elements()) { trie.insert(e.first | text::as_utf32, e.second); if (needs_case_folded) { trie.insert( @@ -2153,6 +2208,67 @@ namespace boost { namespace parser { Iter & it_; }; + template + using has_parser_data_member_expr = + decltype(std::declval().parser_); + template + constexpr bool has_parser_data_member_v = + is_detected_v; + + template + using has_parsers_data_member_expr = + decltype(std::declval().parsers_); + template + constexpr bool has_parsers_data_member_v = + is_detected_v; + + template + void visit_symbol_table_parsers_impl( + Context & context, Parser const & p); + + template + void visit_symbol_table_parser(Context & context, Parser const & p) + { + if constexpr (has_parser_data_member_v) { + detail::visit_symbol_table_parsers_impl(context, p.parser_); + } + } + template + void visit_symbol_table_parser( + Context & context, symbol_parser const & p) + { + auto & [_, has_case_folded, pending_ops] = + (*context.symbol_table_tries_)[(void *)&p.ref()]; + has_case_folded = false; + pending_ops = !p.pending_operations().empty(); + } + + template + void visit_symbol_table_parsers_impl( + Context & context, Parser const & p) + { + if constexpr (has_parsers_data_member_v) { + auto visit = [&context](auto & parser) { + detail::visit_symbol_table_parsers_impl(context, parser); + }; + detail::hl::for_each(p.parsers_, visit); + } else { + detail::visit_symbol_table_parser(context, p); + } + } + + template< + typename Context, + typename Parser, + typename GlobalState, + typename ErrorHandler> + void visit_symbol_table_parsers( + Context & context, + parser_interface const & p) + { + detail::visit_symbol_table_parsers_impl(context, p.parser_); + } + template< bool Debug, typename Iter, @@ -2179,6 +2295,7 @@ namespace boost { namespace parser { error_handler, parser.globals_, symbol_table_tries); + detail::visit_symbol_table_parsers(context, parser); auto const flags = Debug ? detail::enable_trace(detail::flags::gen_attrs) : detail::flags::gen_attrs; @@ -2227,6 +2344,7 @@ namespace boost { namespace parser { error_handler, parser.globals_, symbol_table_tries); + detail::visit_symbol_table_parsers(context, parser); auto const flags = Debug ? detail::enable_trace(detail::flags::gen_attrs) : detail::flags::gen_attrs; @@ -2281,6 +2399,7 @@ namespace boost { namespace parser { callbacks, parser.globals_, symbol_table_tries); + detail::visit_symbol_table_parsers(context, parser); auto const flags = Debug ? detail::enable_trace(detail::flags::gen_attrs) : detail::flags::gen_attrs; @@ -2332,6 +2451,7 @@ namespace boost { namespace parser { error_handler, parser.globals_, symbol_table_tries); + detail::visit_symbol_table_parsers(context, parser); auto const flags = Debug ? detail::enable_trace(detail::default_flags()) : detail::default_flags(); @@ -2377,6 +2497,7 @@ namespace boost { namespace parser { error_handler, parser.globals_, symbol_table_tries); + detail::visit_symbol_table_parsers(context, parser); auto const flags = Debug ? detail::enable_trace(detail::default_flags()) : detail::default_flags(); @@ -2430,6 +2551,7 @@ namespace boost { namespace parser { callbacks, parser.globals_, symbol_table_tries); + detail::visit_symbol_table_parsers(context, parser); auto const flags = Debug ? detail::enable_trace(detail::default_flags()) : detail::default_flags(); @@ -4976,6 +5098,15 @@ namespace boost { namespace parser { } } + /** Erases the entry whose UTF-8 match string is `str` from the copy + of the symbol table inside the parse context `context`. */ + template + void clear(Context const & context) const + { + auto [trie, _] = detail::get_trie(context, ref()); + trie.clear(); + } + template< typename Iter, typename Sentinel, @@ -5025,7 +5156,9 @@ namespace boost { namespace parser { } } - std::vector> initial_elements_; + mutable std::vector> initial_elements_; + mutable std::vector> + pending_operations_; symbol_parser const * copied_from_; symbol_parser const & ref() const noexcept @@ -5034,11 +5167,16 @@ namespace boost { namespace parser { return *copied_from_; return *this; } - std::vector> const & + std::vector> & initial_elements() const noexcept { return ref().initial_elements_; } + std::vector> & + pending_operations() const noexcept + { + return ref().pending_operations_; + } std::string_view diagnostic_text_; }; @@ -5598,7 +5736,9 @@ namespace boost { namespace parser { {} symbols(std::initializer_list> il) { - this->parser_.initial_elements_ = il; + this->parser_.initial_elements_.resize(il.size()); + std::copy(il.begin(), il.end(), + this->parser_.initial_elements_.begin()); } symbols( char const * diagnostic_text, @@ -5606,22 +5746,49 @@ namespace boost { namespace parser { parser_interface>( symbol_parser(diagnostic_text)) { - this->parser_.initial_elements_ = il; + this->parser_.initial_elements_.resize(il.size()); + std::copy(il.begin(), il.end(), + this->parser_.initial_elements_.begin()); } using parser_interface>::operator(); - /** Adds an entry consisting of a UTF-8 string `str` to match, and an - associated attribute `x`, to `*this`. The entry is added for use - in all subsequent top-level parses. Subsequent lookups during the - current top-level parse will not match `str`. */ + /** Inserts an entry consisting of a UTF-8 string `str` to match, and + an associated attribute `x`, to `*this`. The entry is added for + use in all subsequent top-level parses. Subsequent lookups during + the current top-level parse will not necessarily match `str`. */ symbols & insert_for_next_parse(std::string_view str, T x) { - this->parser_.initial_elements_.push_back( - std::pair(str, std::move(x))); + this->parser_.pending_operations().push_back( + detail::symbol_table_operation{ + std::string(str), + std::move(x), + detail::symbol_table_op::insert}); return *this; } + /** Erases the entry whose UTF-8 match string is `str`, from `*this`. + The entry will no longer be available for use in all subsequent + top-level parses. `str` will not be removed from the symbols + matched in the current top-level parse. */ + void erase_for_next_parse(std::string_view str) + { + this->parser_.pending_operations().push_back( + detail::symbol_table_operation{ + std::string(str), + std::nullopt, + detail::symbol_table_op::erase}); + } + + /** Erases all the entries from the copy of the symbol table inside + the parse context `context`. */ + void clear_for_next_parse() + { + this->parser_.pending_operations().push_back( + detail::symbol_table_operation{ + {}, std::nullopt, detail::symbol_table_op::clear}); + } + /** Equivalent to `insert_for_next_parse(str, std::move(x))`. */ symbols & operator()(std::string_view str, T x) { @@ -5655,7 +5822,15 @@ namespace boost { namespace parser { { this->parser_.erase(context, str); } - }; + + /** Erases all the entries from the copy of the symbol table inside + the parse context `context`. */ + template + void clear(Context const & context) const + { + this->parser_.clear(context); + } + }; #ifndef BOOST_PARSER_DOXYGEN diff --git a/include/boost/parser/parser_fwd.hpp b/include/boost/parser/parser_fwd.hpp index 8eb4909b..871d9dae 100644 --- a/include/boost/parser/parser_fwd.hpp +++ b/include/boost/parser/parser_fwd.hpp @@ -80,8 +80,15 @@ namespace boost { namespace parser { in_apply_parser = 1 << 3 }; + struct symbol_table_trie_element + { + std::any trie_; + bool has_case_folded_; + bool pending_operations_; + }; + using symbol_table_tries_t = - std::map, std::less>; + std::map>; template< bool DoTrace, diff --git a/test/parser_symbol_table.cpp b/test/parser_symbol_table.cpp index dcae517b..3a3529e4 100644 --- a/test/parser_symbol_table.cpp +++ b/test/parser_symbol_table.cpp @@ -12,7 +12,6 @@ using namespace boost::parser; int main() { - // symbols_empty { symbols roman_numerals; @@ -164,5 +163,176 @@ int main() } } +// insert/erase/clear +{ + symbols roman_numerals; + roman_numerals.insert_for_next_parse("I", 1)("V", 5)("X", 10); + + auto const insert_numeral = [&roman_numerals](auto & context) { + using namespace boost::parser::literals; + char chars[2] = {get(_attr(context), 0_c), 0}; + roman_numerals.insert(context, chars, get(_attr(context), 1_c)); + }; + auto const erase_numeral = [&roman_numerals](auto & context) { + using namespace boost::parser::literals; + char chars[2] = {_attr(context), 0}; + roman_numerals.erase(context, chars); + }; + auto const clear_numerals = [&roman_numerals](auto & context) { + roman_numerals.clear(context); + }; + + auto const add_parser = ("add" >> char_ >> int_)[insert_numeral]; + auto const delete_parser = ("del" >> char_)[erase_numeral]; + auto const clear_parser = lit("clear")[clear_numerals]; + + auto const next_insert_numeral = [&roman_numerals](auto & context) { + using namespace boost::parser::literals; + char chars[2] = {get(_attr(context), 0_c), 0}; + roman_numerals.insert_for_next_parse(chars, get(_attr(context), 1_c)); + }; + auto const next_erase_numeral = [&roman_numerals](auto & context) { + using namespace boost::parser::literals; + char chars[2] = {_attr(context), 0}; + roman_numerals.erase_for_next_parse(chars); + }; + auto const next_clear_numerals = [&roman_numerals](auto &) { + roman_numerals.clear_for_next_parse(); + }; + + auto const next_add_parser = + ("next-add" >> char_ >> int_)[next_insert_numeral]; + auto const next_delete_parser = ("next-del" >> char_)[next_erase_numeral]; + auto const next_clear_parser = lit("next-clear")[next_clear_numerals]; + + { + // add only for this parse + auto result = parse("addL50L", add_parser >> roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 50); + + result = parse("L", roman_numerals); + BOOST_TEST(!result); + } + { + // add only for the next parse + auto result = parse("next-addL50L", next_add_parser >> roman_numerals); + BOOST_TEST(!result); // TODO + + result = parse("L", roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 50); + } + { + // add for both parses + auto result = parse("addL50next-addL50L", + add_parser >> next_add_parser >> roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 50); + + result = parse("L", roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 50); + } + { + // add for both parses + auto result = parse("next-addL50addL50L", + next_add_parser >> add_parser >> roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 50); + + result = parse("L", roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 50); + } + + { + // delete nonexistent entry + auto result = parse("delQ", delete_parser); + BOOST_TEST(result); + } + { + // delete nonexistent entry + auto result = parse("next-delQ", next_delete_parser); + BOOST_TEST(result); + } + + { + // delete only for this parse + BOOST_TEST(*parse("V", roman_numerals) == 5); + + auto result = parse("delVV", delete_parser >> roman_numerals); + BOOST_TEST(!result); + + result = parse("V", roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == 5); + } + { + // delete only for the next parse + BOOST_TEST(*parse("V", roman_numerals) == 5); + + auto result = parse("next-delVV", next_delete_parser >> roman_numerals); + BOOST_TEST(result); // TODO + BOOST_TEST(*result == 5); // TODO + + result = parse("V", roman_numerals); + BOOST_TEST(!result); + } + { + // delete for both parses + BOOST_TEST(*parse("X", roman_numerals) == 10); + + auto result = parse( + "delXnext-delXX", + delete_parser >> next_delete_parser >> roman_numerals); + BOOST_TEST(!result); + + result = parse("X", roman_numerals); + BOOST_TEST(!result); + } + { + // add and then delete only for this parse + auto result = parse( + "addC100CdelCC", + add_parser >> roman_numerals >> delete_parser >> roman_numerals); + BOOST_TEST(!result); + } + { + // add for this parse and then delete only for the next parse + auto result = parse( + "addC100Cnext-delCC", + add_parser >> roman_numerals >> next_delete_parser >> + roman_numerals); + BOOST_TEST(result); + BOOST_TEST(*result == std::tuple(100, 100)); + } + + { + // clear only for this parse + BOOST_TEST(*parse("I", roman_numerals) == 1); + BOOST_TEST(*parse("L", roman_numerals) == 50); + + BOOST_TEST(!parse("clearI", clear_parser >> roman_numerals)); + BOOST_TEST(!parse("clearL", clear_parser >> roman_numerals)); + + BOOST_TEST(*parse("I", roman_numerals) == 1); + BOOST_TEST(*parse("L", roman_numerals) == 50); + } + + { + // clear only for the next parse + BOOST_TEST(*parse("I", roman_numerals) == 1); + BOOST_TEST(*parse("L", roman_numerals) == 50); + + auto result = parse("next-clearI", next_clear_parser >> roman_numerals); + BOOST_TEST(result); // TODO + BOOST_TEST(*result == 1); // TODO + + BOOST_TEST(!parse("I", roman_numerals)); + BOOST_TEST(!parse("L", roman_numerals)); + } +} + return boost::report_errors(); }