33 using Contribution = std::map<std::string, std::map<std::string, ScriptValue>>;
36 const std::u8string text = path.generic_u8string();
38 return {text.begin(), text.end()};
44 std::expected<std::string, std::string>
ModulePath(std::string_view name) {
47 const size_t slash = name.find(
'/', begin);
48 const std::string_view segment = name.substr(begin,
49 slash == std::string_view::npos ? slash : slash - begin);
51 return std::unexpected(
52 "名前は 'sub/util' の形(英小文字・数字・_・- を / で区切る。.. や拡張子は書かない)");
53 if (slash == std::string_view::npos)
63 return [&package](std::string_view name) -> std::expected<ModuleFile, std::string> {
66 return std::unexpected(path.error());
68 const auto found = package.
files.find(*path);
69 if (found == package.
files.end())
70 return std::unexpected(std::format(
"パッケージ '{}' に {} が無い", package.
name, *path));
72 return ModuleFile{.chunkName = package.
name +
"/" + *path, .source = found->second};
80 return std::unexpected(
"depends は名前の配列");
82 std::vector<std::string> names;
85 field.
key.
number ==
static_cast<double>(names.size() + 1);
87 return std::unexpected(
"depends は名前の配列({ \"core\", \"other\" } の形)");
97 return std::unexpected(
"package.luau は表を 1 つ返す");
100 bool hasFormat =
false;
102 const std::string_view key = field.
key.
IsString() ? std::string_view(field.
key.
text) :
"";
103 if (key ==
"format") {
107 }
else if (key ==
"depends") {
110 return std::unexpected(names.error());
111 manifest.
depends = std::move(*names);
112 }
else if (key ==
"entry") {
114 return std::unexpected(
"entry はパッケージの中のファイルの名前('init' の形)");
117 return std::unexpected(
118 std::format(
"知らない欄 {}(書けるのは format・depends・entry)",
ToDebugText(field.
key)));
131 if (file == package.
files.end())
132 return std::unexpected(std::format(
"{} が無い",
MANIFEST_FILE));
135 RunOptions{.modules = nullptr, .captureValues = true});
137 return std::unexpected(run.error().message);
138 if (run->values.size() != 1)
139 return std::unexpected(
"package.luau は表を 1 つ返す");
143 std::ranges::find(manifest->depends, options.
basePackage) == manifest->depends.end())
164 std::string* missingReason) {
166 const auto found = nodes.find(depend);
167 if (found == nodes.end()) {
169 *missingReason = std::format(
"依存するパッケージ '{}' が無い", depend);
174 *missingReason = std::format(
"依存するパッケージ '{}' を読まなかった", depend);
185 bool OrderStep(std::map<std::string, OrderNode>& nodes, std::vector<std::string>& order,
187 for (
auto& [name, node] : nodes) {
190 order.push_back(name);
196 for (
auto& [name, node] : nodes) {
206 bool anyPending =
false;
207 for (
auto& [name, node] : nodes) {
221 std::expected<Contribution, std::string>
ReadContribution(
const std::vector<ScriptValue>& values) {
225 if (values.size() != 1 || !values[0].IsTable())
226 return std::unexpected(
"entry は「分類 → 鍵 → 値」の表を 1 つ返す");
228 for (
const ScriptField& category : values[0].fields) {
229 if (!category.key.IsString() || !
IsValidPackageName(category.key.text) || !category.value.IsTable())
230 return std::unexpected(
231 std::format(
"分類 {} は名前(英小文字・数字・_・-)で、値は表",
ToDebugText(category.key)));
233 auto& entries = contribution[category.key.text];
236 return std::unexpected(
237 std::format(
"{} の鍵 {} は空でない文字列", category.key.text,
ToDebugText(entry.
key)));
249 const auto file = package.
files.find(path);
250 if (file == package.
files.end())
251 return std::unexpected(std::format(
"entry の {} が無い", path));
254 const auto run = sandbox.
Run(package.
name +
"/" + path, file->second,
255 RunOptions{.modules = &resolver, .captureValues = true});
257 return std::unexpected(run.error().message);
266 std::vector<std::string> conflicts;
267 for (
const auto& [category, entries] : contribution) {
268 const auto existing = result.
tables.find(category);
269 if (existing == result.
tables.end())
272 for (
const auto& [key, value] : entries) {
273 const auto previous = existing->second.find(key);
274 if (previous != existing->second.end())
276 std::format(
"{}.{}('{}' が書いた)", category, key, previous->second.package));
280 if (conflicts.empty())
283 std::string reason =
"既にある鍵を上書きしようとした(追加だけの設定): ";
285 reason += (index > 0 ?
"、" :
"") + conflicts[index];
295 auto [slot, added] = result.
tables[category].try_emplace(key);
298 .category = category, .key = key, .previousPackage = slot->second.package, .package = package});
303 slot->second =
TableEntry{.value = std::move(value), .package = package};
307 for (
auto& [category, entries] : contribution) {
308 for (
auto& [key, value] : entries)
309 MergeEntry(result, category, key, std::move(value), package, mode);
316 if (diagnostics.empty())
321 std::make_move_iterator(diagnostics.end()));
329 for (
const std::string& depend : manifest.
depends) {
330 if (!loaded.contains(depend))
331 return std::format(
"依存するパッケージ '{}' を読まなかった", depend);
342 auto contribution =
RunEntry(sandbox, package, manifest);
344 return contribution.error();
348 if (!forbidden.empty())
362 if (name.empty() || name.size() > MAX_NAME_LENGTH)
365 return std::ranges::all_of(name, [](
char character) {
366 return (character >=
'a' && character <=
'z') || (character >=
'0' && character <=
'9') ||
367 character ==
'_' || character ==
'-';
374 return ModulePath(name);
378 std::error_code error;
379 if (!fs::is_directory(folder, error))
380 return std::unexpected(std::format(
"{} はフォルダではない", ToUtf8String(folder)));
383 package.
name = ToUtf8String(folder.filename());
385 fs::recursive_directory_iterator iterator(folder, error);
386 for (; !error && iterator != fs::recursive_directory_iterator(); iterator.increment(error)) {
387 const fs::path& path = iterator->path();
388 if (!iterator->is_regular_file(error) || path.extension() != MODULE_EXTENSION)
391 std::ifstream file(path, std::ios::binary);
393 return std::unexpected(std::format(
"{} を開けない", ToUtf8String(path)));
395 std::string source{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
396 package.
files.emplace(ToUtf8String(path.lexically_relative(folder)), std::move(source));
400 return std::unexpected(std::format(
"{} を読めない: {}", ToUtf8String(folder), error.message()));
405 std::expected<std::vector<PackageSource>, std::string>
ReadPackageRoot(
const fs::path& root) {
406 std::error_code error;
407 std::vector<fs::path> folders;
408 for (fs::directory_iterator iterator(root, error); !error && iterator != fs::directory_iterator();
409 iterator.increment(error)) {
410 if (iterator->is_directory(error))
411 folders.push_back(iterator->path());
415 return std::unexpected(std::format(
"{} を読めない: {}", ToUtf8String(root), error.message()));
417 std::ranges::sort(folders);
419 std::vector<PackageSource> packages;
420 for (
const fs::path& folder : folders) {
421 auto package = ReadPackageFolder(folder);
423 return std::unexpected(package.error());
425 packages.push_back(std::move(*package));
434 std::span<const PackageSource> packages,
437 return std::unexpected(
"殻を Seal してから読む");
440 std::map<std::string, const PackageSource*> byName;
442 if (!byName.emplace(package.name, &package).second)
443 return std::unexpected(std::format(
"パッケージの名前 '{}' が 2 つある", package.name));
449 std::map<std::string, OrderNode> nodes;
450 for (
const auto& [name, package] : byName) {
453 if (!reason.empty()) {
460 ? ReadManifest(sandbox, *package, options)
461 : std::expected<Manifest, std::string>(std::unexpect,
462 "パッケージの名前は英小文字・数字・_・- だけ");
468 nodes.emplace(name, OrderNode{.manifest = std::move(*manifest)});
472 std::vector<std::string> order;
473 while (OrderStep(nodes, order, result)) {
476 std::set<std::string> loaded;
477 for (
const std::string& name : order) {
478 std::string reason = LoadOne(sandbox, *byName.at(name), nodes.at(name).manifest, loaded, options, result);
479 if (!reason.empty()) {
496 for (
const auto& [category, entries] : result.
tables) {
499 for (
const auto& [key, entry] : entries) {
std::expected< RunResult, ScriptError > Run(std::string_view chunkName, std::string_view source, const RunOptions &options={})
std::vector< TypeDiagnostic > CheckPackage(const PackageSource &package, std::string_view entry)
std::vector< TypeDiagnostic > CheckManifest(const PackageSource &package)
void Merge(PackageSetResult &result, const std::string &package, Contribution &&contribution, MergeMode mode)
ModuleResolver MakeResolver(const PackageSource &package)
bool DependsReady(const std::map< std::string, OrderNode > &nodes, const OrderNode &node, std::string *missingReason)
std::expected< Manifest, std::string > ReadManifest(LuauSandbox &sandbox, const PackageSource &package, const PackageLoadOptions &options)
std::map< std::string, std::map< std::string, ScriptValue > > Contribution
std::expected< std::vector< std::string >, std::string > ReadNameList(const ScriptValue &list)
constexpr std::string_view MANIFEST_FILE
std::expected< std::string, std::string > ModulePath(std::string_view name)
std::expected< Contribution, std::string > ReadContribution(const std::vector< ScriptValue > &values)
std::string LoadOne(LuauSandbox &sandbox, const PackageSource &package, const Manifest &manifest, const std::set< std::string > &loaded, const PackageLoadOptions &options, PackageSetResult &result)
constexpr std::string_view MODULE_EXTENSION
std::string ToUtf8String(const fs::path &path)
constexpr size_t MAX_LISTED_CONFLICTS
constexpr size_t MAX_NAME_LENGTH
void MergeEntry(PackageSetResult &result, const std::string &category, const std::string &key, ScriptValue &&value, const std::string &package, MergeMode mode)
std::string FindForbiddenOverrides(const PackageSetResult &result, const Contribution &contribution)
bool OrderStep(std::map< std::string, OrderNode > &nodes, std::vector< std::string > &order, PackageSetResult &result)
std::expected< Contribution, std::string > RunEntry(LuauSandbox &sandbox, const PackageSource &package, const Manifest &manifest)
std::string TypeCheckReason(std::vector< TypeDiagnostic > &&diagnostics, PackageSetResult &result)
std::expected< Manifest, std::string > ParseManifest(const ScriptValue &table)
std::string ToDebugText(const ScriptValue &value)
std::expected< PackageSource, std::string > ReadPackageFolder(const fs::path &folder)
void AppendCanonicalBytes(const ScriptValue &value, std::string &out)
std::expected< std::vector< PackageSource >, std::string > ReadPackageRoot(const fs::path &root)
uint64_t HashBytes(std::string_view bytes)
std::expected< std::string, std::string > PackageModulePath(std::string_view name)
std::string SummarizeDiagnostics(const std::vector< TypeDiagnostic > &diagnostics, size_t maxShown)
constexpr int64_t PACKAGE_FORMAT_VERSION
std::expected< PackageSetResult, std::string > LoadPackages(LuauSandbox &sandbox, std::span< const PackageSource > packages, const PackageLoadOptions &options)
void AppendCanonicalString(std::string_view text, std::string &out)
uint64_t TableVersion(const PackageSetResult &result)
std::function< std::expected< ModuleFile, std::string >(std::string_view name)> ModuleResolver
std::string TableBytes(const PackageSetResult &result)
bool IsValidPackageName(std::string_view name)
LuauTypeChecker * typeChecker
std::vector< TypeDiagnostic > typeDiagnostics
std::vector< TableOverride > overrides
std::vector< std::string > loadOrder
std::vector< PackageRejection > rejected
std::map< std::string, std::map< std::string, TableEntry > > tables
std::map< std::string, std::string > files
std::vector< ScriptField > fields
std::vector< std::string > depends