| // Copyright 2020 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef SRC_DEVELOPER_SHELL_PARSER_PARSE_RESULT_H_ | |
| #define SRC_DEVELOPER_SHELL_PARSER_PARSE_RESULT_H_ | |
| #include <set> | |
| #include <string_view> | |
| #include "lib/fit/function.h" | |
| #include "src/developer/shell/parser/ast.h" | |
| namespace shell::parser { | |
| // The result of parsing. | |
| class ParseResult { | |
| // Frame in a stack of parsed nodes. | |
| struct Frame { | |
| std::shared_ptr<ast::Node> node; | |
| std::shared_ptr<Frame> prev; | |
| bool is_marker_frame() const { return !node; } | |
| bool is_stack_bottom() const { return !prev; } | |
| }; | |
| public: | |
| ParseResult(const ParseResult &) = default; | |
| explicit ParseResult(std::string_view text) : ParseResult(text, 0, 0, 0, nullptr, nullptr) {} | |
| bool is_end() const { return frame_ == nullptr; } | |
| size_t offset() const { return offset_; } | |
| size_t errors() const { return errors_; } | |
| size_t parsed_successfully() const { return parsed_successfully_; } | |
| std::string_view unit() const { return unit_; } | |
| std::string_view tail() const { return unit_.substr(offset_); } | |
| std::shared_ptr<ast::Node> node() const { return frame_ ? frame_->node : nullptr; } | |
| // Move parsing ahead by len bytes, and push a token of that length. | |
| template <typename T = ast::Terminal> | |
| ParseResult Advance(size_t size) { | |
| if (is_end()) { | |
| return kEnd; | |
| } | |
| return ParseResult(unit_, offset_ + size, parsed_successfully_ + size, errors_, | |
| std::make_shared<T>(offset_, size, tail().substr(0, size)), frame_); | |
| } | |
| // Swap the node in the current frame. Essentially this is a Pop() followed by a Push(). Usually | |
| // the new node will be some modification of the current node, but instead of popping, modifying, | |
| // and pushing, it's usually more efficient to peek the current node with node(), create the | |
| // modified node, then do all the stack alteration in one operation. | |
| // | |
| // An example use case would be the parser-modifying version of Token(), which parses a new | |
| // non-terminal on the stack, then concatenates its children to create a new terminal instead. | |
| ParseResult SetNode(std::shared_ptr<ast::Node> node) { | |
| if (is_end()) { | |
| return kEnd; | |
| } | |
| return ParseResult(unit_, offset_, parsed_successfully_, errors_, node, frame_->prev); | |
| } | |
| // Insert an error indicating some form was expected. The parse position does not change. | |
| ParseResult Expected(std::string_view message) { | |
| if (is_end()) { | |
| return kEnd; | |
| } | |
| return ParseResult(unit_, offset_, parsed_successfully_, errors_ + 1, | |
| std::make_unique<ast::Error>(offset_, 0, message), frame_); | |
| } | |
| // Skip the given number of bytes and push an error token indicating they were skipped. | |
| ParseResult Skip(size_t size, std::string_view message) { | |
| if (is_end()) { | |
| return kEnd; | |
| } | |
| return ParseResult(unit_, offset_ + size, parsed_successfully_, errors_ + 1, | |
| std::make_unique<ast::Error>(offset_, size, message), frame_); | |
| } | |
| // Push a marker frame onto the stack. The next Reduce() call will reduce up to here. | |
| ParseResult Mark() { | |
| if (is_end()) { | |
| return kEnd; | |
| } | |
| return ParseResult(unit_, offset_, parsed_successfully_, errors_, nullptr, frame_); | |
| } | |
| // Pops from the stack until a marker frame or the top of the stack is encountered, then produces | |
| // a single nonterminal from the results and pushes that. | |
| // | |
| // This is how all nonterminals are created: | |
| // 1) A marker frame is pushed onto the stack. | |
| // 2) Assorted parsers are run, pushing the children of the node onto the stack as they go. | |
| // 3) Reduce() is called and turns the nodes between the marker and the stack top into a new | |
| // nonterminal. | |
| // | |
| // If pop_marker is false, we will not remove the marker frame when we pop. This is useful for | |
| // building multiple non-terminals from the same reduction point, as the LAssoc combinator will. | |
| template <typename T> | |
| ParseResult Reduce(bool pop_marker = true); | |
| // Remove the marker frame nearest the top of the stack without disturbing the rest of the stack. | |
| // This is useful if we call reduce with pop_marker = false. | |
| ParseResult DropMarker() { | |
| if (auto frame = DropMarkerFrame()) { | |
| ParseResult ret(unit_, offset_, parsed_successfully_, errors_); | |
| ret.frame_ = frame; | |
| return ret; | |
| } | |
| return kEnd; | |
| } | |
| explicit operator bool() const { return !is_end(); } | |
| // A null parse result indicating no further error alternatives. | |
| static const ParseResult kEnd; | |
| private: | |
| ParseResult(std::string_view unit, size_t offset, size_t parsed_successfully, size_t errors) | |
| : offset_(offset), | |
| parsed_successfully_(parsed_successfully), | |
| errors_(errors), | |
| unit_(unit), | |
| frame_(nullptr) {} | |
| ParseResult(std::string_view unit, size_t offset, size_t parsed_successfully, size_t errors, | |
| std::shared_ptr<ast::Node> node, std::shared_ptr<Frame> prev) | |
| : ParseResult(unit, offset, parsed_successfully, errors) { | |
| frame_ = std::make_shared<Frame>(Frame{.node = std::move(node), .prev = std::move(prev)}); | |
| } | |
| // Implement the core of DropMarker by actually walking the stack and deleting the marker frame. | |
| std::shared_ptr<Frame> DropMarkerFrame(std::shared_ptr<Frame> frame = nullptr) { | |
| if (!frame) { | |
| frame = frame_; | |
| } | |
| if (!frame) { | |
| return nullptr; | |
| } else if (frame->is_stack_bottom()) { | |
| return frame; | |
| } else if (frame->is_marker_frame()) { | |
| return frame->prev; | |
| } else { | |
| return std::make_shared<Frame>( | |
| Frame{.node = frame->node, .prev = DropMarkerFrame(frame->prev)}); | |
| } | |
| } | |
| // Position from the beginning of the parsed text. | |
| size_t offset_; | |
| // How many characters we've advanced past, not including characters skipped due to error. | |
| size_t parsed_successfully_; | |
| // Number of errors we've encountered. | |
| size_t errors_; | |
| // Text being parsed. | |
| std::string_view unit_; | |
| // Last node that was parsed at this position. | |
| std::shared_ptr<Frame> frame_; | |
| }; | |
| template <typename T> | |
| ParseResult ParseResult::Reduce(bool pop_marker) { | |
| std::vector<std::shared_ptr<ast::Node>> children; | |
| std::shared_ptr<Frame> cur; | |
| if (!frame_) { | |
| return kEnd; | |
| } | |
| // We have a dummy marker frame at the bottom of the stack, so we'll always hit the end conditon | |
| // here. | |
| for (cur = frame_; !cur->is_marker_frame(); cur = cur->prev) { | |
| if (!cur->node->IsWhitespace()) { | |
| children.push_back(cur->node); | |
| } | |
| } | |
| // If we didn't arrive at the beginning of the stack, also pop the marker (unless we were told not | |
| // to by the caller). | |
| if (pop_marker && !cur->is_stack_bottom()) { | |
| cur = cur->prev; | |
| } | |
| std::reverse(children.begin(), children.end()); | |
| auto start = offset_; | |
| if (!children.empty()) { | |
| start = children.front()->start(); | |
| } | |
| auto node = std::make_shared<T>(start, std::move(children)); | |
| return ParseResult(unit_, offset_, parsed_successfully_, errors_, node, cur); | |
| } | |
| } // namespace shell::parser | |
| #endif // SRC_DEVELOPER_SHELL_PARSER_PARSE_RESULT_H_ |