diff options
| author | Fuwn <[email protected]> | 2022-06-24 04:34:06 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2022-06-24 04:34:06 -0700 |
| commit | 06a2da38c0bffa43c9e8a26b6d98dc95595dc081 (patch) | |
| tree | 09d1c29f9202b1cb8670fa1f0d6bcf256f8f738c /src | |
| parent | docs(license): add gpl-3.0 (diff) | |
| download | cait-06a2da38c0bffa43c9e8a26b6d98dc95595dc081.tar.xz cait-06a2da38c0bffa43c9e8a26b6d98dc95595dc081.zip | |
ci: push source to remote
Diffstat (limited to 'src')
| -rw-r--r-- | src/cait.cc | 21 | ||||
| -rw-r--r-- | src/cli.cc | 147 | ||||
| -rw-r--r-- | src/cli.hh | 46 | ||||
| -rw-r--r-- | src/context.cc | 86 | ||||
| -rw-r--r-- | src/context.hh | 47 | ||||
| -rw-r--r-- | src/help.cc | 59 | ||||
| -rw-r--r-- | src/help.hh | 35 | ||||
| -rw-r--r-- | src/lexer.cc | 69 | ||||
| -rw-r--r-- | src/lexer.hh | 44 | ||||
| -rw-r--r-- | src/node.cc | 165 | ||||
| -rw-r--r-- | src/node.hh | 137 | ||||
| -rw-r--r-- | src/parser.cc | 364 | ||||
| -rw-r--r-- | src/parser.hh | 90 | ||||
| -rw-r--r-- | src/token.cc | 102 | ||||
| -rw-r--r-- | src/token.hh | 152 | ||||
| -rw-r--r-- | src/utility.hh | 38 |
16 files changed, 1602 insertions, 0 deletions
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 |