blob: a11c72d84555df6f47b1331d5637b3c061f93415 [file] [log] [blame] [edit]
// 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.
#include "lib/fit/function.h"
#include "src/developer/shell/parser/parse_result.h"
#ifndef SRC_DEVELOPER_SHELL_PARSER_TEXT_MATCH_H_
#define SRC_DEVELOPER_SHELL_PARSER_TEXT_MATCH_H_
namespace shell::parser {
namespace internal {
// Produces a parse result for a fixed token or character. This is NOT a parser or combinator; it
// must be told via its arguments whether the initial parse succeeds. The ident argument should
// provide a rendering of what is being matched suitable for error messages. The token matched must
// always be of the given size. The find callback will be used to look ahead for error correction.
// Given a starting offset into the prefix tail, it should return the next location at which a match
// is possible.
template <typename T = ast::Terminal>
ParseResultStream FixedTextResult(ParseResult prefix, const std::string& ident, bool success,
size_t size, fit::function<size_t()> find) {
if (success) {
return ParseResultStream(prefix.Advance<T>(size));
}
return ParseResultStream(false, [prefix, size, ident, find = std::move(find), done = false,
next = std::optional<ParseResult>()]() mutable {
if (done) {
return prefix.End();
}
if (next) {
done = true;
return std::move(*next);
}
size_t pos = find();
ParseResult skip = pos == std::string::npos
? prefix.Skip(prefix.tail().size()).Expected(size, ident)
: prefix.Skip(pos).Advance(size);
ParseResult inject = prefix.Expected(size, ident);
if (skip.error_score() < inject.error_score()) {
next = std::move(inject);
return skip;
}
next = std::move(skip);
return inject;
});
}
} // namespace internal
// Produce a parser which parses any single character from the given list.
fit::function<ParseResultStream(ParseResultStream)> AnyChar(const std::string& name,
std::string_view chars);
// Produce a parser which parses any single character not in the given list.
fit::function<ParseResultStream(ParseResultStream)> AnyCharBut(const std::string& name,
std::string_view chars);
// Produce a parser which parses any single character.
ParseResultStream AnyChar(ParseResultStream prefixes);
// Similar to AnyChar but the input string is a regex style range group like "a-zA-Z0-9".
fit::function<ParseResultStream(ParseResultStream)> CharGroup(const std::string& name,
std::string_view chars);
// Produce a parser to parse a fixed text string.
template <typename T = ast::Terminal>
fit::function<ParseResultStream(ParseResultStream)> Token(const std::string& token) {
return [token](ParseResultStream prefixes) {
return std::move(prefixes).Follow([token](ParseResult prefix) {
return internal::FixedTextResult<T>(
std::move(prefix), "'" + token + "'", prefix.tail().substr(0, token.size()) == token,
token.size(), [tail = prefix.tail(), token]() { return tail.find(token); });
});
};
}
template <typename T = ast::Terminal>
fit::function<ParseResultStream(ParseResultStream)> Token(
fit::function<ParseResultStream(ParseResultStream)> parser) {
return [parser = std::move(parser)](ParseResultStream prefix) {
return parser(std::move(prefix).Mark()).Reduce<ast::TokenResult>().Map([](ParseResult result) {
// The goal here is to return a single terminal representing the parsed region of the result,
// but terminals don't have children, and thus can't have errors. As such, if we have error
// children, we return an unnamed non-terminal, where we combine all the regular parse results
// into single tokens, but leave the error tokens in place. So if we parsed ('foo' 'bar'
// 'baz'), we'd finish with just 'foobarbaz' on the stack, but if we parsed ('foo' 'bar'
// E[Expected 'baz']) we'd end with ('foobar' E[Expected 'baz']).
std::optional<size_t> start = std::nullopt;
size_t end;
std::vector<std::shared_ptr<ast::Node>> children;
for (const auto& child : result.node()->Children()) {
if (child->IsError()) {
if (start) {
children.push_back(
std::make_shared<T>(*start, child->start() - *start,
result.unit().substr(*start, child->start() - *start)));
}
children.push_back(child);
start = std::nullopt;
} else {
if (!start) {
start = child->start();
}
end = child->start() + child->Size();
}
}
if (start) {
size_t size = end - *start;
children.push_back(std::make_shared<T>(*start, size, result.unit().substr(*start, size)));
}
std::shared_ptr<ast::Node> new_child;
if (children.size() == 1) {
new_child = children.front();
} else {
new_child = std::make_shared<ast::TokenResult>(result.node()->start(), std::move(children));
}
return result.SetNode(new_child);
});
};
}
} // namespace shell::parser
#endif // SRC_DEVELOPER_SHELL_PARSER_TEXT_MATCH_H_