| // Copyright 2024 The Bazel Authors. All rights reserved. | 
 | // | 
 | // 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 "tools/cpp/modules_tools/common/common.h" | 
 |  | 
 | #include <iostream> | 
 |  | 
 | #include "json.hpp" | 
 |  | 
 | void die(const std::string &msg) { | 
 |   std::cerr << msg << std::endl; | 
 |   std::exit(1); | 
 | } | 
 |  | 
 | void parse_provides(const JsonValue &provides_data, ModuleDep &dep) { | 
 |   if (provides_data.is_null()) { | 
 |     return; | 
 |   } | 
 |   if (!provides_data.is_array()) { | 
 |     die("require ddi content 'rules[0][\"provides\"]' is JSON array"); | 
 |   } | 
 |   // Only 1 provide in rule | 
 |   // In C++20 Modules, one TU provide only one module. | 
 |   // Fortran can provide more than one module per TU. | 
 |   // This check is fine for C++20 Modules. | 
 |   auto provides = provides_data.as_array(); | 
 |   if (provides.size() > 1) { | 
 |     die("require ddi content 'rules[0][\"provides\"]' has only 1 provide"); | 
 |   } | 
 |   if (provides.size() == 1) { | 
 |     auto provide_data = provides[0]; | 
 |     if (!provide_data.is_object()) { | 
 |       die("require ddi content 'rules[0][\"provides\"][0]' is JSON object"); | 
 |     } | 
 |     auto provide_obj = provide_data.as_object(); | 
 |     if (provide_obj.find("logical-name") == provide_obj.end()) { | 
 |       die("require 'logical-name' in 'rules[0][\"provides\"][0]'"); | 
 |     } | 
 |     auto name_data = provide_obj.at("logical-name"); | 
 |     if (!name_data.is_string()) { | 
 |       die("require ddi content 'rules[0][\"provides\"][0][\"logical-name\"]' " | 
 |           "is JSON string"); | 
 |     } | 
 |     dep.gen_bmi = true; | 
 |     dep.name = name_data.as_string(); | 
 |   } | 
 | } | 
 |  | 
 | void parse_requires(const JsonValue &requires_data, ModuleDep &dep) { | 
 |   if (requires_data.is_null()) { | 
 |     return; | 
 |   } | 
 |   if (!requires_data.is_array()) { | 
 |     die("require ddi content 'rules[0][\"requires\"]' is JSON array"); | 
 |   } | 
 |   for (const auto &item_data : requires_data.as_array()) { | 
 |     if (!item_data.is_object()) { | 
 |       die("require JSON object, but got " + item_data.dump()); | 
 |     } | 
 |     auto item_obj = item_data.as_object(); | 
 |     if (item_obj.find("logical-name") == item_obj.end()) { | 
 |       die("requrie 'logical-name' in 'rules[0][\"requires\"]' item"); | 
 |     } | 
 |     auto name_data = item_obj.at("logical-name"); | 
 |     if (!name_data.is_string()) { | 
 |       die("require JSON string, but got " + name_data.dump()); | 
 |     } | 
 |     dep.require_list.push_back(name_data.as_string()); | 
 |   } | 
 | } | 
 |  | 
 | ModuleDep parse_ddi(std::istream &ddi_stream) { | 
 |   ModuleDep dep{}; | 
 |   std::string ddi_string((std::istreambuf_iterator<char>(ddi_stream)), | 
 |                          std::istreambuf_iterator<char>()); | 
 |   JsonValue data = parse_json(ddi_string); | 
 |   if (!data.is_object()) { | 
 |     die("require ddi content is JSON object"); | 
 |   } | 
 |  | 
 |   auto data_obj = data.as_object(); | 
 |   if (data_obj.find("rules") == data_obj.end()) { | 
 |     die("require 'rules' in ddi content"); | 
 |   } | 
 |  | 
 |   auto rules_data = data.as_object().at("rules"); | 
 |   if (!rules_data.is_array()) { | 
 |     die("require ddi content 'rules' is JSON array"); | 
 |   } | 
 |   auto rules = rules_data.as_array(); | 
 |   // Only 1 rule in DDI file | 
 |   // DDI files can contain multiple rules (in general). | 
 |   // bazel does per-TU scanning rather than batch scanning. | 
 |   // Therefore, report error if multiple rules here | 
 |   if (rules.size() > 1) { | 
 |     die("require ddi content 'rules' has only 1 rule"); | 
 |   } | 
 |   if (rules.empty()) { | 
 |     return dep; | 
 |   } | 
 |   auto rule_data = rules[0]; | 
 |   if (!rule_data.is_object()) { | 
 |     die("require ddi content 'rules[0]' is JSON object"); | 
 |   } | 
 |   auto rule = rule_data.as_object(); | 
 |   auto provides_data = rule["provides"]; | 
 |   auto requires_data = rule["requires"]; | 
 |   parse_provides(provides_data, dep); | 
 |   parse_requires(requires_data, dep); | 
 |   return dep; | 
 | } | 
 |  | 
 | Cpp20ModulesInfo parse_info(std::istream &info_stream) { | 
 |   std::string info_string((std::istreambuf_iterator<char>(info_stream)), | 
 |                           std::istreambuf_iterator<char>()); | 
 |   JsonValue data = parse_json(info_string); | 
 |   if (!data.is_object()) { | 
 |     die("require content is JSON object"); | 
 |   } | 
 |   auto data_obj = data.as_object(); | 
 |   if (data_obj.find("modules") == data_obj.end()) { | 
 |     die("require 'modules' in JSON object"); | 
 |   } | 
 |   auto modules_data = data_obj.at("modules"); | 
 |   if (!modules_data.is_object()) { | 
 |     die("require 'modules' is JSON object"); | 
 |   } | 
 |   if (data_obj.find("usages") == data_obj.end()) { | 
 |     die("require 'usages' in JSON object"); | 
 |   } | 
 |   auto usages_data = data_obj.at("usages"); | 
 |   if (!usages_data.is_object()) { | 
 |     die("require 'usages' is JSON object"); | 
 |   } | 
 |   Cpp20ModulesInfo info; | 
 |   for (const auto &item_data : modules_data.as_object()) { | 
 |     auto name = item_data.first; | 
 |     auto bmi_data = item_data.second; | 
 |     if (!bmi_data.is_string()) { | 
 |       die("require JSON string, but got " + bmi_data.dump()); | 
 |     } | 
 |     info.modules[name] = bmi_data.as_string(); | 
 |   } | 
 |   for (const auto &item_data : usages_data.as_object()) { | 
 |     auto name = item_data.first; | 
 |     auto require_list_data = item_data.second; | 
 |     if (!require_list_data.is_array()) { | 
 |       die("require JSON array"); | 
 |     } | 
 |     std::vector<std::string> require_list; | 
 |     for (const auto &require_item_data : require_list_data.as_array()) { | 
 |       if (!require_item_data.is_string()) { | 
 |         die("require JSON string, but got " + require_item_data.dump()); | 
 |       } | 
 |       require_list.push_back(require_item_data.as_string()); | 
 |     } | 
 |     info.usages[name] = require_list; | 
 |   } | 
 |   return info; | 
 | } | 
 |  |