* @file
*
* This document aims to parse PackageConfig.toml (interop CJ package configuration information),
* which primarily involves the symbols that the target language can expose in interoperability scenarios,
* as well as the specific type sets for generic instantiation.
*/
#include "cangjie/Basic/InteropCJPackageConfigReader.h"
#include "cangjie/Utils/CheckUtils.h"
#include <iostream>
#include <stdexcept>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
#include <toml.h>
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
namespace Cangjie {
using namespace toml;
namespace {
const std::string DEFAULT_SECTION = "default";
const std::string API_STRATEGY = "APIStrategy";
const std::string GENERIC_TYPE_STRATEGY = "GenericTypeStrategy";
const std::string PACKAGE_SECTION = "package";
const std::string PACKAGE_NAME = "name";
const std::string INCLUDED_APIS = "included_apis";
const std::string EXCLUDED_APIS = "excluded_apis";
const std::string GENERIC_OBJECT_CONFIG = "generic_object_configuration";
const std::string TUPLE_CONFIG = "tuple_configuration";
const std::string LAMBDA_PATTERNS = "lambda_patterns";
const std::string SIGNATURE = "signature";
const std::string CLASS_MAPPINGS = "class_mappings";
const std::string TYPE_ARGUMENTS = "type_arguments";
const std::string SYMBOLS = "symbols";
const std::string STRATEGY_FULL = "Full";
const std::string STRATEGY_NONE = "None";
const std::string GENERIC_STRATEGY_PARTIAL = "Partial";
const std::string GENERIC_STRATEGY_NONE = "None";
InteropCJStrategy StringToStrategy(const std::string& str)
{
if (str == STRATEGY_FULL)
return InteropCJStrategy::FULL;
if (str == STRATEGY_NONE)
return InteropCJStrategy::NONE;
return InteropCJStrategy::UNKNOWN;
}
InteropCJGenericStrategyType StringToGenericStrategy(const std::string& str)
{
if (str == GENERIC_STRATEGY_NONE)
return InteropCJGenericStrategyType::NONE;
if (str == GENERIC_STRATEGY_PARTIAL)
return InteropCJGenericStrategyType::PARTIAL;
return InteropCJGenericStrategyType::UNKNOWN;
}
InteropCJStrategy ParseAPIStrategy(toml::Table& packageTable)
{
if (packageTable.find(API_STRATEGY) == packageTable.end() || !packageTable[API_STRATEGY].is<std::string>()) {
return InteropCJStrategy::NONE;
}
auto strategy = packageTable[API_STRATEGY].as<std::string>();
return StringToStrategy(strategy);
}
InteropCJGenericStrategyType ParseGenericTypeStrategy(toml::Table& packageTable)
{
if (packageTable.find(GENERIC_TYPE_STRATEGY) == packageTable.end() ||
!packageTable[GENERIC_TYPE_STRATEGY].is<std::string>()) {
return InteropCJGenericStrategyType::NONE;
}
auto strategy = packageTable[GENERIC_TYPE_STRATEGY].as<std::string>();
return StringToGenericStrategy(strategy);
}
void ParseIncludedAPIs(toml::Table& packageTable, PackageConfig& pkgConfig)
{
if (packageTable.find(INCLUDED_APIS) == packageTable.end() || !packageTable[INCLUDED_APIS].is<toml::Array>()) {
return;
}
auto includedApis = packageTable[INCLUDED_APIS].as<toml::Array>();
for (const auto& item : includedApis) {
if (!item.is<std::string>()) {
continue;
}
auto api = item.as<std::string>();
pkgConfig.interopCJIncludedApis.push_back(api);
}
}
void ParseExcludedAPIs(toml::Table& packageTable, PackageConfig& pkgConfig)
{
if (packageTable.find(EXCLUDED_APIS) == packageTable.end() || !packageTable[EXCLUDED_APIS].is<toml::Array>()) {
return;
}
auto excludedApis = packageTable[EXCLUDED_APIS].as<toml::Array>();
for (const auto& item : excludedApis) {
if (!item.is<std::string>()) {
continue;
}
auto api = item.as<std::string>();
pkgConfig.interopCJExcludedApis.push_back(api);
}
}
void ProcessGenericTypeWithSymbols(toml::Table& genTable, const std::string& fullName, size_t angleBracketPos,
const std::unordered_map<std::string, std::vector<std::string>>& typeArgumentsMap, PackageConfig& pkgConfig)
{
std::string outerType = fullName.substr(0, angleBracketPos);
std::string innerType = fullName.substr(angleBracketPos + 1, fullName.size() - angleBracketPos - 2);
auto it = typeArgumentsMap.find(outerType);
if (it == typeArgumentsMap.end()) {
return;
}
const auto& allowedTypes = it->second;
if (std::find(allowedTypes.begin(), allowedTypes.end(), innerType) == allowedTypes.end()) {
return;
}
if (genTable.find(SYMBOLS) == genTable.end() || !genTable[SYMBOLS].is<toml::Array>()) {
return;
}
GenericTypeArguments typeArgs;
auto symbolsArray = genTable[SYMBOLS].as<toml::Array>();
for (const auto& symbol : symbolsArray) {
if (!symbol.is<std::string>()) {
continue;
}
typeArgs.symbols.insert(symbol.as<std::string>());
}
pkgConfig.allowedInteropCJGenericInstantiations[outerType][innerType] = std::move(typeArgs);
}
void ProcessNonGenericTypeWithSymbols(toml::Table& genTable, const std::string& name, PackageConfig& pkgConfig)
{
if (genTable.find(SYMBOLS) == genTable.end() || !genTable[SYMBOLS].is<toml::Array>()) {
return;
}
GenericTypeArguments typeArgs;
auto symbolsArray = genTable[SYMBOLS].as<toml::Array>();
for (const auto& symbol : symbolsArray) {
if (!symbol.is<std::string>()) {
continue;
}
typeArgs.symbols.insert(symbol.as<std::string>());
}
pkgConfig.allowedInteropCJGenericInstantiations[name][""] = std::move(typeArgs);
}
void ParseTupleConfiguration(toml::Table& packageTable, PackageConfig& pkgConfig)
{
if (packageTable.find(TUPLE_CONFIG) == packageTable.end() || !packageTable[TUPLE_CONFIG].is<toml::Array>()) {
return;
}
auto tuples = packageTable[TUPLE_CONFIG].as<toml::Array>();
for (const auto& item : tuples) {
if (!item.is<std::string>()) {
continue;
}
auto name = item.as<std::string>();
pkgConfig.interopTuples.push_back(name);
}
}
std::string Trim(const std::string& str)
{
size_t start = 0;
size_t end = str.length();
while (start < end && std::isspace(static_cast<unsigned char>(str[start]))) {
++start;
}
while (end > start && std::isspace(static_cast<unsigned char>(str[end - 1]))) {
--end;
}
return str.substr(start, end - start);
}
std::vector<std::string> ParseParameterList(const std::string& paramsStr)
{
std::vector<std::string> parameters;
if (paramsStr.empty()) {
return parameters;
}
std::string currentParam;
bool inGeneric = false;
int genericDepth = 0;
for (char ch : paramsStr) {
if (ch == '<') {
inGeneric = true;
genericDepth++;
currentParam += ch;
} else if (ch == '>') {
genericDepth--;
if (genericDepth == 0) {
inGeneric = false;
}
currentParam += ch;
} else if (ch == ',' && !inGeneric) {
std::string trimmedParam = Trim(currentParam);
if (!trimmedParam.empty()) {
parameters.push_back(trimmedParam);
}
currentParam.clear();
} else {
currentParam += ch;
}
}
if (!currentParam.empty()) {
std::string trimmedParam = Trim(currentParam);
if (!trimmedParam.empty()) {
parameters.push_back(trimmedParam);
}
}
return parameters;
}
LambdaPattern ParseLambdaSignature(const std::string& signature)
{
CJC_ASSERT(!signature.empty());
std::string trimmed = Trim(signature);
size_t arrowPos = trimmed.find("->");
if (arrowPos == std::string::npos) {
std::cerr << "Invalid lambda signature without ->." << std::endl;
}
if (trimmed.front() != '(' || trimmed.find(')') == std::string::npos) {
std::cerr << "Invalid lambda signature without ()." << std::endl;
}
size_t closeParenPos = trimmed.find(')');
if (closeParenPos >= arrowPos) {
std::cerr << "Invalid lambda signature, -> position is not right." << std::endl;
}
std::string paramsStr = trimmed.substr(1, closeParenPos - 1);
std::string returnTypeStr = trimmed.substr(arrowPos + 2);
std::vector<std::string> paramTypes = ParseParameterList(paramsStr);
std::string returnType = Trim(returnTypeStr);
if (returnType.empty()) {
std::cerr << "Invalid lambda signature, return type can not be empty." << std::endl;
}
return LambdaPattern(trimmed, paramTypes, returnType);
}
void ParseLmabdaPatternsConfiguration(toml::Table& packageTable, PackageConfig& pkgConfig)
{
if (packageTable.find(LAMBDA_PATTERNS) == packageTable.end() || !packageTable[LAMBDA_PATTERNS].is<Array>()) {
return;
}
auto lambdaPatterns = packageTable[LAMBDA_PATTERNS].as<Array>();
for (const auto& item : lambdaPatterns) {
if (!item.is<Table>()) {
continue;
}
auto genTable = item.as<Table>();
if (genTable.find(SIGNATURE) == genTable.end() || !genTable[SIGNATURE].is<std::string>()) {
continue;
}
std::string lambdaSigna = genTable[SIGNATURE].as<std::string>();
LambdaPattern lambdaPat = ParseLambdaSignature(lambdaSigna);
if (genTable.find(CLASS_MAPPINGS) != genTable.end() && genTable[CLASS_MAPPINGS].is<Table>()) {
auto classMappings = genTable[CLASS_MAPPINGS].as<Table>();
for (const auto& [key, value] : classMappings) {
ClassMapping classMap(key, value.as<std::string>());
lambdaPat.ClassMappings.push_back(classMap);
}
}
pkgConfig.lambdaPatterns.push_back(lambdaPat);
}
}
bool ValidateAndProcessTypeString(const std::string& typeString,
const std::unordered_set<std::string>& validTypeSet,
const PackageConfig& pkgConfig,
std::vector<std::string>& validatedTypes)
{
std::istringstream typeStream(typeString);
std::string individualType;
while (std::getline(typeStream, individualType, ',')) {
auto firstChar = individualType.find_first_not_of(" \t\n\r");
auto lastChar = individualType.find_last_not_of(" \t\n\r");
if (firstChar == std::string::npos || lastChar == std::string::npos) {
continue;
}
individualType = individualType.substr(firstChar, lastChar - firstChar + 1);
if (validTypeSet.find(individualType) == validTypeSet.end()) {
std::cerr << "Error: Invalid type detected in package '"
<< pkgConfig.name << "' configuration" << std::endl;
std::cerr << " Invalid type: '" << individualType << "'" << std::endl;
std::cerr << " Supported types (Exist difference between Java and Objc): ";
bool first = true;
for (const auto& validType : validTypeSet) {
if (!first) std::cerr << ", ";
std::cerr << validType;
first = false;
}
std::cerr << std::endl;
return false;
}
validatedTypes.push_back(individualType);
}
return true;
}
std::string CombineTypesToString(const std::vector<std::string>& types)
{
if (types.empty()) {
return "";
}
std::ostringstream combinedStream;
for (size_t i = 0; i < types.size(); ++i) {
if (i > 0) {
combinedStream << ", ";
}
combinedStream << types[i];
}
return combinedStream.str();
}
bool ProcessTypeParameterArray(const toml::Array& typeArgs,
const std::unordered_set<std::string>& validTypeSet,
const PackageConfig& pkgConfig, std::vector<std::string>& collectedTypes,
std::unordered_map<std::string, GenericTypeArguments>& genericInstantiations)
{
for (const auto& type : typeArgs) {
if (!type.is<std::string>()) {
continue;
}
std::string typeString = type.as<std::string>();
std::vector<std::string> validatedTypes;
if (!ValidateAndProcessTypeString(typeString, validTypeSet, pkgConfig, validatedTypes)) {
return false;
}
if (validatedTypes.empty()) {
std::cerr << "Error: Empty type definition in package '"
<< pkgConfig.name << "'" << std::endl;
std::cerr << " Type string: '" << typeString << "'" << std::endl;
return false;
}
std::string combinedTypeString = CombineTypesToString(validatedTypes);
collectedTypes.push_back(combinedTypeString);
genericInstantiations[combinedTypeString] = GenericTypeArguments();
}
return true;
}
bool CollectTypeArguments(toml::Array& allowedGenerics,
std::unordered_map<std::string, std::vector<std::string>>& typeArgumentsMap,
PackageConfig& pkgConfig)
{
static const std::unordered_set<std::string> VALID_TYPE_SET = {
"Int", "Int8", "Int16", "Int32", "Int64", "IntNative",
"UInt8", "UInt16", "UInt32", "UInt64", "UIntNative",
"Float16", "Float32", "Float64",
"Bool", "Boolean", "Unit"
};
for (const auto& item : allowedGenerics) {
if (!item.is<toml::Table>()) {
continue;
}
auto genTable = item.as<toml::Table>();
if (genTable.find(PACKAGE_NAME) == genTable.end() ||
!genTable[PACKAGE_NAME].is<std::string>()) {
continue;
}
std::string packageName = genTable[PACKAGE_NAME].as<std::string>();
if (genTable.find(TYPE_ARGUMENTS) == genTable.end() ||
!genTable[TYPE_ARGUMENTS].is<toml::Array>()) {
continue;
}
auto typeArgs = genTable[TYPE_ARGUMENTS].as<toml::Array>();
std::vector<std::string> collectedTypes;
if (!ProcessTypeParameterArray(typeArgs, VALID_TYPE_SET, pkgConfig, collectedTypes,
pkgConfig.allowedInteropCJGenericInstantiations[packageName])) {
return false;
}
if (!collectedTypes.empty()) {
typeArgumentsMap[packageName] = std::move(collectedTypes);
}
}
return true;
}
void ProcessSymbolConfigurations(toml::Array& allowedGenerics,
const std::unordered_map<std::string, std::vector<std::string>>& typeArgumentsMap, PackageConfig& pkgConfig)
{
for (const auto& item : allowedGenerics) {
if (!item.is<toml::Table>()) {
continue;
}
auto genTable = item.as<toml::Table>();
if (genTable.find(PACKAGE_NAME) == genTable.end() || !genTable[PACKAGE_NAME].is<std::string>()) {
continue;
}
std::string name = genTable[PACKAGE_NAME].as<std::string>();
size_t pos = name.find('<');
if (pos != std::string::npos && name.back() == '>') {
ProcessGenericTypeWithSymbols(genTable, name, pos, typeArgumentsMap, pkgConfig);
} else if (genTable.find(SYMBOLS) != genTable.end() && genTable[SYMBOLS].is<toml::Array>()) {
ProcessNonGenericTypeWithSymbols(genTable, name, pkgConfig);
}
}
}
bool ParseGenericObjectConfiguration(toml::Table& packageTable, PackageConfig& pkgConfig)
{
if (packageTable.find(GENERIC_OBJECT_CONFIG) == packageTable.end() ||
!packageTable[GENERIC_OBJECT_CONFIG].is<toml::Array>()) {
return true;
}
auto allowedGenerics = packageTable[GENERIC_OBJECT_CONFIG].as<toml::Array>();
std::unordered_map<std::string, std::vector<std::string>> typeArgumentsMap;
bool trueHandled = CollectTypeArguments(allowedGenerics, typeArgumentsMap, pkgConfig);
ProcessSymbolConfigurations(allowedGenerics, typeArgumentsMap, pkgConfig);
return trueHandled;
}
void ParseDefaultConfig(toml::Table& tbl, InteropCJPackageConfigReader& reader)
{
if (tbl.find(DEFAULT_SECTION) == tbl.end()) {
return;
}
const auto& defaultEntry = tbl.find(DEFAULT_SECTION)->second;
if (!defaultEntry.is<toml::Table>()) {
return;
}
auto defaultTable = defaultEntry.as<toml::Table>();
if (defaultTable.find(API_STRATEGY) != defaultTable.end() && defaultTable[API_STRATEGY].is<std::string>()) {
auto strategy = defaultTable[API_STRATEGY].as<std::string>();
reader.defaultApiStrategy = StringToStrategy(strategy);
}
if (defaultTable.find(GENERIC_TYPE_STRATEGY) != defaultTable.end() &&
defaultTable[GENERIC_TYPE_STRATEGY].is<std::string>()) {
auto strategy = defaultTable[GENERIC_TYPE_STRATEGY].as<std::string>();
reader.defaultGenericTypeStrategy = StringToGenericStrategy(strategy);
}
}
bool ParseSinglePackage(toml::Table& packageTable, PackageConfig& pkgConfig)
{
if (packageTable.find(PACKAGE_NAME) == packageTable.end() || !packageTable[PACKAGE_NAME].is<std::string>()) {
return false;
}
pkgConfig.name = packageTable[PACKAGE_NAME].as<std::string>();
pkgConfig.apiStrategy = ParseAPIStrategy(packageTable);
pkgConfig.genericTypeStrategy = ParseGenericTypeStrategy(packageTable);
ParseIncludedAPIs(packageTable, pkgConfig);
ParseExcludedAPIs(packageTable, pkgConfig);
bool trueHandled = ParseGenericObjectConfiguration(packageTable, pkgConfig);
ParseTupleConfiguration(packageTable, pkgConfig);
ParseLmabdaPatternsConfiguration(packageTable, pkgConfig);
return trueHandled;
}
bool ParsePackageConfigurations(toml::Table& tbl, InteropCJPackageConfigReader& reader)
{
if (tbl.find(PACKAGE_SECTION) == tbl.end()) {
return true;
}
const auto& packageEntry = tbl.find(PACKAGE_SECTION)->second;
if (!packageEntry.is<toml::Array>()) {
return true;
}
auto packageArray = packageEntry.as<toml::Array>();
for (const auto& packageItem : packageArray) {
if (!packageItem.is<toml::Table>()) {
continue;
}
PackageConfig pkgConfig;
auto packageTable = packageItem.as<toml::Table>();
if (!ParseSinglePackage(packageTable, pkgConfig)) {
return false;
}
reader.packages[pkgConfig.name] = std::move(pkgConfig);
}
return true;
}
}
bool InteropCJPackageConfigReader::Parse(const std::string& filePath)
{
try {
std::ifstream file(filePath);
if (!file.is_open()) {
std::cerr << "Error: Cannot open configuration file." << filePath << std::endl;
return false;
}
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
if (content.find("/*") != std::string::npos) {
std::cerr << "Failed to open configuration file. Error: Unsupported block comment '/*' detected." <<
"Please use line comment '#' instead." << std::endl;
return false;
}
toml::Table tbl = toml::parseFile(filePath).value.as<toml::Table>();
ParseDefaultConfig(tbl, *this);
bool trueHandled = ParsePackageConfigurations(tbl, *this);
return trueHandled;
} catch (const std::exception& e) {
std::cerr << "Error parsing config: " << e.what() << std::endl;
return false;
}
}
std::optional<PackageConfig> InteropCJPackageConfigReader::GetPackage(const std::string& name) const
{
auto it = packages.find(name);
if (it != packages.end()) {
return it->second;
}
return std::nullopt;
}
InteropCJStrategy InteropCJPackageConfigReader::GetApiStrategy(const std::string& packageName) const
{
if (auto pkg = GetPackage(packageName)) {
return pkg->apiStrategy;
}
return defaultApiStrategy;
}
InteropCJGenericStrategyType InteropCJPackageConfigReader::GetGenericTypeStrategy(const std::string& packageName) const
{
if (auto pkg = GetPackage(packageName)) {
return pkg->genericTypeStrategy;
}
return defaultGenericTypeStrategy;
}
bool InteropCJPackageConfigReader::Validate() const
{
if (defaultApiStrategy == InteropCJStrategy::UNKNOWN) {
std::cerr << "Validation failed: Default API strategy is unknown" << std::endl;
return false;
}
if (defaultGenericTypeStrategy == InteropCJGenericStrategyType::UNKNOWN) {
std::cerr << "Validation failed: Default generic type strategy is unknown" << std::endl;
return false;
}
for (const auto& [name, pkg] : packages) {
if (pkg.apiStrategy == InteropCJStrategy::UNKNOWN) {
std::cerr << "Validation failed: '" << name << "' API strategy is unknown" << std::endl;
return false;
}
if (pkg.genericTypeStrategy == InteropCJGenericStrategyType::UNKNOWN) {
std::cerr << "Validation failed: '" << name << "' generic type strategy is unknown" << std::endl;
return false;
}
if (pkg.apiStrategy == InteropCJStrategy::FULL && !pkg.interopCJIncludedApis.empty()) {
std::cerr << "Validation failed for package '" << name
<< "': API strategy is Full but IncludedApis is Configured " << std::endl;
return false;
}
if (pkg.apiStrategy == InteropCJStrategy::NONE && !pkg.interopCJExcludedApis.empty()) {
std::cerr << "Validation failed for package '" << name
<< "': API strategy is None but ExcludedApis is Configured " << std::endl;
return false;
}
if (!pkg.interopCJIncludedApis.empty() && !pkg.interopCJExcludedApis.empty()) {
std::cerr << "Validation failed for package '" << name << "': Cannot hava both included and excluded APIs"
<< std::endl;
return false;
}
if (pkg.genericTypeStrategy == InteropCJGenericStrategyType::NONE &&
!pkg.allowedInteropCJGenericInstantiations.empty()) {
std::cerr << "Validation failed for package '" << name
<< "': None generic strategy cannot hava generic instantiations" << std::endl;
return false;
}
}
return true;
}
}