diff --git a/src/File.cc b/src/File.cc index a5b2adb..3857b5b 100644 --- a/src/File.cc +++ b/src/File.cc @@ -43,11 +43,11 @@ File::File(const std::filesystem::path& filename, const std::filesystem::path& b source_directory = filename.parent_path(); build_filename = replace_extension(build_directory / source_filename.filename(), "ninja"); - bindings.add(std::make_shared("build_directory", FilenameList{Filename{Filename::Type::BUILD, build_directory.string()}})); - bindings.add(std::make_shared("source_directory", FilenameList{Filename{Filename::Type::SOURCE, source_directory.string()}})); + bindings.add(std::make_shared("build_directory", FilenameList{Filename{Filename::Type::COMPLETE, build_directory.string()}})); + bindings.add(std::make_shared("source_directory", FilenameList{Filename{Filename::Type::COMPLETE, source_directory.string()}})); if (is_top()) { - bindings.add(std::make_shared("top_build_directory", FilenameList{Filename{Filename::Type::BUILD, build_directory.string()}})); - bindings.add(std::make_shared("top_source_directory", FilenameList{Filename{Filename::Type::SOURCE,source_directory.string()}})); + bindings.add(std::make_shared("top_build_directory", FilenameList{Filename{Filename::Type::COMPLETE, top_file()->build_directory.string()}})); + bindings.add(std::make_shared("top_source_directory", FilenameList{Filename{Filename::Type::COMPLETE, top_file()->source_directory.string()}})); } parse(filename); @@ -61,11 +61,11 @@ void File::process() { if (is_top()) { auto bindings = Bindings{}; bindings.add(std::shared_ptr(new TextVariable{"command", Text{std::vector{ - Word{"fast-ninja"}, - Word{" "}, - Word{source_directory} + Word{"fast-ninja", false}, + Word{" ", false}, + Word{source_directory, true} }}})); - bindings.add(std::shared_ptr(new TextVariable{"generator", Text{"1"}})); + bindings.add(std::shared_ptr(new TextVariable{"generator", Text{"1", false}})); rules["fast-ninja"] = Rule(this, "fast-ninja", bindings); auto outputs = std::vector{}; diff --git a/src/File.h b/src/File.h index e3a2723..e95e525 100644 --- a/src/File.h +++ b/src/File.h @@ -60,7 +60,7 @@ class File: public Scope { void create_output() const; const File* next_file() const; - const File* top_file() const; + const File* top_file() const {return top()->as_file();} std::filesystem::path source_directory; std::filesystem::path build_directory; diff --git a/src/Text.cc b/src/Text.cc index df5128b..224946c 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -42,7 +42,7 @@ Text::Text(Tokenizer& tokenizer) { if (token.type == Tokenizer::TokenType::NEWLINE) { break; } - words.emplace_back(token.string()); + words.emplace_back(token.string(), false); } } diff --git a/src/Text.h b/src/Text.h index ede553e..8b5fef0 100644 --- a/src/Text.h +++ b/src/Text.h @@ -53,8 +53,8 @@ class Variable; class Text { public: Text() = default; - Text(Tokenizer& tokenizer); - explicit Text(std::string value): Text{std::vector{Word{std::move(value)}}} {} + explicit Text(Tokenizer& tokenizer); + explicit Text(std::string value, bool escape): Text{std::vector{Word{std::move(value), escape}}} {} explicit Text(std::vector elements): words{std::move(elements)} {} void append(const Text& other) {words.insert(words.end(), other.words.begin(), other.words.end());} diff --git a/src/Word.cc b/src/Word.cc index 7a97e07..e009855 100644 --- a/src/Word.cc +++ b/src/Word.cc @@ -47,7 +47,7 @@ Word::Word(Tokenizer& tokenizer) { if (token.is_variable_refrence()) { if (!string.empty()) { - elements.emplace_back(string); + elements.emplace_back(StringElement{string, true}); string = ""; } elements.emplace_back(VariableReference(token.value)); @@ -57,12 +57,12 @@ Word::Word(Tokenizer& tokenizer) { elements.emplace_back(FilenameWord(tokenizer)); } else { - string += token.value; + string += token.string(); } } if (!string.empty()) { - elements.emplace_back(string); + elements.emplace_back(StringElement{string, true}); } } @@ -82,8 +82,8 @@ void Word::print(std::ostream& stream) const { const FilenameWord* filename_word{}; for (auto& element: elements) { - if (std::holds_alternative(element)) { - *current_string += std::get(element); + if (std::holds_alternative(element)) { + *current_string += std::get(element).string(); } else if (std::holds_alternative(element)) { auto& variable = std::get(element); @@ -143,7 +143,7 @@ void Word::resolve(const ResolveContext& context) { auto& variable_reference = std::get(element); variable_reference.resolve(context); if (variable_reference.is_text_variable()) { - element = variable_reference.variable->string(); + element = StringElement{variable_reference.variable->string(), true}; } } else if (std::holds_alternative(element)) { @@ -153,7 +153,25 @@ void Word::resolve(const ResolveContext& context) { } } + std::ostream& operator<<(std::ostream& stream, const Word& word) { word.print(stream); return stream; } + +std::string Word::StringElement::string() const { + if (!escape || text.find(' ') == std::string::npos) { + return text; + } + + std::string escaped; + for (char c : text) { + if (c == ' ') { + escaped += "$ "; + } + else { + escaped += c; + } + } + return escaped; +} diff --git a/src/Word.h b/src/Word.h index 63c46c4..f1af593 100644 --- a/src/Word.h +++ b/src/Word.h @@ -45,8 +45,8 @@ class FilenameWord; class Word { public: - Word(Tokenizer& tokenizer); - explicit Word(std::string text) {elements.emplace_back(std::move(text));}; + explicit Word(Tokenizer& tokenizer); + explicit Word(std::string text, bool escape) {elements.emplace_back(StringElement(std::move(text), escape));}; explicit Word(VariableReference variable_reference) {elements.emplace_back(variable_reference);} Word() = default; @@ -58,11 +58,19 @@ class Word { void resolve(const ResolveContext& scope); private: - void append_string(std::string string) { elements.emplace_back(std::move(string)); } - void append_variable(std::string string) { elements.emplace_back(VariableReference(std::move(string))); } - void append_filename(FilenameWord filname) {elements.emplace_back(std::move(filname));} + class StringElement { + public: + StringElement(std::string text, bool escape): text{std::move(text)}, escape{escape} {} + StringElement() = default; - std::vector> elements; + [[nodiscard]] std::string string() const; + + private: + std::string text; + bool escape{false}; + }; + + std::vector> elements; }; std::ostream& operator<<(std::ostream& stream, const Word& word); diff --git a/tests/special-characters.test b/tests/special-characters.test new file mode 100644 index 0000000..52158ca --- /dev/null +++ b/tests/special-characters.test @@ -0,0 +1,18 @@ +arguments .. +file build.fninja <> +rule a + command = a rule build = "a$ b" |@ || | @ +end-of-inline-data +file build/build.ninja {} <> +# This file is automatically created by fast-ninja from ../build.fninja +# Do not edit. + +rule a + command = a rule build = "a$ b" |@ || | @ + +rule fast-ninja + command = fast-ninja .. + generator = 1 + +build build.ninja : fast-ninja ../build.fninja +end-of-inline-data diff --git a/tests/special_directories.test b/tests/special_directories.test new file mode 100644 index 0000000..97b20a4 --- /dev/null +++ b/tests/special_directories.test @@ -0,0 +1,37 @@ +arguments .. +file build.fninja <> +rule a + command = a $in + +build test: a $build_directory $source_directory $top_build_directory $top_source_directory + +subninja src/build.fninja +end-of-inline-data +file src/build.fninja <> +build test: a $build_directory $source_directory $top_build_directory $top_source_directory +end-of-inline-data + +file build/build.ninja {} <> +# This file is automatically created by fast-ninja from ../build.fninja +# Do not edit. + +rule a + command = a $in + +rule fast-ninja + command = fast-ninja .. + generator = 1 + +build test : a . .. . .. + +build build.ninja src/build.ninja : fast-ninja ../build.fninja ../src/build.fninja + +subninja src/build.ninja +end-of-inline-data + +file build/src/build.ninja {} <> +# This file is automatically created by fast-ninja from ../src/build.fninja +# Do not edit. + +build src/test : a src ../src . .. +end-of-inline-data