diff --git a/src/Bindings.h b/src/Bindings.h index 76f5d21..c60dd46 100644 --- a/src/Bindings.h +++ b/src/Bindings.h @@ -45,6 +45,7 @@ class Bindings { void process(const File& file); void add(const std::string& name, Variable variable) {variables[name] = std::move(variable);} + [[nodiscard]] auto empty() const {return variables.empty();} auto begin() { return variables.begin(); } auto end() { return variables.end(); } diff --git a/src/Build.cc b/src/Build.cc index 211824c..20528b1 100644 --- a/src/Build.cc +++ b/src/Build.cc @@ -36,6 +36,9 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Build::Build(Tokenizer& tokenizer) { outputs = Text{ tokenizer, Tokenizer::TokenType::COLON }; + for (auto& element: outputs) { + element.type = Text::ElementType::BUILD_FILE; + } inputs = Text{tokenizer, Tokenizer::TokenType::NEWLINE}; bindings = Bindings{ tokenizer }; } diff --git a/src/File.cc b/src/File.cc index b3b7062..d6f248a 100644 --- a/src/File.cc +++ b/src/File.cc @@ -31,27 +31,20 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "File.h" +#include + #include "../foundation/lib/Util.h" +#include "Exception.h" #include "Tokenizer.h" -#include -#include -File::File(const std::filesystem::path& filename, const File* previous) : source_filename{ filename }, build_filename{ replace_extension(filename, "ninja") }, previous{ previous } { - if (previous) { - // TODO: derive build directory relavtie to previous - } - else { - build_directory = "."; - } +File::File(const std::filesystem::path& filename, const std::filesystem::path& build_directory, const File* previous) : source_filename{ filename }, previous{ previous }, build_directory{ build_directory.lexically_normal() } { source_directory = filename.parent_path(); + build_filename = replace_extension(build_directory / source_filename.filename(), "ninja"); parse(filename); - for (const auto& subninja:subninjas) { - auto name = subninja.string(); - - // TODO: derive build directory - subfiles.emplace_back(source_directory / name, this); + for (const auto& subninja : subninjas) { + subfiles.emplace_back(std::make_unique(source_directory / subninja, build_directory / std::filesystem::path(subninja).parent_path(), this)); } } @@ -73,6 +66,10 @@ void File::process() { for (auto& build : builds) { build.process(*this); } + + for (const auto& file: subfiles) { + file->process(); + } } const Rule* File::find_rule(const std::string& name) const { @@ -100,22 +97,47 @@ const Variable* File::find_variable(const std::string& name) const { } void File::create_output() const { - auto stream = std::ofstream(build_filename); + { + std::filesystem::create_directories(build_directory); + auto stream = std::ofstream(build_filename); - if (stream.fail()) { - throw Exception("can't create output '%s'", build_filename.c_str()); - } + if (stream.fail()) { + throw Exception("can't create output '%s'", build_filename.c_str()); + } - // TODO: print header (automatically created) + auto top_file = this; + while (top_file->previous) { + top_file = top_file->previous; + } - bindings.print(stream, ""); + stream << "top_source_directory = " << top_file->source_directory.string() << std::endl; + stream << "source_directory = " << source_directory.string() << std::endl; + stream << "top_build_directory = " << top_file->build_directory.string() << std::endl; + stream << "build_directory = " << build_directory.string() << std::endl; - for (auto& pair : rules) { - pair.second.print(stream); + if (!bindings.empty()) { + stream << std::endl; + bindings.print(stream, ""); + } + + for (auto& pair : rules) { + pair.second.print(stream); + } + + for (auto& build : builds) { + build.print(stream); + } + + if (!subninjas.empty()) { + stream << std::endl; + for (auto& subninja: subninjas) { + stream << "subninja " << replace_extension(subninja, "ninja") << std::endl; + } + } } - for (auto& build: builds) { - build.print(stream); + for (auto& subfile : subfiles) { + subfile->create_output(); } } @@ -213,5 +235,7 @@ void File::parse_rule(Tokenizer& tokenizer) { } void File::parse_subninja(Tokenizer& tokenizer) { - subninjas.emplace_back(tokenizer, Tokenizer::TokenType::NEWLINE); + auto text = Text{tokenizer, Tokenizer::TokenType::NEWLINE}; + + subninjas.emplace_back(text.string()); } diff --git a/src/File.h b/src/File.h index d2d2fdb..a31e47b 100644 --- a/src/File.h +++ b/src/File.h @@ -48,7 +48,7 @@ class Tokenizer; class File { public: File() = default; - explicit File(const std::filesystem::path& filename, const File* previous = nullptr); + explicit File(const std::filesystem::path& filename, const std::filesystem::path& build_directory = ".", const File* previous = nullptr); void process(); [[nodiscard]] bool is_output(const std::string& word) const {return outputs.contains(word);} @@ -57,6 +57,9 @@ class File { void create_output() const; + std::filesystem::path source_directory; + std::filesystem::path build_directory; + private: void parse(const std::filesystem::path& filename); void parse_assignment(Tokenizer& tokenizer, const std::string& variable_name); @@ -66,21 +69,18 @@ class File { void parse_rule(Tokenizer& tokenizer); void parse_subninja(Tokenizer& tokenizer); - std::string source_filename; - std::string build_filename; + std::filesystem::path source_filename; + std::filesystem::path build_filename; const File* previous = nullptr; - std::filesystem::path source_directory; - std::filesystem::path build_directory; - std::unordered_set defaults; std::unordered_set outputs; std::unordered_map rules; std::vector builds; Bindings bindings; - std::vector subninjas; - std::vector subfiles; + std::vector subninjas; + std::vector> subfiles; }; #endif // FILE_H diff --git a/src/Text.cc b/src/Text.cc index ed6e6a4..b256ea9 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -35,6 +35,40 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "File.h" #include +std::string Text::Element::string() const { + if (!is_word() && !is_file()) { + return value; + } + else { + auto result = std::string{}; + auto index = size_t{0}; + + while (index < value.size()) { + const auto special = value.find_first_of("$ \n", index); + if (special != std::string::npos) { + result += value.substr(index, special - index); + result += "$"; + result += value[special]; + index = special + 1; + } + else { + result += value.substr(index); + break; + } + } + + if (is_build_file()) { + return "$build_directory/" + result; + } + else if (is_source_file()) { + return "$source_directory/" + result; + } + else { + return result; + } + } +} + Text::Text(Tokenizer& tokenizer, Tokenizer::TokenType terminator) { tokenizer.skip_space(); while (const auto token = tokenizer.next()) { @@ -44,7 +78,7 @@ Text::Text(Tokenizer& tokenizer, Tokenizer::TokenType terminator) { throw Exception("missing ':'"); } else if (terminator == Tokenizer::TokenType::END_SCOPE) { - emplace_back(" ", false); + emplace_back(ElementType::WHITESPACE, " "); } else { return; @@ -55,7 +89,7 @@ Text::Text(Tokenizer& tokenizer, Tokenizer::TokenType terminator) { return; case Tokenizer::TokenType::VARIABLE_REFERENCE: - emplace_back(token.value, true); + emplace_back(ElementType::VARIABLE, token.value); break; case Tokenizer::TokenType::COLON: @@ -64,7 +98,7 @@ Text::Text(Tokenizer& tokenizer, Tokenizer::TokenType terminator) { } // fallthrough default: - emplace_back(token.string(), false); + emplace_back((token.is_whitespace() ? ElementType::WHITESPACE : ElementType::WORD), token.string()); break; } } @@ -77,7 +111,7 @@ std::ostream& operator<<(std::ostream& stream, const Text& text) { void Text::print(std::ostream& stream) const { for (const auto& element : *this) { - if (element.is_variable) { + if (element.is_variable()) { if (element.variable) { element.variable->print_use(stream); } @@ -86,22 +120,30 @@ void Text::print(std::ostream& stream) const { } } else { - stream << element.value; + stream << element.string(); } } } void Text::process(const File& file) { for (auto& element : *this) { - if (element.is_variable) { + if (element.is_variable()) { element.variable = file.find_variable(element.value); } + else if (element.is_word()) { + if (file.is_output(element.value)) { + element.type = ElementType::BUILD_FILE; + } + else if (std::filesystem::exists(file.source_directory / element.value)) { + element.type = ElementType::SOURCE_FILE; + } + } } } void Text::collect_words(std::unordered_set& words) const { for (auto& element : *this) { - if (element.is_variable) { + if (element.is_variable()) { if (element.variable && element.variable->is_list) { element.variable->value.collect_words(words); } @@ -113,6 +155,11 @@ void Text::collect_words(std::unordered_set& words) const { } std::string Text::string() const { - // TODO: implement - return {}; + auto result = std::string{}; + + for (auto& element: *this) { + result += element.string(); + } + + return result; } diff --git a/src/Text.h b/src/Text.h index 800c47a..0c7f62c 100644 --- a/src/Text.h +++ b/src/Text.h @@ -51,19 +51,33 @@ class Variable; */ class Text { public: + enum class ElementType { + BUILD_FILE, + SOURCE_FILE, + VARIABLE, + WHITESPACE, + WORD + }; class Element { public: - Element(std::string value, bool is_variable) : value{ std::move(value) }, is_variable{ is_variable } {} + Element(ElementType type, std::string value) : type{type}, value{ std::move(value) } {} + + [[nodiscard]] bool is_build_file() const {return type == ElementType::BUILD_FILE;} + [[nodiscard]] bool is_file() const {return is_build_file() || is_source_file();} + [[nodiscard]] bool is_source_file() const {return type == ElementType::SOURCE_FILE;} + [[nodiscard]] bool is_variable() const {return type == ElementType::VARIABLE;} + [[nodiscard]] bool is_word() const {return type == ElementType::WORD;} + [[nodiscard]] std::string string() const; + ElementType type; std::string value; - bool is_variable; - const Variable *variable; + const Variable *variable = nullptr; }; Text() = default; Text(Tokenizer& tokenizer, Tokenizer::TokenType terminator); - void emplace_back(std::string value, bool is_variable = false) { elements.emplace_back(std::move(value), is_variable); } + void emplace_back(ElementType type, std::string value) { elements.emplace_back(type, std::move(value)); } void print(std::ostream& stream) const; void process(const File& file); diff --git a/src/Tokenizer.cc b/src/Tokenizer.cc index 7ea90e6..322dc14 100644 --- a/src/Tokenizer.cc +++ b/src/Tokenizer.cc @@ -61,6 +61,12 @@ std::unordered_map Tokenizer::special_characters }; // clang-format on +Tokenizer::Tokenizer(const std::filesystem::path& filename): source{filename} { + if (source.fail()) { + throw Exception("can't open '%s'", filename.c_str()); + } +} + std::string Tokenizer::Token::string() const { if (type == TokenType::VARIABLE_REFERENCE) { return "$" + value; diff --git a/src/Tokenizer.h b/src/Tokenizer.h index 4597e6a..62c255d 100644 --- a/src/Tokenizer.h +++ b/src/Tokenizer.h @@ -38,7 +38,7 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class Tokenizer { public: - explicit Tokenizer(const std::filesystem::path& filename): source{filename} {} + explicit Tokenizer(const std::filesystem::path& filename); enum class Skip { NONE, @@ -84,6 +84,7 @@ class Tokenizer { Token(TokenType type, std::string value) : type{type}, value{std::move(value)} {} explicit operator bool() const {return type != TokenType::END;} + [[nodiscard]] bool is_whitespace() const {return type == TokenType::SPACE || type == TokenType::NEWLINE;} [[nodiscard]] std::string string() const; [[nodiscard]] std::string type_name() const {return type_name(type);} [[nodiscard]] static std::string type_name(TokenType type); diff --git a/src/fast-ninja.cc b/src/fast-ninja.cc index e75ac4c..f919341 100644 --- a/src/fast-ninja.cc +++ b/src/fast-ninja.cc @@ -53,7 +53,7 @@ class fast_ninja : public Command { private: static std::vector options; - File file; + std::unique_ptr file; }; std::vector fast_ninja::options = {}; @@ -65,12 +65,12 @@ int main(int argc, char* argv[]) { } void fast_ninja::process() { - auto filename = arguments.arguments[0]; + const auto top_source_directory = std::filesystem::path(arguments.arguments[0]); - file = File(filename); - file.process(); + file = std::make_unique(top_source_directory / "build.fninja"); + file->process(); } void fast_ninja::create_output() { - file.create_output(); + file->create_output(); }