summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2022-06-24 04:34:06 -0700
committerFuwn <[email protected]>2022-06-24 04:34:06 -0700
commit06a2da38c0bffa43c9e8a26b6d98dc95595dc081 (patch)
tree09d1c29f9202b1cb8670fa1f0d6bcf256f8f738c
parentdocs(license): add gpl-3.0 (diff)
downloadcait-06a2da38c0bffa43c9e8a26b6d98dc95595dc081.tar.xz
cait-06a2da38c0bffa43c9e8a26b6d98dc95595dc081.zip
ci: push source to remote
-rw-r--r--.gitignore19
-rw-r--r--CMakeLists.txt33
-rw-r--r--build.ninja40
-rw-r--r--src/cait.cc21
-rw-r--r--src/cli.cc147
-rw-r--r--src/cli.hh46
-rw-r--r--src/context.cc86
-rw-r--r--src/context.hh47
-rw-r--r--src/help.cc59
-rw-r--r--src/help.hh35
-rw-r--r--src/lexer.cc69
-rw-r--r--src/lexer.hh44
-rw-r--r--src/node.cc165
-rw-r--r--src/node.hh137
-rw-r--r--src/parser.cc364
-rw-r--r--src/parser.hh90
-rw-r--r--src/token.cc102
-rw-r--r--src/token.hh152
-rw-r--r--src/utility.hh38
19 files changed, 1694 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5ea87b1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+# Tup
+.tup
+
+# Artifacts
+out
+build
+
+# Visual Studio Code
+.vscode
+.cache
+
+# Ninja
+.ninja_*
+
+# CLion
+.idea
+
+# CMake
+cmake-*
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c348f88
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.15)
+
+project(cait)
+
+string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER)
+
+set(CMAKE_CXX_EXTENSIONS OFF)
+set(CMAKE_CXX_STANDARD 23)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+option(${PROJECT_NAME_UPPER}_BUILD_BINARY "Build ${PROJECT_NAME} binary" ON)
+
+if(MSVC)
+ add_compile_options(/Wall)
+
+ add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
+else()
+ add_compile_options(-Weverything -Wno-c++98-compat)
+endif(MSVC)
+
+file(GLOB LIB${PROJECT_NAME_UPPER}_SOURCES ${PROJECT_SOURCE_DIR}/src/*.cc)
+
+list(REMOVE_ITEM LIB${PROJECT_NAME_UPPER}_SOURCES ${PROJECT_SOURCE_DIR}/src/${PROJECT_NAME}.cc)
+
+add_library(lib${PROJECT_NAME} OBJECT ${LIB${PROJECT_NAME_UPPER}_SOURCES})
+
+if(${PROJECT_NAME_UPPER}_BUILD_BINARY)
+ add_executable(${PROJECT_NAME} src/${PROJECT_NAME}.cc)
+
+ target_link_libraries(${PROJECT_NAME} PRIVATE lib${PROJECT_NAME})
+
+ install(TARGETS ${PROJECT_NAME})
+endif(${PROJECT_NAME_UPPER}_BUILD_BINARY)
diff --git a/build.ninja b/build.ninja
new file mode 100644
index 0000000..5134409
--- /dev/null
+++ b/build.ninja
@@ -0,0 +1,40 @@
+cc = clang++
+cxxflags = -Isrc -O3 -std=c++20 -Weverything -Wno-c++98-compat
+out_dir = out
+name = cait
+src_dir = src
+out_ext = .exe
+obj_ext = .o
+cc_ext = .cc
+
+rule cc
+ command = $cc $cxxflags -c $in -o $out
+rule clang_format
+ command = clang-format -i -style=LLVM $src_dir/*$cc_ext $src_dir/*.hh
+rule clang_tidy
+ command = run-clang-tidy -checks="*" -j 8 files $src_dir -p=out
+rule link
+ command = $cc $in -o $out
+
+build $out_dir/$name$obj_ext: cc $src_dir/$name$cc_ext
+build $out_dir/cli$obj_ext: cc $src_dir/cli$cc_ext
+build $out_dir/context$obj_ext: cc $src_dir/context$cc_ext
+build $out_dir/help$obj_ext: cc $src_dir/help$cc_ext
+build $out_dir/lexer$obj_ext: cc $src_dir/lexer$cc_ext
+build $out_dir/node$obj_ext: cc $src_dir/node$cc_ext
+build $out_dir/parser$obj_ext: cc $src_dir/parser$cc_ext
+build $out_dir/token$obj_ext: cc $src_dir/token$cc_ext
+build $out_dir/$name$out_ext: link $out_dir/$name$obj_ext $
+ $out_dir/cli$obj_ext $
+ $out_dir/context$obj_ext $
+ $out_dir/help$obj_ext $
+ $out_dir/lexer$obj_ext $
+ $out_dir/node$obj_ext $
+ $out_dir/parser$obj_ext $
+ $out_dir/token$obj_ext
+build _format: clang_format
+build format: phony _format
+build _tidy: clang_tidy
+build tidy: phony _tidy
+
+default format $out_dir/$name$out_ext
diff --git a/src/cait.cc b/src/cait.cc
new file mode 100644
index 0000000..623c781
--- /dev/null
+++ b/src/cait.cc
@@ -0,0 +1,21 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "cli.hh"
+
+auto main(int argc, char **argv) -> int { return cait::cli(argc, argv).look(); }
diff --git a/src/cli.cc b/src/cli.cc
new file mode 100644
index 0000000..d74e327
--- /dev/null
+++ b/src/cli.cc
@@ -0,0 +1,147 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include <fstream>
+
+#include "cli.hh"
+#include "context.hh"
+#include "help.hh"
+#include "lexer.hh"
+#include "parser.hh"
+#include "utility.hh"
+
+namespace cait {
+
+cli::cli(int argc, char **argv) : _argc(argc), _argv(argv) {
+ for (int i = 1; i < _argc; i++) {
+ if (_argv[i][0] == '-') {
+ std::string key = _argv[i];
+ std::string value;
+
+ if (i + 1 < _argc) {
+ value = _argv[i + 1];
+
+ i += 1;
+ }
+
+ options[key] = value.empty() ? std::nullopt : std::optional(value);
+ }
+ }
+}
+
+// auto cli::argc() const -> int { return this->_argc; }
+
+// auto cli::argv() const -> char ** { return this->_argv; }
+
+auto cli::arg(int i) -> std::optional<std::string> {
+ if (i < this->_argc) {
+ return std::string(this->_argv[i]);
+ } else {
+ return std::nullopt;
+ }
+}
+
+auto cli::option(const std::string &key)
+ -> std::optional<std::optional<std::string>> {
+ if (this->options.contains(key)) {
+ return this->options[key];
+ } else {
+ return std::nullopt;
+ }
+}
+
+auto cli::look() -> int {
+ switch (cait::utility::hash(this->arg(1).value_or("").c_str())) {
+ case cait::utility::hash("--help"):
+ case cait::utility::hash("-h"): {
+ std::cout << cait::help_message() << '\n';
+ } break;
+ case cait::utility::hash("--version"): {
+ std::cout << VERSION << '\n';
+ } break;
+ default: {
+ std::string cait_file_name = this->option("-f")->value_or("build.cait");
+ std::ifstream cait_file(cait_file_name, std::ios::in);
+
+ if (cait_file.is_open()) {
+ lexer lexer(cait_file);
+ parser parser(lexer.tree());
+
+ try {
+ parser.generate_nodes(cait_file_name, lexer.lines());
+ } catch (const parser_exception &e) {
+ std::cout << e.what() << '\n';
+
+ return 1;
+ }
+
+ context context(parser);
+
+ for (const auto &token_line : parser.tree()) {
+ for (const auto &token : token_line) {
+ std::cout << token.word << "(" << token.type.string() << ")";
+ }
+
+ std::cout << "\n\n";
+ }
+
+ for (auto &token_line : parser.nodes()) {
+ std::visit(
+ [](const auto &node) -> void {
+ std::cout << node.string() << '\n';
+ },
+ token_line);
+ }
+
+ // auto cc = context.rule("cc");
+
+ // if (cc.has_value()) {
+ // std::cout << "cc: '" << cc.value().string() << "'\n";
+ // } else {
+ // std::cout << "cc has no value\n";
+ // }
+
+ return 0;
+ }
+
+ std::cout << "cait: error: loading '" << cait_file_name
+ << "': The system cannot find "
+ "the file specified.\n\n";
+
+ return 1;
+ } // break;
+ }
+
+ // auto option = this->option("-h");
+
+ // if (option.has_value()) {
+ // std::cout << "option exists ";
+
+ // if (option->has_value()) {
+ // std::cout << "with value " << option->value() << std::endl;
+ // } else {
+ // std::cout << "with no value\n";
+ // }
+ // } else {
+ // std::cout << "option does not exist\n";
+ // }
+
+ return 0;
+}
+
+} // namespace cait
diff --git a/src/cli.hh b/src/cli.hh
new file mode 100644
index 0000000..1181db2
--- /dev/null
+++ b/src/cli.hh
@@ -0,0 +1,46 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef CLI_HH
+#define CLI_HH
+
+#include <iostream>
+#include <map>
+#include <optional>
+#include <string>
+
+namespace cait {
+
+class cli {
+private:
+ int _argc;
+ char **_argv;
+ std::map<std::string, std::optional<std::string>> options;
+
+public:
+ cli(int, char **);
+ // auto argc() const -> int;
+ // auto argv() const -> char **;
+ auto arg(int) -> std::optional<std::string>;
+ auto option(const std::string &) -> std::optional<std::optional<std::string>>;
+ auto look() -> int;
+};
+
+} // namespace cait
+
+#endif // CLI_HH
diff --git a/src/context.cc b/src/context.cc
new file mode 100644
index 0000000..1c1dce2
--- /dev/null
+++ b/src/context.cc
@@ -0,0 +1,86 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include <iostream>
+#include <optional>
+#include <thread>
+
+#include "context.hh"
+
+namespace cait {
+
+context::context(const parser &parser) {
+ this->jobs = std::thread::hardware_concurrency() + 2;
+
+ for (auto &parse_node : parser.nodes()) {
+ std::visit(
+ [this](auto &node) {
+ using T = std::decay_t<decltype(node)>;
+
+ if constexpr (std::is_same_v<T, node::variable>) {
+ this->identifiers.insert(
+ {std::make_pair(node.name().word,
+ token_type::variable_declaration),
+ node});
+ } else if constexpr (std::is_same_v<T, node::rule>) {
+ this->identifiers.insert(
+ {std::make_pair(node.name().word, token_type::rule_declaration),
+ node});
+ }
+ },
+ parse_node);
+ }
+}
+
+[[maybe_unused]] auto context::variable(const std::string &variable)
+ -> std::optional<node::variable> {
+ auto *it = std::get_if<node::variable>(&this->identifiers.at(
+ std::make_pair(variable, token_type::variable_declaration)));
+
+ if (it == nullptr) {
+ return std::nullopt;
+ } else {
+ return *it;
+ }
+}
+
+[[maybe_unused]] auto context::rule(const std::string &variable)
+ -> std::optional<node::rule> {
+ auto *it = std::get_if<node::rule>(&this->identifiers.at(
+ std::make_pair(variable, token_type::rule_declaration)));
+
+ if (it == nullptr) {
+ return std::nullopt;
+ } else {
+ return *it;
+ }
+}
+
+[[maybe_unused]] auto context::build(const std::string &variable)
+ -> std::optional<node::build> {
+ auto *it = std::get_if<node::build>(&this->identifiers.at(
+ std::make_pair(variable, token_type::build_declaration)));
+
+ if (it == nullptr) {
+ return std::nullopt;
+ } else {
+ return *it;
+ }
+}
+
+} // namespace cait
diff --git a/src/context.hh b/src/context.hh
new file mode 100644
index 0000000..f42555d
--- /dev/null
+++ b/src/context.hh
@@ -0,0 +1,47 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef CONTEXT_HH
+#define CONTEXT_HH
+
+#include <map>
+#include <optional>
+#include <string>
+
+#include "parser.hh"
+#include "token.hh"
+
+namespace cait {
+
+class context {
+private:
+ std::map<std::pair<std::string, token_type>, node::node_set> identifiers;
+ [[maybe_unused]] std::size_t jobs;
+
+public:
+ explicit context(const parser &);
+ [[maybe_unused]] auto variable(const std::string &)
+ -> std::optional<node::variable>;
+ [[maybe_unused]] auto rule(const std::string &) -> std::optional<node::rule>;
+ [[maybe_unused]] auto build(const std::string &)
+ -> std::optional<node::build>;
+};
+
+} // namespace cait
+
+#endif // CONTEXT_HH
diff --git a/src/help.cc b/src/help.cc
new file mode 100644
index 0000000..18853e1
--- /dev/null
+++ b/src/help.cc
@@ -0,0 +1,59 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include <thread>
+
+#include "help.hh"
+
+namespace cait {
+
+auto help_message() -> std::string {
+ std::ostringstream help;
+
+ help
+ << "usage: cait [options] [targets...]\n"
+ "\n"
+ "if targets are unspecified, builds the 'default' target (see "
+ "manual).\n"
+ "\n"
+ "options:\n"
+ " --version print cait version (\"" VERSION "\")\n"
+ " -v, --verbose show all command lines while building\n"
+ "\n"
+ " -C DIR change to DIR before doing anything else\n"
+ " -f FILE specify input build file [default=build.cait]\n"
+ "\n"
+ " -j N run N jobs in parallel (0 means infinity) [default="
+ << std::thread::hardware_concurrency() + 2
+ << " on this system]\n"
+ " -k N keep going until N jobs fail (0 means infinity) "
+ "[default=1]\n"
+ " -l N do not start new jobs if the load average is greater "
+ "than N\n"
+ " -n dry run (don't run commands but act like they succeeded)\n"
+ "\n"
+ " -d MODE enable debugging (use '-d list' to list modes)\n"
+ " -t TOOL run a subtool (use '-t list' to list subtools)\n"
+ " terminates toplevel options; further flags are passed to the "
+ "tool\n"
+ " -w FLAG adjust warnings (use '-w list' to list warnings)";
+
+ return help.str();
+}
+
+} // namespace cait
diff --git a/src/help.hh b/src/help.hh
new file mode 100644
index 0000000..37afb58
--- /dev/null
+++ b/src/help.hh
@@ -0,0 +1,35 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef HELP_HH
+#define HELP_HH
+
+#include <sstream>
+
+#define VERSION_MAJOR "0"
+#define VERSION_MINOR "1"
+#define VERSION_PATCH "0"
+#define VERSION VERSION_MAJOR "." VERSION_MINOR "." VERSION_PATCH
+
+namespace cait {
+
+auto help_message() -> std::string;
+
+} // namespace cait
+
+#endif // HELP_HH
diff --git a/src/lexer.cc b/src/lexer.cc
new file mode 100644
index 0000000..1100a2e
--- /dev/null
+++ b/src/lexer.cc
@@ -0,0 +1,69 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "lexer.hh"
+
+namespace cait {
+
+lexer::lexer(std::ifstream &file) {
+ std::string line;
+ std::size_t line_number = 1;
+
+ for (;;) {
+ while (std::getline(file, line)) {
+ std::vector<token> token_line;
+
+ this->_lines.push_back(line);
+
+ start:
+ std::istringstream line_stream(line);
+ std::string word;
+ std::size_t character = 1;
+
+ while (std::getline(line_stream, word, ' ')) {
+ if (word.empty()) {
+ continue;
+ }
+
+ token_line.emplace_back(line_number, character, character + word.size(),
+ word);
+
+ character += word.size() + 1;
+ }
+
+ if (word == "$") {
+ token_line.pop_back();
+ std::getline(file, line);
+
+ goto start;
+ }
+
+ this->_tree.push_back(token_line);
+
+ line_number += 1;
+ }
+
+ break;
+ }
+}
+
+auto lexer::tree() -> token_tree & { return this->_tree; }
+
+auto lexer::lines() -> const std::vector<std::string> & { return this->_lines; }
+
+} // namespace cait
diff --git a/src/lexer.hh b/src/lexer.hh
new file mode 100644
index 0000000..ffe3a33
--- /dev/null
+++ b/src/lexer.hh
@@ -0,0 +1,44 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef LEXER_HH
+#define LEXER_HH
+
+#include <cstdint>
+#include <fstream>
+#include <sstream>
+#include <string>
+
+#include "token.hh"
+
+namespace cait {
+
+class lexer {
+private:
+ token_tree _tree;
+ std::vector<std::string> _lines;
+
+public:
+ explicit lexer(std::ifstream &);
+ auto tree() -> token_tree &;
+ auto lines() -> const std::vector<std::string> &;
+};
+
+} // namespace cait
+
+#endif // LEXER_HH
diff --git a/src/node.cc b/src/node.cc
new file mode 100644
index 0000000..9b76c27
--- /dev/null
+++ b/src/node.cc
@@ -0,0 +1,165 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include <iostream>
+
+#include "node.hh"
+
+namespace cait::node {
+
+variable::variable(const token &variable_name,
+ std::vector<token> variable_value_list)
+ : _name(variable_name), _value_list(std::move(variable_value_list)) {}
+
+[[nodiscard]] auto variable::string() const -> std::string {
+ std::ostringstream values;
+
+ for (const auto &command : this->_value_list) {
+ values << command.word << " ";
+ }
+
+ return this->_name.word + " = " + values.str();
+}
+
+[[nodiscard]] auto variable::name() const noexcept -> const token & {
+ return this->_name;
+}
+
+[[maybe_unused]] [[nodiscard]] auto variable::value_list() const noexcept
+ -> const std::vector<token> & {
+ return this->_value_list;
+}
+
+rule::rule(const token &rule_name, item_map_type rule_item_map)
+ : _name(rule_name), _item_map(std::move(rule_item_map)) {}
+
+[[nodiscard]] auto rule::string() const -> std::string {
+ std::ostringstream values;
+
+ for (const auto &item : this->_item_map) {
+ values << " " << item.first << " = ";
+
+ for (const auto &command : item.second) {
+ values << command.word << " ";
+ }
+
+ values << '\n';
+ }
+
+ // Clip off the last newline
+ return "rule " + this->_name.word + "\n" +
+ values.str().substr(0, values.str().size() - 1);
+}
+
+[[nodiscard]] auto rule::name() const noexcept -> const token & {
+ return this->_name;
+}
+
+[[maybe_unused]] [[nodiscard]] auto rule::item_map() const noexcept
+ -> const item_map_type & {
+ return this->_item_map;
+}
+
+pool::pool(const token &pool_name, const token &pool_depth)
+ : _name(pool_name), _depth(pool_depth) {}
+
+[[nodiscard]] auto pool::string() const -> std::string {
+ return "pool " + this->_name.word + "\n depth = " + this->_depth.word;
+}
+
+[[nodiscard]] auto pool::name() const noexcept -> const token & {
+ return this->_name;
+}
+
+[[maybe_unused]] [[nodiscard]] auto pool::depth() const noexcept
+ -> const token & {
+ return this->_depth;
+}
+
+build::build(const token &build_output, const token &build_rule,
+ std::vector<token> build_inputs)
+ : _output(build_output), _rule(build_rule),
+ _inputs(std::move(build_inputs)) {}
+
+[[nodiscard]] auto build::string() const -> std::string {
+ std::ostringstream values;
+
+ for (const auto &command : this->_inputs) {
+ values << command.word << " ";
+ }
+
+ return "build " + this->_output.word + ": " + this->_rule.word + ' ' +
+ values.str();
+}
+
+[[maybe_unused]] [[nodiscard]] auto build::output() const noexcept
+ -> const token & {
+ return this->_output;
+}
+
+[[maybe_unused]] [[nodiscard]] auto build::rule() const noexcept
+ -> const token & {
+ return this->_rule;
+}
+
+[[maybe_unused]] [[nodiscard]] auto build::inputs() const noexcept
+ -> const std::vector<token> & {
+ return this->_inputs;
+}
+
+default_::default_(std::vector<token> default_targets)
+ : _targets(std::move(default_targets)) {}
+
+[[nodiscard]] auto default_::string() const -> std::string {
+ std::ostringstream values;
+
+ for (const auto &target : this->_targets) {
+ values << target.word << " ";
+ }
+
+ return "default " + values.str();
+}
+
+[[maybe_unused]] [[nodiscard]] auto default_::targets() const noexcept
+ -> const std::vector<token> & {
+ return this->_targets;
+}
+
+include::include(const token &include_path) : _path(include_path) {}
+
+[[nodiscard]] auto include::string() const -> std::string {
+ return "include " + this->_path.word;
+}
+
+[[maybe_unused]] [[nodiscard]] auto include::path() const noexcept
+ -> const token & {
+ return this->_path;
+}
+
+subninja::subninja(const token &subninja_path) : _path(subninja_path) {}
+
+[[nodiscard]] auto subninja::string() const -> std::string {
+ return "subninja " + this->_path.word;
+}
+
+[[maybe_unused]] [[nodiscard]] auto subninja::path() const noexcept
+ -> const token & {
+ return this->_path;
+}
+
+} // namespace cait::node
diff --git a/src/node.hh b/src/node.hh
new file mode 100644
index 0000000..919d013
--- /dev/null
+++ b/src/node.hh
@@ -0,0 +1,137 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef NODE_HH
+#define NODE_HH
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "token.hh"
+
+namespace cait::node {
+
+class variable;
+class rule;
+class build;
+class default_;
+class pool;
+class include;
+class subninja;
+
+using item_map_type = std::map<std::string, std::vector<token>>;
+using node_set =
+ std::variant<rule, build, default_, variable, pool, include, subninja>;
+
+class stringable {
+public:
+ [[nodiscard]] virtual auto string() const -> std::string = 0;
+ virtual ~stringable() = default;
+};
+
+class variable : stringable {
+private:
+ const token &_name;
+ std::vector<token> _value_list;
+
+public:
+ variable(const token &, std::vector<token>);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[nodiscard]] auto name() const noexcept -> const token &;
+ [[maybe_unused]] [[nodiscard]] auto value_list() const noexcept
+ -> const std::vector<token> &;
+};
+
+class rule : stringable {
+private:
+ const token &_name;
+ item_map_type _item_map;
+
+public:
+ rule(const token &, item_map_type);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[nodiscard]] auto name() const noexcept -> const token &;
+ [[maybe_unused]] [[nodiscard]] auto item_map() const noexcept
+ -> const item_map_type &;
+};
+
+class pool : stringable {
+private:
+ const token &_name;
+ const token &_depth;
+
+public:
+ pool(const token &, const token &);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[nodiscard]] auto name() const noexcept -> const token &;
+ [[maybe_unused]] [[nodiscard]] auto depth() const noexcept -> const token &;
+};
+
+class build : stringable {
+private:
+ const token &_output;
+ const token &_rule;
+ std::vector<token> _inputs;
+
+public:
+ build(const token &, const token &, std::vector<token>);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[maybe_unused]] [[nodiscard]] auto output() const noexcept -> const token &;
+ [[maybe_unused]] [[nodiscard]] auto rule() const noexcept -> const token &;
+ [[maybe_unused]] [[nodiscard]] auto inputs() const noexcept
+ -> const std::vector<token> &;
+};
+
+class default_ : stringable {
+private:
+ std::vector<token> _targets;
+
+public:
+ explicit default_(std::vector<token>);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[maybe_unused]] [[nodiscard]] auto targets() const noexcept
+ -> const std::vector<token> &;
+};
+
+class include : stringable {
+private:
+ const token &_path;
+
+public:
+ explicit include(const token &);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[maybe_unused]] [[nodiscard]] auto path() const noexcept -> const token &;
+};
+
+class subninja : stringable {
+private:
+ const token &_path;
+
+public:
+ explicit subninja(const token &);
+ [[nodiscard]] auto string() const -> std::string override;
+ [[maybe_unused]] [[nodiscard]] auto path() const noexcept -> const token &;
+};
+
+} // namespace cait::node
+
+#endif // NODE_HH
diff --git a/src/parser.cc b/src/parser.cc
new file mode 100644
index 0000000..e884287
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,364 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include <iostream>
+
+#include "parser.hh"
+#include "utility.hh"
+
+namespace cait {
+
+token_bundle::token_bundle(std::size_t &_j, token_t &bundle_token,
+ std::vector<token_t> &_parsed_token_line,
+ std::vector<token_t> &_token_line)
+ : j(_j), token(bundle_token), parsed_token_line(_parsed_token_line),
+ token_line(_token_line) {}
+
+template <typename T>
+range_t<T>::range_t(T begin, T end) : _begin(begin), _end(end) {}
+
+template <typename T> T range_t<T>::begin() { return this->_begin; }
+
+template <typename T> T range_t<T>::end() { return this->_end; }
+
+template <typename T> range_t<T> range(T b, T e) { return range_t<T>(b, e); }
+
+parser::parser(token_tree &token_tree) {
+ this->fix_tree(token_tree);
+ this->prune_empty_token_lines();
+}
+
+[[nodiscard]] auto parser::tree() const noexcept -> const token_tree & {
+ return this->_tree;
+}
+
+[[nodiscard]] auto parser::nodes() const noexcept -> const node_tree & {
+ return this->_nodes;
+}
+
+auto parser::token_parse_assignment_operator(token_bundle bundle) -> void {
+ auto &local = bundle.token_line[bundle.j - 1];
+
+ if (local.type != token_type::pool_depth) {
+ local = local.with_type(token_type::variable_declaration);
+ }
+
+ bundle.parsed_token_line.push_back(local);
+ bundle.parsed_token_line.push_back(bundle.token);
+
+ for (auto &token_line_item :
+ range(bundle.token_line.begin() + 2, bundle.token_line.end())) {
+ token_line_item.type = token_type::variable_value;
+ }
+
+ bundle.parsed_token_line.insert(bundle.parsed_token_line.end(),
+ bundle.token_line.begin() + 2,
+ bundle.token_line.end());
+
+ bundle.j += bundle.token_line.size() - 1;
+}
+
+auto parser::token_parse_rule_pool_declaration(token_bundle bundle) -> void {
+ bundle.parsed_token_line.push_back(bundle.token);
+ bundle.parsed_token_line.push_back(
+ bundle.token_line[bundle.j + 1].with_type(token_type::identifier));
+
+ bundle.j += bundle.token_line.size();
+}
+
+auto parser::token_parse_default_declaration(token_bundle bundle) -> void {
+ bundle.parsed_token_line.push_back(bundle.token);
+
+ for (auto &token_line_item :
+ range(bundle.token_line.begin() + 1, bundle.token_line.end())) {
+ token_line_item.type = token_type::default_target;
+ }
+
+ bundle.parsed_token_line.insert(bundle.parsed_token_line.end(),
+ bundle.token_line.begin() + 1,
+ bundle.token_line.end());
+
+ bundle.j += bundle.token_line.size();
+}
+
+auto parser::token_parse_include_subninja_declaration(token_bundle bundle)
+ -> void {
+ bundle.parsed_token_line.push_back(bundle.token);
+ bundle.parsed_token_line.push_back(
+ bundle.token_line[bundle.j + 1].with_type(token_type::string_literal));
+
+ bundle.j += bundle.token_line.size();
+}
+
+auto parser::token_parse_rule_variable(token_bundle bundle) -> void {
+ bundle.parsed_token_line.push_back(bundle.token);
+
+ for (auto &token_line_item :
+ range(bundle.token_line.begin() + 2, bundle.token_line.end())) {
+ token_line_item.type = token_type::variable_value;
+ }
+
+ bundle.parsed_token_line.insert(bundle.parsed_token_line.end(),
+ bundle.token_line.begin() + 1,
+ bundle.token_line.end());
+
+ bundle.j += bundle.token_line.size() - 1;
+}
+
+auto parser::token_parse_build_declaration(token_bundle bundle) -> void {
+ bundle.parsed_token_line.push_back(bundle.token);
+
+ {
+ auto &output_file_token = bundle.token_line[bundle.j + 1];
+
+ output_file_token.word =
+ output_file_token.word.substr(0, output_file_token.word.size() - 1);
+
+ bundle.parsed_token_line.push_back(
+ output_file_token.with_type(token_type::build_output));
+
+ // We can actually just throw away the colon...
+ // parsed_token_line.push_back(
+ // cait::token(output_file_token.line, output_file_token.begin +
+ // 1,
+ // output_file_token.end + 1, ":")
+ // .with_type(token_type::colon));
+ }
+
+ bundle.token_line[2].type = token_type::build_rule;
+
+ for (auto &token_line_item :
+ range(bundle.token_line.begin() + 3, bundle.token_line.end())) {
+ token_line_item.type = token_type::build_input;
+ }
+
+ bundle.parsed_token_line.insert(bundle.parsed_token_line.end(),
+ bundle.token_line.begin() + 2,
+ bundle.token_line.end());
+
+ bundle.j += bundle.token_line.size();
+}
+
+auto parser::fix_tree(token_tree &token_tree) -> void {
+ for (auto &token_line : token_tree) {
+ std::vector<token> parsed_token_line;
+
+ for (std::size_t j = 0; j < token_line.size(); ++j) {
+ auto &token = token_line[j];
+
+ switch (token.type) {
+ case token_type::assignment_operator: {
+ this->token_parse_assignment_operator(
+ token_bundle(j, token, parsed_token_line, token_line));
+ } break;
+ case token_type::rule_declaration:
+ case token_type::pool_declaration: {
+ this->token_parse_rule_pool_declaration(
+ token_bundle(j, token, parsed_token_line, token_line));
+ } break;
+ case token_type::default_declaration: {
+ this->token_parse_default_declaration(
+ token_bundle(j, token, parsed_token_line, token_line));
+ } break;
+ case token_type::include_declaration:
+ case token_type::subninja_declaration: {
+ this->token_parse_include_subninja_declaration(
+ token_bundle(j, token, parsed_token_line, token_line));
+ } break;
+ case token_type::rule_command:
+ case token_type::rule_depfile:
+ case token_type::rule_deps:
+ case token_type::rule_msvc_deps_prefix:
+ case token_type::rule_description:
+ case token_type::rule_dyndep:
+ case token_type::rule_generator:
+ case token_type::rule_in:
+ case token_type::rule_in_newline:
+ case token_type::rule_out:
+ case token_type::rule_restat:
+ case token_type::rule_rspfile:
+ case token_type::rule_rspfile_content: {
+ this->token_parse_rule_variable(
+ token_bundle(j, token, parsed_token_line, token_line));
+ } break;
+ case token_type::build_declaration: {
+ this->token_parse_build_declaration(
+ token_bundle(j, token, parsed_token_line, token_line));
+ } break;
+ case token_type::string_literal:
+ case token_type::variable_declaration:
+ case token_type::identifier:
+ case token_type::build_output:
+ case token_type::build_input:
+ case token_type::build_rule:
+ case token_type::default_target:
+ case token_type::variable_value:
+ case token_type::pool_depth:
+ break;
+ }
+
+ this->_tree.push_back(parsed_token_line);
+ }
+ }
+}
+
+auto parser::generate_nodes(const std::string &file_name,
+ const std::vector<std::string> &lines) -> void {
+ for (std::size_t i = 0; i < this->_tree.size(); ++i) {
+ auto &token_line = this->_tree[i];
+
+ for (std::size_t j = 0; j < token_line.size(); ++j) {
+ auto &token = token_line[j];
+
+ switch (token.type) {
+ case token_type::variable_declaration: {
+ this->_nodes.push_back(
+ node::variable(token_line[0], std::vector(token_line.begin() + 2,
+ token_line.end())));
+
+ j += token_line.size();
+ } break;
+ case token_type::default_declaration: {
+ this->_nodes.push_back(node::default_(
+ std::vector(token_line.begin() + 1, token_line.end())));
+ } break;
+ case token_type::include_declaration: {
+ this->_nodes.push_back(node::include(token_line[1]));
+ } break;
+ case token_type::subninja_declaration: {
+ this->_nodes.push_back(node::subninja(token_line[1]));
+ } break;
+ case token_type::build_declaration: {
+ this->_nodes.push_back(
+ node::build(token_line[1], token_line[2],
+ std::vector(token_line.begin() + 3, token_line.end())));
+ } break;
+ case token_type::rule_declaration: {
+ auto command_line_index = i + 1;
+ auto &command_line = this->_tree[command_line_index];
+ node::item_map_type item_map;
+
+ while (command_line[0].type == token_type::rule_command ||
+ command_line[0].type == token_type::rule_description ||
+ command_line[0].type == token_type::rule_depfile ||
+ command_line[0].type == token_type::rule_deps ||
+ command_line[0].type == token_type::rule_msvc_deps_prefix ||
+ command_line[0].type == token_type::rule_dyndep ||
+ command_line[0].type == token_type::rule_generator ||
+ command_line[0].type == token_type::rule_in ||
+ command_line[0].type == token_type::rule_in_newline ||
+ command_line[0].type == token_type::rule_out ||
+ command_line[0].type == token_type::rule_restat ||
+ command_line[0].type == token_type::rule_rspfile ||
+ command_line[0].type == token_type::rule_rspfile_content) {
+ item_map.insert(std::make_pair(
+ command_line[0].word,
+ std::vector(command_line.begin() + 2, command_line.end())));
+
+ command_line_index += 1;
+ command_line = this->_tree[command_line_index];
+ }
+
+ // Handle unknown rule variable error
+ if (command_line[1].type == token_type::assignment_operator) {
+ std::ostringstream error;
+
+ error << "cait: error: " << file_name << ":" << command_line[0].line
+ << ": unexpected variable '" << command_line[0].word << "'\n"
+ << lines[command_line[0].line - 1] << "\n";
+
+ for (auto &word : command_line) {
+ error << std::string(word.word.size(), ' ');
+ }
+
+ auto leading_spaces = [](const std::string &string) -> std::size_t {
+ std::size_t count = 0;
+
+ while (string[count] == ' ') {
+ count += 1;
+ }
+
+ return count;
+ };
+
+ error << std::string(leading_spaces(lines[command_line[0].line - 1]),
+ ' ')
+ << " ^ near here\n";
+
+ throw parser_exception(error.str());
+ }
+
+ // Handle no command line error
+ if (item_map.count("command") == 0) {
+ std::ostringstream error;
+
+ error << "cait: error: " << file_name << ":" << command_line[0].line
+ << ": expected 'command =' line\n";
+
+ throw parser_exception(error.str());
+ }
+
+ this->_nodes.push_back(node::rule(token_line[1], item_map));
+
+ i += 1;
+ } break;
+ case token_type::pool_declaration: {
+ this->_nodes.push_back(
+ node::pool(token_line[1], this->_tree[i + 1][2]));
+
+ i += 1;
+ } break;
+ case token_type::string_literal:
+ std::cout << "string: " << token.word << "\n";
+ break;
+ case token_type::rule_command:
+ case token_type::assignment_operator:
+ case token_type::identifier:
+ case token_type::build_output:
+ case token_type::rule_depfile:
+ case token_type::rule_deps:
+ case token_type::rule_msvc_deps_prefix:
+ case token_type::rule_description:
+ case token_type::rule_dyndep:
+ case token_type::rule_generator:
+ case token_type::rule_in:
+ case token_type::rule_in_newline:
+ case token_type::rule_out:
+ case token_type::rule_restat:
+ case token_type::rule_rspfile:
+ case token_type::rule_rspfile_content:
+ case token_type::build_input:
+ case token_type::build_rule:
+ case token_type::default_target:
+ case token_type::variable_value:
+ case token_type::pool_depth:
+ break;
+ }
+ }
+ }
+}
+
+auto parser::prune_empty_token_lines() -> void {
+ for (int j = 0; static_cast<size_t>(j) < this->_tree.size(); ++j) {
+ if (this->_tree[static_cast<size_t>(j)].empty()) {
+ this->_tree.erase(this->_tree.begin() + j);
+ }
+ }
+}
+
+} // namespace cait
diff --git a/src/parser.hh b/src/parser.hh
new file mode 100644
index 0000000..f49081e
--- /dev/null
+++ b/src/parser.hh
@@ -0,0 +1,90 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef PARSER_HH
+#define PARSER_HH
+
+#include <exception>
+#include <stdexcept>
+
+#include "lexer.hh"
+#include "node.hh"
+
+namespace cait {
+
+using node_tree = std::vector<node::node_set>;
+
+class parser_exception : public std::runtime_error {
+public:
+ explicit parser_exception(const std::string &message)
+ : std::runtime_error(message) {}
+};
+
+struct token_bundle {
+private:
+ using token_t = token;
+
+public:
+ std::size_t &j;
+ token_t &token;
+ std::vector<token_t> &parsed_token_line;
+ std::vector<token_t> &token_line;
+
+ token_bundle(std::size_t &, token_t &, std::vector<token_t> &,
+ std::vector<token_t> &);
+};
+
+// https://stackoverflow.com/a/25652548/14452787
+template <typename T> struct range_t {
+private:
+ T _begin, _end;
+
+public:
+ range_t(T, T);
+ T begin();
+ T end();
+};
+
+// https://stackoverflow.com/a/25652548/14452787
+template <typename T> range_t<T> range(T, T);
+
+class parser {
+private:
+ token_tree _tree;
+ node_tree _nodes;
+
+ auto fix_tree(token_tree &) -> void;
+ auto prune_empty_token_lines() -> void;
+ static auto token_parse_build_declaration(token_bundle) -> void;
+ static auto token_parse_rule_variable(token_bundle) -> void;
+ static auto token_parse_include_subninja_declaration(token_bundle) -> void;
+ static auto token_parse_default_declaration(token_bundle) -> void;
+ static auto token_parse_rule_pool_declaration(token_bundle) -> void;
+ static auto token_parse_assignment_operator(token_bundle) -> void;
+
+public:
+ explicit parser(token_tree &);
+ [[nodiscard]] auto tree() const noexcept -> const token_tree &;
+ [[nodiscard]] auto nodes() const noexcept -> const node_tree &;
+ auto generate_nodes(const std::string &,
+ const std::vector<std::string> &) noexcept(false) -> void;
+};
+
+} // namespace cait
+
+#endif // PARSER_HH
diff --git a/src/token.cc b/src/token.cc
new file mode 100644
index 0000000..3ec3efd
--- /dev/null
+++ b/src/token.cc
@@ -0,0 +1,102 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "token.hh"
+
+namespace cait {
+
+token::token(std::size_t _line, std::size_t _begin, std::size_t _end,
+ const std::string &_word)
+ : line(_line), begin(_begin), end(_end), word(_word) {
+ switch (utility::hash(_word.c_str())) {
+ case utility::hash("rule"): {
+ this->type = token_type::rule_declaration;
+ } break;
+ case utility::hash("command"): {
+ this->type = token_type::rule_command;
+ } break;
+ case utility::hash("depfile"): {
+ this->type = token_type::rule_depfile;
+ } break;
+ case utility::hash("deps"): {
+ this->type = token_type::rule_deps;
+ } break;
+ case utility::hash("msvc_deps_prefix"): {
+ this->type = token_type::rule_msvc_deps_prefix;
+ } break;
+ case utility::hash("description"): {
+ this->type = token_type::rule_description;
+ } break;
+ case utility::hash("dyndep"): {
+ this->type = token_type::rule_dyndep;
+ } break;
+ case utility::hash("generator"): {
+ this->type = token_type::rule_generator;
+ } break;
+ case utility::hash("in"): {
+ this->type = token_type::rule_in;
+ } break;
+ case utility::hash("in_newline"): {
+ this->type = token_type::rule_in_newline;
+ } break;
+ case utility::hash("out"): {
+ this->type = token_type::rule_out;
+ } break;
+ case utility::hash("restat"): {
+ this->type = token_type::rule_restat;
+ } break;
+ case utility::hash("rspfile"): {
+ this->type = token_type::rule_rspfile;
+ } break;
+ case utility::hash("rspfile_content"): {
+ this->type = token_type::rule_rspfile_content;
+ } break;
+ case utility::hash("depth"): {
+ this->type = token_type::pool_depth;
+ } break;
+ case utility::hash("build"): {
+ this->type = token_type::build_declaration;
+ } break;
+ case utility::hash("default"): {
+ this->type = token_type::default_declaration;
+ } break;
+ case utility::hash("pool"): {
+ this->type = token_type::pool_declaration;
+ } break;
+ case utility::hash("subninja"): {
+ this->type = token_type::subninja_declaration;
+ } break;
+ case utility::hash("include"): {
+ this->type = token_type::include_declaration;
+ } break;
+ case utility::hash("="): {
+ this->type = token_type::assignment_operator;
+ } break;
+ default: {
+ this->type = token_type::string_literal;
+ } break;
+ }
+}
+
+auto token::with_type(token_type _type) -> token {
+ this->type = _type;
+
+ return *this;
+}
+
+} // namespace cait
diff --git a/src/token.hh b/src/token.hh
new file mode 100644
index 0000000..0724bc7
--- /dev/null
+++ b/src/token.hh
@@ -0,0 +1,152 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef TOKEN_HH
+#define TOKEN_HH
+
+#include <string>
+#include <vector>
+
+#include "utility.hh"
+
+namespace cait {
+
+struct token;
+
+using token_tree = std::vector<std::vector<token>>;
+
+// https://stackoverflow.com/a/53284026/14452787
+class token_type {
+public:
+ enum token_type_value {
+ rule_declaration,
+ rule_command,
+ rule_depfile,
+ rule_deps,
+ rule_msvc_deps_prefix,
+ rule_description,
+ rule_dyndep,
+ rule_generator,
+ rule_in,
+ rule_in_newline,
+ rule_out,
+ rule_restat,
+ rule_rspfile,
+ rule_rspfile_content,
+ build_declaration,
+ build_input,
+ build_rule,
+ build_output,
+ default_declaration,
+ default_target,
+ string_literal,
+ variable_declaration,
+ variable_value,
+ assignment_operator,
+ identifier,
+ pool_declaration,
+ pool_depth,
+ subninja_declaration,
+ include_declaration
+ };
+
+private:
+ token_type_value value;
+
+public:
+ token_type() = default;
+ constexpr token_type(token_type_value _value) : value(_value) {}
+ [[nodiscard]] constexpr std::string string() const {
+ switch (this->value) {
+ case rule_declaration:
+ return "rule_declaration";
+ case rule_command:
+ return "rule_command";
+ case rule_depfile:
+ return "rule_depfile";
+ case rule_deps:
+ return "rule_deps";
+ case rule_msvc_deps_prefix:
+ return "rule_msvc_deps_prefix";
+ case rule_description:
+ return "rule_description";
+ case rule_dyndep:
+ return "rule_dyndep";
+ case rule_generator:
+ return "rule_generator";
+ case rule_in:
+ return "rule_in";
+ case rule_in_newline:
+ return "rule_in_newline";
+ case rule_out:
+ return "rule_out";
+ case rule_restat:
+ return "rule_restat";
+ case rule_rspfile:
+ return "rule_rspfile";
+ case rule_rspfile_content:
+ return "rule_rspfile_content";
+ case build_declaration:
+ return "build_declaration";
+ case build_input:
+ return "build_input";
+ case build_rule:
+ return "build_rule";
+ case default_declaration:
+ return "default_declaration";
+ case default_target:
+ return "default_target";
+ case string_literal:
+ return "string_literal";
+ case variable_declaration:
+ return "variable_declaration";
+ case variable_value:
+ return "variable_value";
+ case assignment_operator:
+ return "assignment_operator";
+ case identifier:
+ return "identifier";
+ case build_output:
+ return "build_output";
+ case pool_declaration:
+ return "pool_declaration";
+ case pool_depth:
+ return "pool_depth";
+ case subninja_declaration:
+ return "subninja_declaration";
+ case include_declaration:
+ return "include_declaration";
+ }
+ }
+ constexpr operator token_type_value() const { return this->value; }
+};
+
+struct token {
+ std::size_t line;
+ [[maybe_unused]] std::size_t begin;
+ [[maybe_unused]] std::size_t end;
+ std::string word;
+ token_type type{};
+
+ token(std::size_t, std::size_t, std::size_t, const std::string &);
+ auto with_type(token_type) -> token;
+};
+
+} // namespace cait
+
+#endif // TOKEN_HH
diff --git a/src/utility.hh b/src/utility.hh
new file mode 100644
index 0000000..26a324e
--- /dev/null
+++ b/src/utility.hh
@@ -0,0 +1,38 @@
+// This file is part of Cait <https://github.com/Fuwn/cait>.
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+// Copyright (C) 2022-2022 Fuwn <[email protected]>
+// SPDX-License-Identifier: GPL-3.0-only
+
+#ifndef UTILITY_HH
+#define UTILITY_HH
+
+#include <cstdint>
+
+namespace cait::utility {
+
+// "Hash" a string so that it can be used within a `switch` statement.
+//
+// https://stackoverflow.com/a/46711735/14452787
+constexpr auto hash(const char *data, std::size_t const size = 0) noexcept
+ -> std::uint32_t {
+ return !data[size] ? 5381
+ : (hash(data, size + 1) * 33) ^
+ static_cast<unsigned char>(data[size]);
+}
+
+} // namespace cait::utility
+
+#endif // UTILITY_HH