Initial release
This commit is contained in:
@@ -0,0 +1,517 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Project : VST SDK
|
||||
// Flags : clang-format SMTGSequencer
|
||||
//
|
||||
// Category : moduleinfo
|
||||
// Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp
|
||||
// Created by : Steinberg, 01/2022
|
||||
// Description : utility functions to parse moduleinfo json files
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// This file is part of a Steinberg SDK. It is subject to the license terms
|
||||
// in the LICENSE file found in the top-level directory of this distribution
|
||||
// and at www.steinberg.net/sdklicenses.
|
||||
// No part of the SDK, including this file, may be copied, modified, propagated,
|
||||
// or distributed except according to the terms contained in the LICENSE file.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "moduleinfoparser.h"
|
||||
#include "jsoncxx.h"
|
||||
#include "pluginterfaces/base/ipluginbase.h"
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
namespace Steinberg::ModuleInfoLib {
|
||||
namespace {
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
void printJsonParseError (json_parse_result_s& parseResult, std::ostream& errorOut)
|
||||
{
|
||||
errorOut << "error : "
|
||||
<< JSON::errorToString (static_cast<json_parse_error_e> (parseResult.error)) << '\n';
|
||||
errorOut << "offset : " << parseResult.error_offset << '\n';
|
||||
errorOut << "line no: " << parseResult.error_line_no << '\n';
|
||||
errorOut << "row no : " << parseResult.error_row_no << '\n';
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct parse_error : std::exception
|
||||
{
|
||||
parse_error (const std::string& str, const JSON::Value& value)
|
||||
: str (str), location (value.getSourceLocation ())
|
||||
{
|
||||
addLocation (location);
|
||||
}
|
||||
parse_error (const std::string& str, const JSON::String& value)
|
||||
: str (str), location (value.getSourceLocation ())
|
||||
{
|
||||
addLocation (location);
|
||||
}
|
||||
const char* what () const noexcept override { return str.data (); }
|
||||
|
||||
private:
|
||||
void addLocation (const JSON::SourceLocation& loc)
|
||||
{
|
||||
str += '\n';
|
||||
str += "offset:";
|
||||
str += std::to_string (loc.offset);
|
||||
str += '\n';
|
||||
str += "line:";
|
||||
str += std::to_string (loc.line);
|
||||
str += '\n';
|
||||
str += "row:";
|
||||
str += std::to_string (loc.row);
|
||||
str += '\n';
|
||||
}
|
||||
|
||||
std::string str;
|
||||
JSON::SourceLocation location;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
struct ModuleInfoJsonParser
|
||||
{
|
||||
ModuleInfoJsonParser () = default;
|
||||
|
||||
std::string_view getText (const JSON::Value& value) const
|
||||
{
|
||||
if (auto str = value.asString ())
|
||||
return str->text ();
|
||||
throw parse_error ("Expect a String here", value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T getInteger (const JSON::Value& value) const
|
||||
{
|
||||
if (auto number = value.asNumber ())
|
||||
{
|
||||
if (auto result = number->getInteger ())
|
||||
{
|
||||
if (result > static_cast<int64_t> (std::numeric_limits<T>::max ()) ||
|
||||
result < static_cast<int64_t> (std::numeric_limits<T>::min ()))
|
||||
throw parse_error ("Value is out of range here", value);
|
||||
return static_cast<T> (*result);
|
||||
}
|
||||
throw parse_error ("Expect an Integer here", value);
|
||||
}
|
||||
throw parse_error ("Expect a Number here", value);
|
||||
}
|
||||
|
||||
double getDouble (const JSON::Value& value) const
|
||||
{
|
||||
if (auto number = value.asNumber ())
|
||||
{
|
||||
if (auto result = number->getDouble ())
|
||||
return *result;
|
||||
throw parse_error ("Expect a Double here", value);
|
||||
}
|
||||
throw parse_error ("Expect a Number here", value);
|
||||
}
|
||||
|
||||
void parseFactoryInfo (const JSON::Value& value)
|
||||
{
|
||||
enum ParsedBits
|
||||
{
|
||||
Vendor = 1 << 0,
|
||||
URL = 1 << 1,
|
||||
EMail = 1 << 2,
|
||||
Flags = 1 << 3,
|
||||
};
|
||||
uint32_t parsed {0};
|
||||
if (auto obj = value.asObject ())
|
||||
{
|
||||
for (const auto& el : *obj)
|
||||
{
|
||||
auto elementName = el.name ().text ();
|
||||
if (elementName == "Vendor")
|
||||
{
|
||||
if (parsed & ParsedBits::Vendor)
|
||||
throw parse_error ("Only one 'Vendor' key allowed", el.name ());
|
||||
parsed |= ParsedBits::Vendor;
|
||||
info.factoryInfo.vendor = getText (el.value ());
|
||||
}
|
||||
else if (elementName == "URL")
|
||||
{
|
||||
if (parsed & ParsedBits::URL)
|
||||
throw parse_error ("Only one 'URL' key allowed", el.name ());
|
||||
parsed |= ParsedBits::URL;
|
||||
info.factoryInfo.url = getText (el.value ());
|
||||
}
|
||||
else if (elementName == "E-Mail")
|
||||
{
|
||||
if (parsed & ParsedBits::EMail)
|
||||
throw parse_error ("Only one 'E-Mail' key allowed", el.name ());
|
||||
parsed |= ParsedBits::EMail;
|
||||
info.factoryInfo.email = getText (el.value ());
|
||||
}
|
||||
else if (elementName == "Flags")
|
||||
{
|
||||
if (parsed & ParsedBits::Flags)
|
||||
throw parse_error ("Only one 'Flags' key allowed", el.name ());
|
||||
auto flags = el.value ().asObject ();
|
||||
if (!flags)
|
||||
throw parse_error ("Expect 'Flags' to be a JSON Object", el.name ());
|
||||
for (const auto& flag : *flags)
|
||||
{
|
||||
auto flagName = flag.name ().text ();
|
||||
auto flagValue = flag.value ().asBoolean ();
|
||||
if (!flagValue)
|
||||
throw parse_error ("Flag must be a boolean", flag.value ());
|
||||
if (flagName == "Classes Discardable")
|
||||
{
|
||||
if (*flagValue)
|
||||
info.factoryInfo.flags |= PFactoryInfo::kClassesDiscardable;
|
||||
}
|
||||
else if (flagName == "Component Non Discardable")
|
||||
{
|
||||
if (*flagValue)
|
||||
info.factoryInfo.flags |= PFactoryInfo::kComponentNonDiscardable;
|
||||
}
|
||||
else if (flagName == "Unicode")
|
||||
{
|
||||
if (*flagValue)
|
||||
info.factoryInfo.flags |= PFactoryInfo::kUnicode;
|
||||
}
|
||||
else
|
||||
throw parse_error ("Unknown flag", flag.name ());
|
||||
}
|
||||
parsed |= ParsedBits::Flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(parsed & ParsedBits::Vendor))
|
||||
throw std::logic_error ("Missing 'Vendor' in Factory Info");
|
||||
if (!(parsed & ParsedBits::URL))
|
||||
throw std::logic_error ("Missing 'URL' in Factory Info");
|
||||
if (!(parsed & ParsedBits::EMail))
|
||||
throw std::logic_error ("Missing 'EMail' in Factory Info");
|
||||
if (!(parsed & ParsedBits::Flags))
|
||||
throw std::logic_error ("Missing 'Flags' in Factory Info");
|
||||
}
|
||||
|
||||
void parseClasses (const JSON::Value& value)
|
||||
{
|
||||
enum ParsedBits
|
||||
{
|
||||
CID = 1 << 0,
|
||||
Category = 1 << 1,
|
||||
Name = 1 << 2,
|
||||
Vendor = 1 << 3,
|
||||
Version = 1 << 4,
|
||||
SDKVersion = 1 << 5,
|
||||
SubCategories = 1 << 6,
|
||||
ClassFlags = 1 << 7,
|
||||
Snapshots = 1 << 8,
|
||||
Cardinality = 1 << 9,
|
||||
};
|
||||
|
||||
auto array = value.asArray ();
|
||||
if (!array)
|
||||
throw parse_error ("Expect Classes Array", value);
|
||||
for (const auto& classInfoEl : *array)
|
||||
{
|
||||
auto classInfo = classInfoEl.value ().asObject ();
|
||||
if (!classInfo)
|
||||
throw parse_error ("Expect Class Object", classInfoEl.value ());
|
||||
|
||||
ModuleInfo::ClassInfo ci {};
|
||||
|
||||
uint32_t parsed {0};
|
||||
|
||||
for (const auto& el : *classInfo)
|
||||
{
|
||||
auto elementName = el.name ().text ();
|
||||
if (elementName == "CID")
|
||||
{
|
||||
if (parsed & ParsedBits::CID)
|
||||
throw parse_error ("Only one 'CID' key allowed", el.name ());
|
||||
ci.cid = getText (el.value ());
|
||||
parsed |= ParsedBits::CID;
|
||||
}
|
||||
else if (elementName == "Category")
|
||||
{
|
||||
if (parsed & ParsedBits::Category)
|
||||
throw parse_error ("Only one 'Category' key allowed", el.name ());
|
||||
ci.category = getText (el.value ());
|
||||
parsed |= ParsedBits::Category;
|
||||
}
|
||||
else if (elementName == "Name")
|
||||
{
|
||||
if (parsed & ParsedBits::Name)
|
||||
throw parse_error ("Only one 'Name' key allowed", el.name ());
|
||||
ci.name = getText (el.value ());
|
||||
parsed |= ParsedBits::Name;
|
||||
}
|
||||
else if (elementName == "Vendor")
|
||||
{
|
||||
if (parsed & ParsedBits::Vendor)
|
||||
throw parse_error ("Only one 'Vendor' key allowed", el.name ());
|
||||
ci.vendor = getText (el.value ());
|
||||
parsed |= ParsedBits::Vendor;
|
||||
}
|
||||
else if (elementName == "Version")
|
||||
{
|
||||
if (parsed & ParsedBits::Version)
|
||||
throw parse_error ("Only one 'Version' key allowed", el.name ());
|
||||
ci.version = getText (el.value ());
|
||||
parsed |= ParsedBits::Version;
|
||||
}
|
||||
else if (elementName == "SDKVersion")
|
||||
{
|
||||
if (parsed & ParsedBits::SDKVersion)
|
||||
throw parse_error ("Only one 'SDKVersion' key allowed", el.name ());
|
||||
ci.sdkVersion = getText (el.value ());
|
||||
parsed |= ParsedBits::SDKVersion;
|
||||
}
|
||||
else if (elementName == "Sub Categories")
|
||||
{
|
||||
if (parsed & ParsedBits::SubCategories)
|
||||
throw parse_error ("Only one 'Sub Categories' key allowed", el.name ());
|
||||
auto subCatArr = el.value ().asArray ();
|
||||
if (!subCatArr)
|
||||
throw parse_error ("Expect Array here", el.value ());
|
||||
for (const auto& catEl : *subCatArr)
|
||||
{
|
||||
auto cat = getText (catEl.value ());
|
||||
ci.subCategories.emplace_back (cat);
|
||||
}
|
||||
parsed |= ParsedBits::SubCategories;
|
||||
}
|
||||
else if (elementName == "Class Flags")
|
||||
{
|
||||
if (parsed & ParsedBits::ClassFlags)
|
||||
throw parse_error ("Only one 'Class Flags' key allowed", el.name ());
|
||||
ci.flags = getInteger<uint32_t> (el.value ());
|
||||
parsed |= ParsedBits::ClassFlags;
|
||||
}
|
||||
else if (elementName == "Cardinality")
|
||||
{
|
||||
if (parsed & ParsedBits::Cardinality)
|
||||
throw parse_error ("Only one 'Cardinality' key allowed", el.name ());
|
||||
ci.cardinality = getInteger<int32_t> (el.value ());
|
||||
parsed |= ParsedBits::Cardinality;
|
||||
}
|
||||
else if (elementName == "Snapshots")
|
||||
{
|
||||
if (parsed & ParsedBits::Snapshots)
|
||||
throw parse_error ("Only one 'Snapshots' key allowed", el.name ());
|
||||
auto snapArr = el.value ().asArray ();
|
||||
if (!snapArr)
|
||||
throw parse_error ("Expect Array here", el.value ());
|
||||
for (const auto& snapEl : *snapArr)
|
||||
{
|
||||
auto snap = snapEl.value ().asObject ();
|
||||
if (!snap)
|
||||
throw parse_error ("Expect Object here", snapEl.value ());
|
||||
ModuleInfo::Snapshot snapshot;
|
||||
for (const auto& spEl : *snap)
|
||||
{
|
||||
auto spElName = spEl.name ().text ();
|
||||
if (spElName == "Path")
|
||||
snapshot.path = getText (spEl.value ());
|
||||
else if (spElName == "Scale Factor")
|
||||
snapshot.scaleFactor = getDouble (spEl.value ());
|
||||
else
|
||||
throw parse_error ("Unexpected key", spEl.name ());
|
||||
}
|
||||
if (snapshot.scaleFactor == 0. || snapshot.path.empty ())
|
||||
throw parse_error ("Missing Snapshot keys", snapEl.value ());
|
||||
ci.snapshots.emplace_back (std::move (snapshot));
|
||||
}
|
||||
parsed |= ParsedBits::Snapshots;
|
||||
}
|
||||
else
|
||||
throw parse_error ("Unexpected key", el.name ());
|
||||
}
|
||||
if (!(parsed & ParsedBits::CID))
|
||||
throw parse_error ("'CID' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::Category))
|
||||
throw parse_error ("'Category' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::Name))
|
||||
throw parse_error ("'Name' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::Vendor))
|
||||
throw parse_error ("'Vendor' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::Version))
|
||||
throw parse_error ("'Version' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::SDKVersion))
|
||||
throw parse_error ("'SDK Version' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::ClassFlags))
|
||||
throw parse_error ("'Class Flags' key missing", classInfoEl.value ());
|
||||
if (!(parsed & ParsedBits::Cardinality))
|
||||
throw parse_error ("'Cardinality' key missing", classInfoEl.value ());
|
||||
info.classes.emplace_back (std::move (ci));
|
||||
}
|
||||
}
|
||||
|
||||
void parseCompatibility (const JSON::Value& value)
|
||||
{
|
||||
auto arr = value.asArray ();
|
||||
if (!arr)
|
||||
throw parse_error ("Expect Array here", value);
|
||||
for (const auto& el : *arr)
|
||||
{
|
||||
auto obj = el.value ().asObject ();
|
||||
if (!obj)
|
||||
throw parse_error ("Expect Object here", el.value ());
|
||||
|
||||
ModuleInfo::Compatibility compat;
|
||||
for (const auto& objEl : *obj)
|
||||
{
|
||||
auto elementName = objEl.name ().text ();
|
||||
if (elementName == "New")
|
||||
compat.newCID = getText (objEl.value ());
|
||||
else if (elementName == "Old")
|
||||
{
|
||||
auto oldElArr = objEl.value ().asArray ();
|
||||
if (!oldElArr)
|
||||
throw parse_error ("Expect Array here", objEl.value ());
|
||||
for (const auto& old : *oldElArr)
|
||||
{
|
||||
compat.oldCID.emplace_back (getText (old.value ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (compat.newCID.empty ())
|
||||
throw parse_error ("Expect New CID here", el.value ());
|
||||
if (compat.oldCID.empty ())
|
||||
throw parse_error ("Expect Old CID here", el.value ());
|
||||
info.compatibility.emplace_back (std::move (compat));
|
||||
}
|
||||
}
|
||||
|
||||
void parse (const JSON::Document& doc)
|
||||
{
|
||||
auto docObj = doc.asObject ();
|
||||
if (!docObj)
|
||||
throw parse_error ("Unexpected", doc);
|
||||
|
||||
enum ParsedBits
|
||||
{
|
||||
Name = 1 << 0,
|
||||
Version = 1 << 1,
|
||||
FactoryInfo = 1 << 2,
|
||||
Compatibility = 1 << 3,
|
||||
Classes = 1 << 4,
|
||||
};
|
||||
|
||||
uint32_t parsed {0};
|
||||
for (const auto& el : *docObj)
|
||||
{
|
||||
auto elementName = el.name ().text ();
|
||||
if (elementName == "Name")
|
||||
{
|
||||
if (parsed & ParsedBits::Name)
|
||||
throw parse_error ("Only one 'Name' key allowed", el.name ());
|
||||
parsed |= ParsedBits::Name;
|
||||
info.name = getText (el.value ());
|
||||
}
|
||||
else if (elementName == "Version")
|
||||
{
|
||||
if (parsed & ParsedBits::Version)
|
||||
throw parse_error ("Only one 'Version' key allowed", el.name ());
|
||||
parsed |= ParsedBits::Version;
|
||||
info.version = getText (el.value ());
|
||||
}
|
||||
else if (elementName == "Factory Info")
|
||||
{
|
||||
if (parsed & ParsedBits::FactoryInfo)
|
||||
throw parse_error ("Only one 'Factory Info' key allowed", el.name ());
|
||||
parseFactoryInfo (el.value ());
|
||||
parsed |= ParsedBits::FactoryInfo;
|
||||
}
|
||||
else if (elementName == "Compatibility")
|
||||
{
|
||||
if (parsed & ParsedBits::Compatibility)
|
||||
throw parse_error ("Only one 'Compatibility' key allowed", el.name ());
|
||||
parseCompatibility (el.value ());
|
||||
parsed |= ParsedBits::Compatibility;
|
||||
}
|
||||
else if (elementName == "Classes")
|
||||
{
|
||||
if (parsed & ParsedBits::Classes)
|
||||
throw parse_error ("Only one 'Classes' key allowed", el.name ());
|
||||
parseClasses (el.value ());
|
||||
parsed |= ParsedBits::Classes;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw parse_error ("Unexpected JSON Token", el.name ());
|
||||
}
|
||||
}
|
||||
if (!(parsed & ParsedBits::Name))
|
||||
throw std::logic_error ("'Name' key missing");
|
||||
if (!(parsed & ParsedBits::Version))
|
||||
throw std::logic_error ("'Version' key missing");
|
||||
if (!(parsed & ParsedBits::FactoryInfo))
|
||||
throw std::logic_error ("'Factory Info' key missing");
|
||||
if (!(parsed & ParsedBits::Classes))
|
||||
throw std::logic_error ("'Classes' key missing");
|
||||
}
|
||||
|
||||
ModuleInfo&& takeInfo () { return std::move (info); }
|
||||
|
||||
private:
|
||||
ModuleInfo info;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // anonymous
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::optional<ModuleInfo> parseJson (std::string_view jsonData, std::ostream* optErrorOutput)
|
||||
{
|
||||
auto docVar = JSON::Document::parse (jsonData);
|
||||
if (auto res = std::get_if<json_parse_result_s> (&docVar))
|
||||
{
|
||||
if (optErrorOutput)
|
||||
printJsonParseError (*res, *optErrorOutput);
|
||||
return {};
|
||||
}
|
||||
auto doc = std::get_if<JSON::Document> (&docVar);
|
||||
assert (doc);
|
||||
try
|
||||
{
|
||||
ModuleInfoJsonParser parser;
|
||||
parser.parse (*doc);
|
||||
return parser.takeInfo ();
|
||||
}
|
||||
catch (std::exception& error)
|
||||
{
|
||||
if (optErrorOutput)
|
||||
*optErrorOutput << error.what () << '\n';
|
||||
return {};
|
||||
}
|
||||
// unreachable
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
std::optional<ModuleInfo::CompatibilityList> parseCompatibilityJson (std::string_view jsonData,
|
||||
std::ostream* optErrorOutput)
|
||||
{
|
||||
auto docVar = JSON::Document::parse (jsonData);
|
||||
if (auto res = std::get_if<json_parse_result_s> (&docVar))
|
||||
{
|
||||
if (optErrorOutput)
|
||||
printJsonParseError (*res, *optErrorOutput);
|
||||
return {};
|
||||
}
|
||||
auto doc = std::get_if<JSON::Document> (&docVar);
|
||||
assert (doc);
|
||||
try
|
||||
{
|
||||
ModuleInfoJsonParser parser;
|
||||
parser.parseCompatibility (*doc);
|
||||
return parser.takeInfo ().compatibility;
|
||||
}
|
||||
catch (std::exception& error)
|
||||
{
|
||||
if (optErrorOutput)
|
||||
*optErrorOutput << error.what () << '\n';
|
||||
return {};
|
||||
}
|
||||
// unreachable
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
} // Steinberg::ModuelInfoLib
|
||||
Reference in New Issue
Block a user