From e6696efd52ff9c2203c83c715dfc6ee3a89e8a16 Mon Sep 17 00:00:00 2001 From: Ada Freya Ahmed Date: Sat, 25 Apr 2026 11:53:54 +0530 Subject: [PATCH] cli: Add lint subcommand --- cli/CMakeLists.txt | 1 + cli/source/main.cpp | 4 +- cli/source/subcommands/lint.cpp | 79 +++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 cli/source/subcommands/lint.cpp diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index 163eded2..09678b83 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -13,6 +13,7 @@ set(PLCLI_SOURCES source/subcommands/run.cpp source/subcommands/docs.cpp source/subcommands/info.cpp + source/subcommands/lint.cpp ) if (LIBPL_BUILD_CLI_AS_EXECUTABLE) diff --git a/cli/source/main.cpp b/cli/source/main.cpp index 985012b6..c5288993 100644 --- a/cli/source/main.cpp +++ b/cli/source/main.cpp @@ -16,6 +16,7 @@ namespace pl::cli { void addRunSubcommand(CLI::App *app); void addDocsSubcommand(CLI::App *app); void addInfoSubcommand(CLI::App *app); + void addLintSubcommand(CLI::App *app); } @@ -30,6 +31,7 @@ namespace pl::cli { sub::addRunSubcommand(&app); sub::addDocsSubcommand(&app); sub::addInfoSubcommand(&app); + sub::addLintSubcommand(&app); // Print help message if not enough arguments were provided if (args.size() == 0) { @@ -76,4 +78,4 @@ namespace pl::cli { return pl::cli::executeCommandLineInterface(args); } -#endif \ No newline at end of file +#endif diff --git a/cli/source/subcommands/lint.cpp b/cli/source/subcommands/lint.cpp new file mode 100644 index 00000000..a4dbc5f4 --- /dev/null +++ b/cli/source/subcommands/lint.cpp @@ -0,0 +1,79 @@ +#include +#include +#include + +#include + +#include +#include +#include + +#include + +namespace pl::cli::sub { + + void addLintSubcommand(CLI::App *app) { + static std::fs::path patternFilePath; + static std::vector includePaths; + static std::vector defines; + + static bool outputJson = false; + + auto subcommand = app->add_subcommand("lint", "Compiles the given pattern"); + + // Add command line arguments + subcommand->add_option("-p,--pattern,PATTERN_FILE", patternFilePath, "Pattern file")->required()->check(CLI::ExistingFile); + subcommand->add_option("-I,--includes", includePaths, "Include file paths")->take_all()->check(CLI::ExistingDirectory); + subcommand->add_option("-D,--define", defines, "Define a preprocessor macro")->take_all(); + subcommand->add_flag("-j,--json", outputJson, "Output JSON instead of text")->default_val(false); + + subcommand->callback([] { + // Open pattern file + wolv::io::File patternFile(patternFilePath, wolv::io::File::Mode::Read); + if (!patternFile.isValid()) { + ::fmt::print("Failed to open file '{}'\n", patternFilePath.string()); + std::exit(EXIT_FAILURE); + } + + // Create and configure Pattern Language runtime + pl::PatternLanguage runtime; + + runtime.setIncludePaths(includePaths); + + for (const auto &define : defines) + runtime.addDefine(define); + + const auto code = patternFile.readString(); + const auto source = wolv::util::toUTF8String(patternFile.getPath()); + auto _ = runtime.parseString(code, source); + + auto compileErrors = runtime.getCompileErrors(); + ::nlohmann::json errors = ::nlohmann::json::array(); + if (compileErrors.size() > 0) { + for (const auto &error : compileErrors) { + if (outputJson) { + ::nlohmann::json obj; + obj["message"] = error.getMessage(); + obj["description"] = error.getDescription(); + + ::nlohmann::json loc; + const auto &location = error.getLocation(); + loc["line"] = location.line; + loc["column"] = location.column; + loc["source"] = location.source->source; + obj["location"] = loc; + + errors.emplace_back(obj); + } else { + ::fmt::print("{}\n", error.format()); + } + } + } + + if(outputJson) { + ::fmt::print("{}\n", errors.dump()); + } + }); + } + +}