thrift/compiler/parse/parsing_driver.cc (710 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <thrift/compiler/parse/parsing_driver.h>
#include <errno.h>
#include <stdlib.h>
#include <cmath>
#include <cstdarg>
#include <limits>
#include <memory>
#include <boost/filesystem.hpp>
#include <thrift/compiler/parse/lexer.h>
namespace apache {
namespace thrift {
namespace compiler {
namespace {
class parsing_terminator : public std::runtime_error {
public:
parsing_terminator()
: std::runtime_error(
"Internal exception used to terminate the parsing process.") {}
};
} // namespace
class parsing_driver::lex_handler_impl : public lex_handler {
private:
parsing_driver& driver_;
public:
explicit lex_handler_impl(parsing_driver& d) : driver_(d) {}
// Consume doctext and store it in `driver_.doctext`.
//
// It is non-trivial for a yacc-style LR(1) parser to accept doctext
// as an optional prefix before either a definition or standalone at
// the header. Hence this method of "pushing" it into the driver and
// "pop-ing" it on the node as needed.
void on_doc_comment(const char* text, int lineno) override {
driver_.clear_doctext();
driver_.doctext = driver_.strip_doctext(text);
driver_.doctext_lineno = lineno;
}
};
parsing_driver::parsing_driver(
diagnostic_context& ctx, std::string path, parsing_params parse_params)
: lex_handler_(std::make_unique<lex_handler_impl>(*this)),
lexer_(std::make_unique<lexer>(
lexer::from_string(*lex_handler_, ctx, path, {}))),
params(std::move(parse_params)),
doctext(boost::none),
doctext_lineno(0),
mode(parsing_mode::INCLUDES),
ctx_(ctx) {
auto root_program = std::make_unique<t_program>(path);
ctx_.start_program(program = root_program.get());
program_bundle = std::make_unique<t_program_bundle>(std::move(root_program));
scope_cache = program->scope();
}
/**
* The default destructor needs to be explicitly defined in the .cc file since
* it invokes the destructor of parse_ (of type unique_ptr<yy::parser>). It
* cannot go in the header file since yy::parser is only forward-declared there.
*/
parsing_driver::~parsing_driver() = default;
int parsing_driver::get_lineno() const {
return lexer_->lineno();
}
std::string parsing_driver::get_text() const {
return lexer_->token_text();
}
std::unique_ptr<t_program_bundle> parsing_driver::parse() {
parser_ = std::make_unique<yy::parser>(*this, &yylval_, &yylloc_);
std::unique_ptr<t_program_bundle> result;
try {
parse_file();
result = std::move(program_bundle);
} catch (const parsing_terminator&) {
// No need to do anything here. The purpose of the exception is simply to
// end the parsing process by unwinding to here.
}
return result;
}
void parsing_driver::parse_file() {
// Get scope file path
const std::string& path = program->path();
// Skip on already parsed files
if (already_parsed_paths_.count(path)) {
return;
} else {
already_parsed_paths_.insert(path);
}
try {
lexer_ =
std::make_unique<lexer>(lexer::from_file(*lex_handler_, ctx_, path));
reset_locations();
} catch (std::runtime_error const& ex) {
failure(ex.what());
}
// Create new scope and scan for includes
verbose([&](auto& o) { o << "Scanning " << path << " for includes\n"; });
mode = parsing_mode::INCLUDES;
try {
if (parser_->parse() != 0) {
failure("Parser error during include pass.");
}
} catch (const std::string& x) {
failure(x);
}
// Recursively parse all the include programs
const auto& includes = program->get_included_programs();
// Always enable allow_neg_field_keys when parsing included files.
// This way if a thrift file has negative keys, --allow-neg-keys doesn't have
// to be used by everyone that includes it.
auto old_params = params;
auto old_program = program;
for (auto included_program : includes) {
circular_deps_.insert(path);
// Fail on circular dependencies
if (circular_deps_.count(included_program->path())) {
failure([&](auto& o) {
o << "Circular dependency found: file `" << included_program->path()
<< "` is already parsed.";
});
}
// This must be after the previous circular include check, since the emitted
// error message above is supposed to reference the parent file name.
params.allow_neg_field_keys = true;
ctx_.start_program(program = included_program);
parse_file();
ctx_.end_program(program);
size_t num_removed = circular_deps_.erase(path);
(void)num_removed;
assert(num_removed == 1);
}
params = old_params;
program = old_program;
// Parse the program file
try {
lexer_ =
std::make_unique<lexer>(lexer::from_file(*lex_handler_, ctx_, path));
reset_locations();
} catch (std::runtime_error const& ex) {
failure(ex.what());
}
// Compute locations in the program mode.
boost::string_view source = lexer_->source();
for (size_t i = 0; i < source.size(); ++i) {
if (source[i] == '\n') {
program->add_line_offset(i + 1);
}
}
mode = parsing_mode::PROGRAM;
verbose([&](auto& o) { o << "Parsing " << path << " for types\n"; });
try {
if (parser_->parse() != 0) {
failure("Parser error during types pass.");
}
} catch (const std::string& x) {
failure(x);
}
for (auto td : program->placeholder_typedefs()) {
if (!td->resolve()) {
failure(
[&](auto& o) { o << "Type `" << td->name() << "` not defined."; });
}
}
}
[[noreturn]] void parsing_driver::end_parsing() {
throw parsing_terminator{};
}
// TODO: This doesn't really need to be a member function. Move it somewhere
// else (e.g. `util.{h|cc}`) once everything gets consolidated into `parse/`.
std::string parsing_driver::directory_name(const std::string& filename) {
boost::filesystem::path fullpath = filename;
auto parent_path = fullpath.parent_path();
auto result = parent_path.string();
// No parent dir, just use the current directory
if (result.empty()) {
return ".";
}
return result;
}
std::string parsing_driver::find_include_file(const std::string& filename) {
// Absolute path? Just try that
boost::filesystem::path path{filename};
if (path.has_root_directory()) {
try {
return boost::filesystem::canonical(path).string();
} catch (const boost::filesystem::filesystem_error& e) {
failure([&](auto& o) {
o << "Could not find file: " << filename << ". Error: " << e.what();
});
}
}
// relative path, start searching
// new search path with current dir global
std::vector<std::string> sp = params.incl_searchpath;
sp.insert(sp.begin(), directory_name(program->path()));
// iterate through paths
std::vector<std::string>::iterator it;
for (it = sp.begin(); it != sp.end(); it++) {
boost::filesystem::path sfilename = filename;
if ((*it) != "." && (*it) != "") {
sfilename = boost::filesystem::path(*(it)) / filename;
}
if (boost::filesystem::exists(sfilename)) {
return sfilename.string();
} else {
debug([&](auto& o) { o << "Could not find: " << filename << "."; });
}
}
// File was not found
failure([&](auto& o) { o << "Could not find include file " << filename; });
}
void parsing_driver::validate_not_ambiguous_enum(const std::string& name) {
if (scope_cache->is_ambiguous_enum_value(name)) {
std::string possible_enums =
scope_cache->get_fully_qualified_enum_value_names(name).c_str();
std::string msg = "The ambiguous enum `" + name +
"` is defined in more than one place. " +
"Please refer to this enum using ENUM_NAME.ENUM_VALUE.";
if (!possible_enums.empty()) {
msg += (" Possible options: " + possible_enums);
}
warning(msg);
}
}
void parsing_driver::clear_doctext() {
if (doctext && mode == parsing_mode::PROGRAM) {
warning_strict([&](auto& o) {
o << "Uncaptured doctext at on line " << doctext_lineno << ".";
});
}
doctext = boost::none;
}
t_doc parsing_driver::pop_doctext() {
if (mode != parsing_mode::PROGRAM) {
return boost::none;
}
return std::exchange(doctext, boost::none);
}
t_doc parsing_driver::clean_up_doctext(std::string docstring) {
// Convert to C++ string, and remove Windows's carriage returns.
docstring.erase(
remove(docstring.begin(), docstring.end(), '\r'), docstring.end());
// Separate into lines.
std::vector<std::string> lines;
std::string::size_type pos = std::string::npos;
std::string::size_type last;
while (true) {
last = (pos == std::string::npos) ? 0 : pos + 1;
pos = docstring.find('\n', last);
if (pos == std::string::npos) {
// First bit of cleaning. If the last line is only whitespace, drop it.
std::string::size_type nonwhite =
docstring.find_first_not_of(" \t", last);
if (nonwhite != std::string::npos) {
lines.push_back(docstring.substr(last));
}
break;
}
lines.push_back(docstring.substr(last, pos - last));
}
// A very profound docstring.
if (lines.empty()) {
return boost::none;
}
// Clear leading whitespace from the first line.
pos = lines.front().find_first_not_of(" \t");
lines.front().erase(0, pos);
// If every nonblank line after the first has the same number of spaces/tabs,
// then a comment prefix, remove them.
enum Prefix {
None = 0,
Star = 1, // length of '*'
Slashes = 3, // length of '///'
InlineSlash = 4, // length of '///<'
};
Prefix found_prefix = None;
std::string::size_type prefix_len = 0;
std::vector<std::string>::iterator l_iter;
// Start searching for prefixes from second line, since lexer already removed
// initial prefix/suffix.
for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) {
if (l_iter->empty()) {
continue;
}
pos = l_iter->find_first_not_of(" \t");
if (found_prefix == None) {
if (pos != std::string::npos) {
if (l_iter->at(pos) == '*') {
found_prefix = Star;
prefix_len = pos;
} else if (l_iter->compare(pos, 4, "///<") == 0) {
found_prefix = InlineSlash;
prefix_len = pos;
} else if (l_iter->compare(pos, 3, "///") == 0) {
found_prefix = Slashes;
prefix_len = pos;
} else {
found_prefix = None;
break;
}
} else {
// Whitespace-only line. Truncate it.
l_iter->clear();
}
} else if (
l_iter->size() > pos && pos == prefix_len &&
((found_prefix == Star && l_iter->at(pos) == '*') ||
(found_prefix == InlineSlash &&
l_iter->compare(pos, 4, "///<") == 0) ||
(found_prefix == Slashes && l_iter->compare(pos, 3, "///") == 0))) {
// Business as usual
} else if (pos == std::string::npos) {
// Whitespace-only line. Let's truncate it for them.
l_iter->clear();
} else {
// The pattern has been broken.
found_prefix = None;
break;
}
}
// If our prefix survived, delete it from every line.
if (found_prefix != None) {
// Get the prefix too.
prefix_len += found_prefix;
for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) {
l_iter->erase(0, prefix_len);
}
}
// Now delete the minimum amount of leading whitespace from each line.
prefix_len = std::string::npos;
for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) {
if (l_iter->empty()) {
continue;
}
pos = l_iter->find_first_not_of(" \t");
if (pos != std::string::npos &&
(prefix_len == std::string::npos || pos < prefix_len)) {
prefix_len = pos;
}
}
// If our whitespace prefix survived, delete it from every line.
if (prefix_len != std::string::npos) {
for (l_iter = lines.begin() + 1; l_iter != lines.end(); ++l_iter) {
l_iter->erase(0, prefix_len);
}
}
// Remove trailing whitespace from every line.
for (l_iter = lines.begin(); l_iter != lines.end(); ++l_iter) {
pos = l_iter->find_last_not_of(" \t");
if (pos != std::string::npos && pos != l_iter->length() - 1) {
l_iter->erase(pos + 1);
}
}
// If the first line is empty, remove it.
// Don't do this earlier because a lot of steps skip the first line.
if (lines.front().empty()) {
lines.erase(lines.begin());
}
// Now rejoin the lines and copy them back into doctext.
docstring.clear();
for (l_iter = lines.begin(); l_iter != lines.end(); ++l_iter) {
docstring += *l_iter;
docstring += '\n';
}
return docstring;
}
bool parsing_driver::require_experimental_feature(const char* feature) {
assert(feature != std::string("all"));
if (params.allow_experimental_features.count("all") ||
params.allow_experimental_features.count(feature)) {
warning_strict([&](auto& o) {
o << "'" << feature << "' is an experimental feature.";
});
return true;
}
failure(
[&](auto& o) { o << "'" << feature << "' is an experimental feature."; });
return false;
}
void parsing_driver::set_annotations(
t_node* node, std::unique_ptr<t_annotations> annotations) {
if (annotations != nullptr) {
node->reset_annotations(annotations->strings);
}
}
void parsing_driver::set_attributes(
t_named& node,
std::unique_ptr<t_def_attrs> attrs,
std::unique_ptr<t_annotations> annots,
const YYLTYPE& loc) const {
node.set_src_range(get_source_range(loc));
if (attrs != nullptr) {
if (attrs->doc) {
node.set_doc(std::move(*attrs->doc));
}
if (attrs->struct_annotations != nullptr) {
for (auto& an : *attrs->struct_annotations) {
node.add_structured_annotation(std::move(an));
}
}
}
set_annotations(&node, std::move(annots));
}
void parsing_driver::set_doctext(t_node& node, t_doc doctext) const {
if (node.has_doc() && doctext) {
/* concatenating prefix doctext + inline doctext with a newline
* However, this syntax should be strongly discouraged */
std::string new_doc = node.doc() + "\n" + std::move(*doctext);
node.set_doc(std::move(new_doc));
} else if (doctext) {
node.set_doc(std::move(*doctext));
}
}
source_range parsing_driver::get_source_range(const YYLTYPE& loc) const {
return source_range(
*program, loc.begin.line, loc.begin.column, loc.end.line, loc.end.column);
}
void parsing_driver::reset_locations() {
yylloc_.begin.line = 1;
yylloc_.begin.column = 1;
yylloc_.end.line = 1;
yylloc_.end.column = 1;
yylval_ = 0;
}
std::unique_ptr<t_const> parsing_driver::new_struct_annotation(
std::unique_ptr<t_const_value> const_struct) {
auto ttype = const_struct->ttype().value(); // Copy the t_type_ref.
auto result = std::make_unique<t_const>(
program, std::move(ttype), "", std::move(const_struct));
result->set_lineno(lexer_->lineno());
return result;
}
std::unique_ptr<t_throws> parsing_driver::new_throws(
std::unique_ptr<t_field_list> exceptions) {
assert(exceptions != nullptr);
auto result = std::make_unique<t_throws>();
set_fields(*result, std::move(*exceptions));
return result;
}
void parsing_driver::set_fields(t_structured& tstruct, t_field_list&& fields) {
if (mode != parsing_mode::PROGRAM) {
return;
}
assert(tstruct.fields().empty());
if (mode != parsing_mode::PROGRAM) {
return;
}
t_field_id next_id = -1;
for (auto& field : fields) {
maybe_allocate_field_id(next_id, *field);
if (!tstruct.try_append_field(std::move(field))) {
// Since we process root_program twice, we need to check parsing mode to
// avoid double reporting
if (mode == parsing_mode::PROGRAM ||
program != program_bundle->root_program()) {
ctx_.failure(*field, [&](auto& o) {
o << "Field identifier " << field->get_key() << " for \""
<< field->get_name() << "\" has already been used.";
});
}
}
}
}
t_type_ref parsing_driver::new_type_ref(
const t_type& type, std::unique_ptr<t_annotations> annotations) {
if (annotations == nullptr) {
return type;
}
// Make a copy of the node to hold the annotations.
// TODO(afuller): Remove the need for copying the underlying type by making
// t_type_ref annotatable directly.
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(&type)) {
// base types can be copy constructed.
auto node = std::make_unique<t_base_type>(*tbase_type);
set_annotations(node.get(), std::move(annotations));
t_type_ref result(*node);
if (mode == parsing_mode::INCLUDES) {
delete_at_the_end(node.release());
} else {
program->add_unnamed_type(std::move(node));
}
return result;
}
// Containers always use a new type, so should never show up here.
assert(!type.is_container());
// For all other types, we can just create a dummy typedef node with
// the same name.
// NOTE(afuller): This is not a safe assumption as it breaks all
// dynamic casts and t_type::is_* calls.
return *add_unnamed_typedef(
std::make_unique<t_typedef>(
const_cast<t_program*>(type.program()), type.get_name(), type),
std::move(annotations));
}
t_type_ref parsing_driver::new_type_ref(
std::unique_ptr<t_templated_type> node,
std::unique_ptr<t_annotations> annotations) {
assert(node != nullptr);
const t_type* type = node.get();
set_annotations(node.get(), std::move(annotations));
if (mode == parsing_mode::INCLUDES) {
delete_at_the_end(node.release());
} else {
program->add_type_instantiation(std::move(node));
}
return *type;
}
t_type_ref parsing_driver::new_type_ref(
std::string name,
std::unique_ptr<t_annotations> annotations,
bool is_const) {
if (mode == parsing_mode::INCLUDES) {
// Ignore identifier-based type references in include mode
return {};
}
// Try to resolve the type.
const t_type* type = scope_cache->find_type(name);
if (type == nullptr) {
type = scope_cache->find_type(program->name() + "." + name);
}
if (type == nullptr) {
// TODO(afuller): Remove this special case for const, which requires a
// specific declaration order.
if (is_const) {
failure([&](auto& o) {
o << "The type '" << name << "' is not defined yet. Types must be "
<< "defined before the usage in constant values.";
});
}
// TODO(afuller): Why are interactions special? They should just be another
// declared type.
type = scope_cache->find_interaction(name);
if (type == nullptr) {
type = scope_cache->find_interaction(program->name() + "." + name);
}
}
if (type != nullptr) {
// We found the type!
return new_type_ref(*type, std::move(annotations));
}
/*
Either this type isn't yet declared, or it's never
declared. Either way allow it and we'll figure it out
during generation.
*/
// NOTE(afuller): This assumes that, since the type was referenced by name, it
// is safe to create a dummy typedef to use as a proxy for the original type.
// However, this actually breaks dynamic casts and t_type::is_* calls.
// TODO(afuller): Resolve *all* types in a second pass.
return t_type_ref(*add_placeholder_typedef(
std::make_unique<t_placeholder_typedef>(
program, std::move(name), scope_cache),
std::move(annotations)));
}
void parsing_driver::set_functions(
t_interface& node, std::unique_ptr<t_function_list> functions) {
if (functions != nullptr) {
node.set_functions(std::move(*functions));
}
}
t_ref<t_named> parsing_driver::add_def(std::unique_ptr<t_named> node) {
t_ref<t_named> result(node.get());
if (should_add_node(node)) {
// Add to scope.
// TODO(afuller): Move program level scope management to t_program.
if (auto* tnode = dynamic_cast<t_interaction*>(node.get())) {
scope_cache->add_interaction(scoped_name(*node), tnode);
} else if (auto* tnode = dynamic_cast<t_service*>(node.get())) {
scope_cache->add_service(scoped_name(*node), tnode);
} else if (auto* tnode = dynamic_cast<t_const*>(node.get())) {
scope_cache->add_constant(scoped_name(*node), tnode);
} else if (auto* tnode = dynamic_cast<t_enum*>(node.get())) {
scope_cache->add_type(scoped_name(*node), tnode);
// Register enum value names in scope.
for (const auto& value : tnode->consts()) {
// TODO(afuller): Remove ability to access unscoped enum values.
scope_cache->add_constant(scoped_name(value), &value);
scope_cache->add_constant(scoped_name(*node, value), &value);
}
} else if (auto* tnode = dynamic_cast<t_type*>(node.get())) {
scope_cache->add_type(scoped_name(*node), tnode);
} else {
throw std::logic_error("Unsupported declaration.");
}
// Add to program.
program->add_definition(std::move(node));
}
return result;
}
void parsing_driver::add_include(std::string name) {
if (mode != parsing_mode::INCLUDES) {
return;
}
std::string path = find_include_file(name);
assert(!path.empty()); // Should have throw an exception if not found.
if (program_cache.find(path) == program_cache.end()) {
auto included_program = program->add_include(path, name, lexer_->lineno());
program_cache[path] = included_program.get();
program_bundle->add_program(std::move(included_program));
} else {
auto include = std::make_unique<t_include>(program_cache[path]);
include->set_lineno(lexer_->lineno());
program->add_include(std::move(include));
}
}
void parsing_driver::set_package(std::string name) {
if (mode != parsing_mode::PROGRAM) {
return;
}
if (!program->package().empty()) {
failure("Package already specified.");
}
try {
program->set_package(t_package{name});
} catch (const std::invalid_argument& e) {
failure(e.what());
}
}
const t_type* parsing_driver::add_unnamed_typedef(
std::unique_ptr<t_typedef> node,
std::unique_ptr<t_annotations> annotations) {
const t_type* result(node.get());
set_annotations(node.get(), std::move(annotations));
program->add_unnamed_typedef(std::move(node));
return result;
}
const t_type* parsing_driver::add_placeholder_typedef(
std::unique_ptr<t_placeholder_typedef> node,
std::unique_ptr<t_annotations> annotations) {
const t_type* result(node.get());
set_annotations(node.get(), std::move(annotations));
program->add_placeholder_typedef(std::move(node));
return result;
}
void parsing_driver::allocate_field_id(t_field_id& next_id, t_field& field) {
if (params.strict >= 192) {
ctx_.failure(
field,
"Implicit field keys are deprecated and not allowed with -strict");
}
if (next_id < t_field::min_id) {
ctx_.failure(
field,
"Cannot allocate an id for `" + field.name() +
"`. Automatic field ids are exhausted.");
}
field.set_implicit_id(next_id--);
}
void parsing_driver::maybe_allocate_field_id(
t_field_id& next_id, t_field& field) {
if (!field.has_explicit_id()) {
// Auto assign an id.
allocate_field_id(next_id, field);
return;
}
// Check the explicitly provided id.
if (field.id() <= 0) {
// TODO(afuller): Move this validation to ast_validator.
if (params.allow_neg_field_keys) {
/*
* allow_neg_field_keys exists to allow users to add explicitly
* specified id values to old .thrift files without breaking
* protocol compatibility.
*/
if (field.id() != next_id) {
/*
* warn if the user-specified negative value isn't what
* thrift would have auto-assigned.
*/
ctx_.warning(field, [&](auto& o) {
o << "Nonpositive field id (" << field.id()
<< ") differs from what would "
<< "be auto-assigned by thrift (" << next_id << ").";
});
}
} else if (field.id() == next_id) {
ctx_.warning(field, [&](auto& o) {
o << "Nonpositive value (" << field.id()
<< ") not allowed as a field id.";
});
} else {
// TODO(afuller): Make ignoring the user provided value a failure.
ctx_.warning(field, [&](auto& o) {
o << "Nonpositive field id (" << field.id()
<< ") differs from what is auto-"
"assigned by thrift. The id must positive or "
<< next_id << ".";
});
// Ignore user provided value and auto assign an id.
allocate_field_id(next_id, field);
}
// Skip past any negative, manually assigned ids.
if (field.id() < 0) {
/*
* Update next field id to be one less than the value.
* The FieldList parsing will catch any duplicate id values.
*/
next_id = field.id() - 1;
}
}
}
std::unique_ptr<t_const_value> parsing_driver::to_const_value(
int64_t int_const) {
if (mode == parsing_mode::PROGRAM && !params.allow_64bit_consts &&
(int_const < INT32_MIN || int_const > INT32_MAX)) {
warning([&](auto& o) {
o << "64-bit constant \"" << int_const
<< "\" may not work in all languages.";
});
}
auto node = std::make_unique<t_const_value>();
node->set_integer(int_const);
return node;
}
int64_t parsing_driver::to_int(uint64_t val, bool negative) {
constexpr uint64_t i64max = std::numeric_limits<int64_t>::max();
if (negative) {
if (val > i64max + 1) { // Can store one more negative number.
failure(
[&](auto& o) { o << "This integer is too small: -" << val << "\n"; });
}
return -val;
}
if (val > i64max) {
failure([&](auto& o) { o << "This integer is too big: " << val << "\n"; });
}
return val;
}
t_doc parsing_driver::strip_doctext(const char* text) {
if (mode != apache::thrift::compiler::parsing_mode::PROGRAM) {
return boost::none;
}
std::string str{text};
if (str.compare(0, 3, "/**") == 0) {
str = str.substr(3, str.length() - 3 - 2);
} else if (str.compare(0, 3, "///") == 0) {
str = str.substr(3, str.length() - 3);
}
if ((str.size() >= 1) && str[0] == '<') {
str = str.substr(1, str.length() - 1);
}
return clean_up_doctext(str);
}
const t_service* parsing_driver::find_service(const std::string& name) {
if (mode != parsing_mode::PROGRAM) {
return nullptr;
}
if (auto* result = scope_cache->find_service(name)) {
return result;
}
if (auto* result = scope_cache->find_service(scoped_name(name))) {
return result;
}
failure([&](auto& o) {
o << "Service \"" << name << "\" has not been defined.";
});
}
const t_const* parsing_driver::find_const(const std::string& name) {
validate_not_ambiguous_enum(name);
if (const t_const* constant = scope_cache->find_constant(name)) {
return constant;
}
if (const t_const* constant = scope_cache->find_constant(scoped_name(name))) {
validate_not_ambiguous_enum(scoped_name(name));
return constant;
}
return nullptr;
}
std::unique_ptr<t_const_value> parsing_driver::copy_const_value(
const std::string& name) {
if (const t_const* constant = find_const(name)) {
// Copy const_value to perform isolated mutations
auto result = constant->get_value()->clone();
// We only want to clone the value, while discarding all real type
// information.
result->set_ttype(boost::none);
result->set_is_enum(false);
result->set_enum(nullptr);
result->set_enum_value(nullptr);
return result;
}
// TODO(afuller): Make this an error.
if (mode == parsing_mode::PROGRAM) {
warning([&](auto& o) {
o << "The identifier '" << name << "' is not defined yet. Constansts and "
<< "enums should be defined before using them as default values.";
});
}
return std::make_unique<t_const_value>(name);
}
void parsing_driver::set_parsed_definition() {
if (mode == parsing_mode::PROGRAM) {
programs_that_parsed_definition_.insert(program->path());
}
}
void parsing_driver::validate_header_location() {
if (programs_that_parsed_definition_.find(program->path()) !=
programs_that_parsed_definition_.end()) {
failure("Headers must be specified before definitions.");
}
}
void parsing_driver::validate_header_annotations(
std::unique_ptr<t_def_attrs> statement_attrs,
std::unique_ptr<t_annotations> annotations) {
// Ideally the failures below have to be handled by a grammar, but it's not
// expressive enough to avoid conflicts when doing so.
if (statement_attrs) {
bool has_annotations = statement_attrs->struct_annotations.get() != nullptr;
if (has_annotations) {
failure("Structured annotations are not supported for a given entity.");
}
}
if (annotations) {
failure("Annotations are not supported for a given entity.");
}
}
void parsing_driver::set_program_annotations(
std::unique_ptr<t_def_attrs> statement_attrs,
std::unique_ptr<t_annotations> annotations,
const YYLTYPE& loc) {
set_attributes(
*program, std::move(statement_attrs), std::move(annotations), loc);
}
} // namespace compiler
} // namespace thrift
} // namespace apache