diff --git a/Grammar.txt b/Grammar.txt new file mode 100644 index 0000000..ef75602 --- /dev/null +++ b/Grammar.txt @@ -0,0 +1,71 @@ +file: + < statement NEWLINE > + + +statement: + variable-assignment + | rule + | build + | default + | pool + | include + | subninja + +build: + BUILD filename-list ':' IDENTIFIER filename-list NEWLINE [ BEGIN_SCOPE variable-assignments END_SCOPE ] + +rule: + RULE IDENTIFIER NEWLINE BEGIN_SCOPE variable-assignments END_SCOPE + +pool: + POOL IDENTIFIER NEWLINE BEGIN_SCOPE variable-assignments END_SCOPE + +default: + DEFAULT filename-list + +include: + INCLUDE filename-list + +subninja: + SUBNINJA filename-list + +variable-assignments: + < variable-assignment NEWLINE > + + +variable-assignment: + variable '=' variable-text + | single-filename-variable ':=' filename + | multi-filename-variable ':=' [ filename-list ] BEGIN_SCOPE multi-line-filename-list END_SCOPE + +filename-list: + filename + | multi-file-variable + | filename-list SPACE filename + | filename-list SPACE multi-file-variable + +multi-line-filename-list: + filename + | multi-file-variable + | filename-list [SPACE | NEWLINE] filename + | filename-list [SPACE | NEWLINE] multi-file-variable + +filename: + literal-filename + explicit-filename + | single-file-variable + +literal-filename: + < WORD | variable > + + +variable-text: + < variable-text-component > * + +variable-text-component: + WORD + | explicit-filename + | SPACE + | variable + | single-filename-variable + | multi-filename-variable + +explicit-filename: + '{{' < WORD SPACE > + '}}' diff --git a/src/Bindings.cc b/src/Bindings.cc index 3a85ec4..c768249 100644 --- a/src/Bindings.cc +++ b/src/Bindings.cc @@ -31,7 +31,8 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Bindings.h" - +#include "FilenameVariable.h" +#include "TextVariable.h" #include Bindings::Bindings(Tokenizer& tokenizer) { @@ -47,31 +48,30 @@ Bindings::Bindings(Tokenizer& tokenizer) { } auto name = token.string(); token = tokenizer.next(Tokenizer::Skip::SPACE); - if (token.type != Tokenizer::TokenType::ASSIGN && token.type != Tokenizer::TokenType::ASSIGN_LIST) { + if (token.type == Tokenizer::TokenType::ASSIGN) { + variables[name] = std::unique_ptr(new TextVariable{name, tokenizer}); + } + else if (token.type == Tokenizer::TokenType::ASSIGN_LIST) { + variables[name] = std::unique_ptr(new FilenameVariable{name, tokenizer}); + } + else { throw Exception("assignment expected"); } - variables[name] = Variable(name, token.type == Tokenizer::TokenType::ASSIGN_LIST, tokenizer); - } -} - -Bindings::Bindings(const std::vector& variable_list) { - for (auto& variable: variable_list) { - variables[variable.name] = variable; } } void Bindings::print(std::ostream& stream, const std::string& indent) const { for (auto& pair : *this) { - if (!pair.second.is_list) { + if (!pair.second->is_filename()) { stream << indent; - pair.second.print_definition(stream); - stream << std::endl; + pair.second->print_definition(stream); } } } -void Bindings::process(const File& file) { +void Bindings::resolve(const Scope& scope) { + auto context = ResolveContext{scope}; for (auto& pair : variables) { - pair.second.process(file); + pair.second->resolve(context); } } diff --git a/src/Bindings.h b/src/Bindings.h index 6b7cc57..da7440c 100644 --- a/src/Bindings.h +++ b/src/Bindings.h @@ -34,31 +34,30 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include "Tokenizer.h" #include "Variable.h" +class Scope; + class Bindings { public: Bindings() = default; explicit Bindings(Tokenizer& tokenizer); - explicit Bindings(const std::vector& variable_list); void print(std::ostream& stream, const std::string& indent) const; - void process(const File& file); - void add(const std::string& name, Variable variable) {variables[name] = std::move(variable);} + void resolve(const Scope& scope); + void add(std::shared_ptr variable) {variables[variable->name] = std::move(variable);} [[nodiscard]] auto empty() const {return variables.empty();} auto begin() { return variables.begin(); } - auto end() { return variables.end(); } - [[nodiscard]] auto begin() const { return variables.begin(); } - [[nodiscard]] auto end() const { return variables.end(); } auto find(const std::string& name) {return variables.find(name);} - auto find(const std::string& name) const {return variables.find(name);} + [[nodiscard]] auto find(const std::string& name) const {return variables.find(name);} private: - std::map variables; + std::map> variables; }; #endif // BINDINGS_H diff --git a/src/Build.cc b/src/Build.cc index e59fc84..027cd06 100644 --- a/src/Build.cc +++ b/src/Build.cc @@ -34,34 +34,26 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "File.h" -Build::Build(Tokenizer& tokenizer) { - outputs = Text{ tokenizer, Tokenizer::TokenType::COLON }; - for (auto& element: outputs) { - if (element.type == Text::ElementType::WORD) { - element.type = Text::ElementType::BUILD_FILE; - } - } +Build::Build(const File* file, Tokenizer& tokenizer): ScopedDirective(file) { + outputs = Dependencies{tokenizer, true}; + tokenizer.expect(Tokenizer::TokenType::COLON, Tokenizer::Skip::SPACE); rule_name = tokenizer.expect(Tokenizer::TokenType::WORD, Tokenizer::Skip::SPACE).string(); - inputs = Text{tokenizer, Tokenizer::TokenType::NEWLINE}; - bindings = Bindings{ tokenizer }; + inputs = Dependencies{tokenizer, false}; + bindings = Bindings{tokenizer}; } +Build::Build(const File* file, std::string rule_name, Dependencies outputs, Dependencies inputs, Bindings bindings): ScopedDirective{file, std::move(bindings)}, rule_name{std::move(rule_name)}, outputs{std::move(outputs)}, inputs{std::move(inputs)} {} + void Build::process(const File& file) { - inputs.process(file); - bindings.process(file); + inputs.resolve(file); + bindings.resolve(file); } void Build::process_outputs(const File& file) { - outputs.process(file); + outputs.resolve(file); } void Build::print(std::ostream& stream) const { stream << std::endl << "build " << outputs << " : " << rule_name << " " << inputs << std::endl; bindings.print(stream, " "); } - -std::unordered_set Build::get_outputs() const { - auto result = std::unordered_set{}; - outputs.collect_words(result); - return result; -} diff --git a/src/Build.h b/src/Build.h index d724a27..b033992 100644 --- a/src/Build.h +++ b/src/Build.h @@ -33,29 +33,28 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define BUILD_H #include -#include #include "Bindings.h" +#include "Dependencies.h" #include "Rule.h" +#include "ScopedDirective.h" -class Build { +class Build: public ScopedDirective { public: - Build() = default; - explicit Build(Tokenizer& tokenizer); - Build(std::string rule_name, Text outputs, Text inputs, Bindings bindings): rule_name{std::move(rule_name)}, outputs{std::move(outputs)}, inputs{std::move(inputs)}, bindings{std::move(bindings)} {} + explicit Build(const File* file, Tokenizer& tokenizer); + Build(const File* file, std::string rule_name, Dependencies outputs, Dependencies inputs, Bindings bindings); void process(const File& file); void process_outputs(const File& file); void print(std::ostream& stream) const; - [[nodiscard]] std::unordered_set get_outputs() const; + void collect_output_files(std::unordered_set& output_files) const {outputs.collect_output_files(output_files);} private: const Rule* rule{}; std::string rule_name; - Text outputs; - Text inputs; - Bindings bindings; + Dependencies outputs; + Dependencies inputs; }; #endif // BUILD_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b49e250..01fe75d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,13 +4,23 @@ ADD_EXECUTABLE(fast-ninja fast-ninja.cc Bindings.cc Build.cc + Dependencies.cc File.cc + Filename.cc + FilenameList.cc + FilenameVariable.cc + FilenameWord.cc Pool.cc + ResolveContext.cc Rule.cc + Scope.cc ScopedDirective.cc Text.cc + TextVariable.cc Tokenizer.cc Variable.cc + VariableReference.cc + Word.cc ) target_include_directories(fast-ninja PRIVATE ${CMAKE_SOURCE_DIR}/foundation/lib ${PROJECT_BINARY_DIR}) target_link_libraries(fast-ninja foundation) diff --git a/src/Dependencies.cc b/src/Dependencies.cc new file mode 100644 index 0000000..be02ca2 --- /dev/null +++ b/src/Dependencies.cc @@ -0,0 +1,124 @@ +/* +Dependencies.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Dependencies.h" + + +#include + +Dependencies::Dependencies(Tokenizer& tokenizer, bool is_build) { + auto type = is_build ? FilenameList::BUILD : FilenameList::INLINE; + + direct = FilenameList{tokenizer, type}; + + while (true) { + tokenizer.skip_space(); + const auto token = tokenizer.next(); + switch (token.type) { + case Tokenizer::TokenType::END: + case Tokenizer::TokenType::NEWLINE: + case Tokenizer::TokenType::COLON: + tokenizer.unget(token); + return; + + case Tokenizer::TokenType::IMPLICIT_DEPENDENCY: + implicit = FilenameList(tokenizer, type); + break; + + case Tokenizer::TokenType::ORDER_DEPENDENCY: + order = FilenameList(tokenizer, type); + break; + + case Tokenizer::TokenType::VALIDATION_DEPENDENCY: + validation = FilenameList(tokenizer, type); + break; + + default: + throw Exception("internal error: %s not included in filename", token.type_name().c_str()); + } + } +} + + +void Dependencies::resolve(const Scope& scope) { + auto context = ResolveContext{scope}; + direct.resolve(context); + implicit.resolve(context); + order.resolve(context); + validation.resolve(context); +} + +void Dependencies::serialize(std::ostream& stream) const { + auto first = true; + if (!direct.empty()) { + stream << direct; + first = false; + } + if (!implicit.empty()) { + if (first) { + first = false; + } + else { + stream << " "; + } + stream << "| " << implicit; + } + if (!order.empty()) { + if (first) { + first = false; + } + else { + stream << " "; + } + stream << "|| " << order; + } + if (!validation.empty()) { + if (first) { + first = false; + } + else { + stream << " "; + } + stream << "|@ " << validation; + } +} + +void Dependencies::collect_output_files(std::unordered_set& output_files) const { + direct.collect_output_files(output_files); + implicit.collect_output_files(output_files); + order.collect_output_files(output_files); + validation.collect_output_files(output_files); +} + +std::ostream& operator<<(std::ostream& stream, const Dependencies& dependencies) { + dependencies.serialize(stream); + return stream; +} diff --git a/src/Dependencies.h b/src/Dependencies.h new file mode 100644 index 0000000..d2be434 --- /dev/null +++ b/src/Dependencies.h @@ -0,0 +1,60 @@ +#ifndef DEPENDENCIES_H +#define DEPENDENCIES_H + +/* +Dependencies.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "Filename.h" +#include "FilenameList.h" +#include "Tokenizer.h" + +class Dependencies { + public: + Dependencies(Tokenizer& tokenizer, bool force_build); + Dependencies(FilenameList direct): direct(std::move(direct)) {} + Dependencies() = default; + + void resolve(const Scope& scope); + void collect_output_files(std::unordered_set& output_files) const; + void mark_as_build(); + void serialize(std::ostream& stream) const; + + private: + FilenameList direct; + FilenameList implicit; + FilenameList order; + FilenameList validation; +}; + +std::ostream& operator<<(std::ostream& stream, const Dependencies& dependencies); +#endif // DEPENDENCIES_H diff --git a/src/File.cc b/src/File.cc index 4daf774..a5b2adb 100644 --- a/src/File.cc +++ b/src/File.cc @@ -35,12 +35,21 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../foundation/lib/Util.h" #include "Exception.h" +#include "FilenameVariable.h" +#include "TextVariable.h" #include "Tokenizer.h" -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() } { +File::File(const std::filesystem::path& filename, const std::filesystem::path& build_directory, const File* next) : Scope(next), source_filename{ filename }, build_directory{ build_directory.lexically_normal() } { 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()}})); + 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()}})); + } + parse(filename); for (const auto& subninja : subninjas) { @@ -50,30 +59,33 @@ File::File(const std::filesystem::path& filename, const std::filesystem::path& b void File::process() { if (is_top()) { - rules["fast-ninja"] = Rule("fast-ninja", Bindings({ - Variable("command", false, Text{std::vector{ - Text::Element{Text::ElementType::WORD, "fast-ninja"}, - Text::Element{Text::ElementType::WHITESPACE, " "}, - Text::Element{Text::ElementType::VARIABLE, "top_source_directory"} - }}), - Variable("generator", false, Text("1")) - })); - auto outputs = Text{}; - auto inputs = Text{}; + auto bindings = Bindings{}; + bindings.add(std::shared_ptr(new TextVariable{"command", Text{std::vector{ + Word{"fast-ninja"}, + Word{" "}, + Word{source_directory} + }}})); + bindings.add(std::shared_ptr(new TextVariable{"generator", Text{"1"}})); + + rules["fast-ninja"] = Rule(this, "fast-ninja", bindings); + auto outputs = std::vector{}; + auto inputs = std::vector{}; add_generator_build(outputs, inputs); - builds.emplace_back("fast-ninja", outputs, inputs, Bindings{}); + builds.emplace_back(this, "fast-ninja", Dependencies{FilenameList{outputs}}, Dependencies{FilenameList{inputs}}, Bindings{}); + } + + auto top_file = const_cast(top()->as_file()); + if (!top_file) { + throw Exception("internal error: top scope is not a file"); } for (auto& build : builds) { build.process_outputs(*this); - auto current_outputs = build.get_outputs(); - outputs.insert(current_outputs.begin(), current_outputs.end()); + build.collect_output_files(top_file->outputs); } - for (auto& pair : bindings) { - pair.second.process(*this); - } + bindings.resolve(*this); for (auto& pair : rules) { pair.second.process(*this); @@ -83,7 +95,8 @@ void File::process() { build.process(*this); } - defaults.process(*this); + auto context = ResolveContext{*this}; + defaults.resolve(context); for (const auto& file : subfiles) { file->process(); @@ -91,7 +104,7 @@ void File::process() { } const Rule* File::find_rule(const std::string& name) const { - for (auto file = this; file; file = file->previous) { + for (auto file = this; file; file = file->next_file()) { const auto& it = file->rules.find(name); if (it != rules.end()) { @@ -103,11 +116,11 @@ const Rule* File::find_rule(const std::string& name) const { } const Variable* File::find_variable(const std::string& name) const { - for (auto file = this; file; file = file->previous) { + for (auto file = this; file; file = file->next_file()) { const auto& it = file->bindings.find(name); if (it != file->bindings.end()) { - return &it->second; + return it->second.get(); } } @@ -122,18 +135,10 @@ void File::create_output() const { throw Exception("can't create output '%s'", build_filename.c_str()); } - auto top_file = this; - while (!top_file->is_top()) { - top_file = top_file->previous; - } - - 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; + stream << "# This file is automatically created by fast-ninja from " << source_filename.string() << std::endl; + stream << "# Do not edit." << std::endl; if (!bindings.empty()) { - stream << std::endl; bindings.print(stream, ""); } @@ -215,13 +220,15 @@ void File::parse(const std::filesystem::path& filename) { case Tokenizer::TokenType::ASSIGN: case Tokenizer::TokenType::ASSIGN_LIST: case Tokenizer::TokenType::BEGIN_SCOPE: + case Tokenizer::TokenType::BEGIN_FILENAME: case Tokenizer::TokenType::COLON: + case Tokenizer::TokenType::END_FILENAME: case Tokenizer::TokenType::END_SCOPE: case Tokenizer::TokenType::IMPLICIT_DEPENDENCY: case Tokenizer::TokenType::ORDER_DEPENDENCY: case Tokenizer::TokenType::VALIDATION_DEPENDENCY: case Tokenizer::TokenType::VARIABLE_REFERENCE: - throw Exception("invalid token"); + throw Exception("unexpected %s",token.type_name().c_str()); } } catch (Exception& ex) { std::cerr << tokenizer.file_name().string() << ":" << tokenizer.current_line_number() << ": " << ex.what() << std::endl; @@ -233,21 +240,23 @@ void File::parse(const std::filesystem::path& filename) { void File::parse_assignment(Tokenizer& tokenizer, const std::string& variable_name) { const auto token = tokenizer.next(Tokenizer::Skip::SPACE); - auto is_list = false; - - if (token.type == Tokenizer::TokenType::ASSIGN_LIST) { - is_list = true; + if (token.type == Tokenizer::TokenType::ASSIGN) { + bindings.add(std::shared_ptr(new TextVariable(variable_name, tokenizer))); + } + else if (token.type == Tokenizer::TokenType::ASSIGN_LIST) { + bindings.add(std::shared_ptr(new FilenameVariable(variable_name, tokenizer))); } - else if (token.type != Tokenizer::TokenType::ASSIGN) { + else { throw Exception("invalid assignment"); } - - bindings.add(variable_name, Variable{ variable_name, is_list, tokenizer }); } -void File::parse_build(Tokenizer& tokenizer) { builds.emplace_back(tokenizer); } +void File::parse_build(Tokenizer& tokenizer) { builds.emplace_back(this, tokenizer); } -void File::parse_default(Tokenizer& tokenizer) { defaults.append(Text(tokenizer, Tokenizer::TokenType::NEWLINE)); } +void File::parse_default(Tokenizer& tokenizer) { + // TODO: append in case of multiple defaults statements + defaults = FilenameList{tokenizer, FilenameList::BUILD}; +} void File::parse_pool(Tokenizer& tokenizer) { tokenizer.skip_space(); @@ -264,29 +273,30 @@ void File::parse_rule(Tokenizer& tokenizer) { if (token.type != Tokenizer::TokenType::WORD) { throw Exception("name expected"); } - rules[token.string()] = Rule(token.string(), tokenizer); + rules[token.string()] = Rule(this, token.string(), tokenizer); } void File::parse_subninja(Tokenizer& tokenizer) { - auto text = Text{ tokenizer, Tokenizer::TokenType::NEWLINE }; + auto text = Text{ tokenizer }; subninjas.emplace_back(text.string()); } -void File::add_generator_build(Text& outputs, Text& inputs) const { - if (!outputs.empty()) { - outputs.emplace_back(Text::ElementType::WHITESPACE, " "); - } - outputs.emplace_back(Text::ElementType::WORD, build_filename.string()); - if (!inputs.empty()) { - inputs.emplace_back(Text::ElementType::WHITESPACE, " "); - } - inputs.emplace_back(Text::ElementType::WORD, source_filename.string()); - for (auto& file: includes) { - inputs.emplace_back(Text::ElementType::WHITESPACE, " "); - inputs.emplace_back(Text::ElementType::WORD, file); - } +void File::add_generator_build(std::vector& outputs, std::vector& inputs) const { + outputs.emplace_back(Filename::Type::BUILD, build_filename); + inputs.emplace_back(Filename::Type::COMPLETE, source_filename); for (const auto& file: subfiles) { file->add_generator_build(outputs, inputs); } } + +const File* File::next_file() const { + if (!next) { + return {}; + } + + if (auto file = dynamic_cast(next)) { + return file; + } + throw Exception("internal error: file contained in non-file scope"); +} \ No newline at end of file diff --git a/src/File.h b/src/File.h index b6f9caf..e3a2723 100644 --- a/src/File.h +++ b/src/File.h @@ -32,7 +32,6 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef FILE_H #define FILE_H -#include "Bindings.h" #include #include #include @@ -42,24 +41,27 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Build.h" #include "Pool.h" #include "Rule.h" +#include "Scope.h" #include "Variable.h" class Tokenizer; -class File { +class File: public Scope { public: File() = default; - explicit File(const std::filesystem::path& filename, const std::filesystem::path& build_directory = ".", const File* previous = nullptr); + explicit File(const std::filesystem::path& filename, const std::filesystem::path& build_directory = ".", const File* next = {}); void process(); - [[nodiscard]] bool is_output(const std::string& word) const {return outputs.contains(word);} - [[nodiscard]] bool is_top() const {return !previous;} + [[nodiscard]] bool is_output(const std::filesystem::path& file) const {return outputs.contains(file.lexically_normal());} [[nodiscard]] const Rule* find_rule(const std::string& name) const; [[nodiscard]] const Variable* find_variable(const std::string& name) const; void create_output() const; + const File* next_file() const; + const File* top_file() const; + std::filesystem::path source_directory; std::filesystem::path build_directory; @@ -72,20 +74,17 @@ class File { void parse_rule(Tokenizer& tokenizer); void parse_subninja(Tokenizer& tokenizer); - void add_generator_build(Text& outputs, Text& inputs) const; + void add_generator_build(std::vector& outputs, std::vector& inputs) const; std::filesystem::path source_filename; std::filesystem::path build_filename; - const File* previous = nullptr; - - std::unordered_set outputs; + std::unordered_set outputs; std::set includes; std::map rules; std::map pools; std::vector builds; - Bindings bindings; - Text defaults; + FilenameList defaults{true}; std::vector subninjas; std::vector> subfiles; }; diff --git a/src/Filename.cc b/src/Filename.cc new file mode 100644 index 0000000..ad1ebfd --- /dev/null +++ b/src/Filename.cc @@ -0,0 +1,83 @@ +/* +FileName.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Filename.h" + + +#include "Exception.h" +#include "File.h" + +void Filename::resolve(const ResolveContext& context) { + const auto file = context.scope.get_file(); + + if (type == Type::UNKNOWN) { + if (context.scope.is_output_file(file->build_directory / name)) { + type = Type::BUILD; + } + if (std::filesystem::exists(file->source_directory / name)) { + type = Type::SOURCE; + } + } + + switch (type) { + case Type::BUILD: + prefix = file->build_directory; + break; + + case Type::COMPLETE: + prefix = ""; + break; + + case Type::SOURCE: + prefix = file->source_directory; + if (!std::filesystem::exists(full_name())) { + throw Exception("source file '" + full_name().string() + "' does not exist"); + } + break; + + case Type::UNKNOWN: + throw Exception("unknown file '" + name + "'"); // TODO: include sub-directory + } +} + +std::filesystem::path Filename::full_name() const { + if (prefix.empty()) { + return std::filesystem::path(name).lexically_normal(); + } + else { + return (prefix / name).lexically_normal(); + } +} + +std::ostream& operator<<(std::ostream& stream, const Filename& file_name) { + stream << file_name.full_name().string(); + return stream; +} diff --git a/src/Filename.h b/src/Filename.h new file mode 100644 index 0000000..14280ec --- /dev/null +++ b/src/Filename.h @@ -0,0 +1,65 @@ +#ifndef FILENAME_H +#define FILENAME_H + +/* +Filename.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "ResolveContext.h" + +class Scope; + +class Filename { +public: + enum class Type { + BUILD, + COMPLETE, + SOURCE, + UNKNOWN + }; + + explicit Filename(std::string name): name{std::move(name)} {} + Filename(Type type, std::string name): type{type}, name{std::move(name)} {} + Filename() = default; + + void resolve(const ResolveContext& context); + + [[nodiscard]] std::filesystem::path full_name() const; + + Type type{Type::UNKNOWN}; + std::string name; + std::filesystem::path prefix; +}; + +std::ostream& operator<<(std::ostream& stream, const Filename& file_name); + +#endif //FILENAME_H diff --git a/src/FilenameList.cc b/src/FilenameList.cc new file mode 100644 index 0000000..09f4232 --- /dev/null +++ b/src/FilenameList.cc @@ -0,0 +1,124 @@ +/* +FilenameList.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "FilenameList.h" + + +#include "Exception.h" +#include "File.h" + + +FilenameList::FilenameList(Tokenizer& tokenizer, Type type) { + auto scoped = (type == Type::SCOPED); + force_build = (type == Type::BUILD); + + while (true) { + auto word = FilenameWord{tokenizer}; + if (!word.empty()) { + words.emplace_back(word); + } + + if (scoped) { + tokenizer.skip_whitespace(); + if (auto token = tokenizer.next()) { + if (token.type == Tokenizer::TokenType::END_SCOPE) { + return; + } + else { + tokenizer.unget(token); + } + } + else { + throw Exception("unterminated scope"); + } + } + else { + if (word.empty()) { + return; + } + } + } +} + +void FilenameList::resolve(const ResolveContext& context) { + for (auto& word : words) { + word.resolve(context); + word.collect_filenames(filenames); + } + for (auto& filename: filenames) { + if (force_build) { + filename.type = Filename::Type::BUILD; + } + filename.resolve(context); + } +} + +void FilenameList::serialize(std::ostream& stream) const { + auto first = true; + for (auto& filename : filenames) { + if (first) { + first = false; + } + else { + stream << " "; + } + stream << filename; + } +} + +std::string FilenameList::string() const { + auto str = std::string(); + auto first = true; + + for (auto& filename: filenames) { + if (first) { + first = false; + } + else { + str += " "; + } + str += filename.full_name().string(); + } + return str; +} + +std::ostream& operator<<(std::ostream& stream, const FilenameList& filename_list) { + filename_list.serialize(stream); + return stream; +} + +void FilenameList::collect_output_files(std::unordered_set& output_files) const { + for (auto& filename: filenames) { + if (filename.type == Filename::Type::BUILD) { + output_files.insert(filename.full_name()); + } + } +} diff --git a/src/FilenameList.h b/src/FilenameList.h new file mode 100644 index 0000000..91fd3bd --- /dev/null +++ b/src/FilenameList.h @@ -0,0 +1,68 @@ +#ifndef FILENAMELIST_H +#define FILENAMELIST_H + +/* +FilenameList.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "Filename.h" +#include "FilenameWord.h" + +class FilenameList { + public: + enum Type { + BUILD, + INLINE, + SCOPED + }; + explicit FilenameList(Tokenizer& tokenizer, Type type); + explicit FilenameList(Filename filename): filenames({std::move(filename)}) {} + explicit FilenameList(bool force_build = false): force_build{force_build} {} + explicit FilenameList(std::vector filenames): filenames(std::move(filenames)) {} + + void resolve(const ResolveContext& context); + [[nodiscard]] bool empty() const {return words.empty() && filenames.empty();} + void serialize(std::ostream& stream) const; + [[nodiscard]] std::string string() const; + void collect_output_files(std::unordered_set& output_files) const; + + void collect_filenames(std::vector& collector) const {collector.insert(collector.end(), filenames.begin(), filenames.end());} + + private: + std::vector words; + std::vector filenames; + bool force_build{false}; +}; + +std::ostream& operator<<(std::ostream& stream, const FilenameList& filename_list); + +#endif // FILENAMELIST_H diff --git a/src/FilenameVariable.cc b/src/FilenameVariable.cc new file mode 100644 index 0000000..4f1d621 --- /dev/null +++ b/src/FilenameVariable.cc @@ -0,0 +1,54 @@ +/* +FilenameVariable.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "FilenameVariable.h" + + +#include "File.h" + +FilenameVariable::FilenameVariable(std::string name, Tokenizer& tokenizer) : Variable(std::move(name)) { + auto token = tokenizer.next(Tokenizer::Skip::SPACE); + if (token.type == Tokenizer::TokenType::NEWLINE) { + token = tokenizer.next(); + if (token.type == Tokenizer::TokenType::BEGIN_SCOPE) { + value = FilenameList(tokenizer, FilenameList::SCOPED); + tokenizer.expect(Tokenizer::TokenType::END_SCOPE); + } + else { + tokenizer.unget(token); + value = FilenameList(); + } + } + else { + tokenizer.unget(token); + value = FilenameList(tokenizer, FilenameList::INLINE); + } +} diff --git a/src/FilenameVariable.h b/src/FilenameVariable.h new file mode 100644 index 0000000..1945aa1 --- /dev/null +++ b/src/FilenameVariable.h @@ -0,0 +1,56 @@ +/* +FilenameVariable.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FILENAMEVARIABLE_H +#define FILENAMEVARIABLE_H + +#include "FilenameList.h" +#include "ResolveContext.h" +#include "Variable.h" + +#include + +class FilenameVariable : public Variable { + public: + FilenameVariable(std::string name, Tokenizer& tokenizer); + FilenameVariable(std::string name, FilenameList value): Variable(std::move(name)), value{std::move(value)} {} + + void resolve_sub(const ResolveContext& context) override {value.resolve(context);} + void print_definition(std::ostream& stream) const override {} // TODO + void print_use(std::ostream& stream) const override {} // TODO + [[nodiscard]] std::string string() const override {return value.string();} + void collect_filenames(std::vector& collector) const {return value.collect_filenames(collector);} + +private: + FilenameList value; +}; + +#endif // FILENAMEVARIABLE_H diff --git a/src/FilenameWord.cc b/src/FilenameWord.cc new file mode 100644 index 0000000..e16e7c1 --- /dev/null +++ b/src/FilenameWord.cc @@ -0,0 +1,154 @@ +/* +ExplicitFilename.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "FilenameWord.h" + +#include + +#include "FilenameVariable.h" + +FilenameWord::FilenameWord(Tokenizer& tokenizer) { + std::string string; + + auto first = true; + auto braced = false; + + tokenizer.skip_space(); + + while (auto token = tokenizer.next()) { + if (token.type == Tokenizer::TokenType::SPACE && !braced) { + break; + } + else if (token.type == Tokenizer::TokenType::END_FILENAME) { + if (braced) { + break; + } + else { + throw Exception("unmatched }}"); + } + } + else if (token.type == Tokenizer::TokenType::BEGIN_FILENAME) { + if (!braced && first) { + first = false; + braced = true; + continue; + } + else { + throw Exception("cannot nest filenames"); + } + } + else if (token.type == Tokenizer::TokenType::NEWLINE) { + if (!braced) { + tokenizer.unget(token); + break; + } + throw Exception("unterminated filename"); + } + + first = false; + + if (token.is_variable_refrence()) { + if (!string.empty()) { + elements.emplace_back(string); + string = ""; + } + elements.emplace_back(VariableReference(token.value)); + } + else if (braced || token.type == Tokenizer::TokenType::WORD) { + string += token.value; + } + else { + tokenizer.unget(token); + break; + } + } + + if (!string.empty()) { + elements.emplace_back(string); + } +} + +void FilenameWord::resolve(const ResolveContext& context) { + for (auto& element : elements) { + if (std::holds_alternative(element)) { + auto& variable_reference = std::get(element); + variable_reference.resolve(context); + if (variable_reference.is_text_variable()) { + element = variable_reference.variable->string(); + } + } + } +} + +void FilenameWord::collect_filenames(std::vector& filenames) const { + std::string prefix; + std::string postfix; + auto current_string = &prefix; + const FilenameVariable* filename_variable{}; + + for (auto& element: elements) { + if (std::holds_alternative(element)) { + *current_string += std::get(element); + } + else { + auto variable = std::get(element); + if (variable.is_filename_variable()) { + if (filename_variable) { + throw Exception("multiple filename variables in filename not allowed"); + } + else { + filename_variable = variable.variable->as_filename(); + current_string = &postfix; + } + } + else { + *current_string += variable.variable->string(); + } + } + } + + if (filename_variable) { + if (prefix.empty() && postfix.empty()) { + filename_variable->collect_filenames(filenames); + } + else { + std::vector inner_filenames; + filename_variable->collect_filenames(inner_filenames); + for (auto& filename : inner_filenames) { + filename.name = prefix + filename.name + postfix; + } + filenames.insert(filenames.end(), inner_filenames.begin(), inner_filenames.end()); + } + } + else { + filenames.emplace_back(prefix); + } +} diff --git a/src/FilenameWord.h b/src/FilenameWord.h new file mode 100644 index 0000000..22c7d1d --- /dev/null +++ b/src/FilenameWord.h @@ -0,0 +1,55 @@ +#ifndef EXPLICITFILENAME_H +#define EXPLICITFILENAME_H + +/* +FilenameWord.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Filename.h" +#include "VariableReference.h" + + +class FilenameList; + +class FilenameWord { + public: + explicit FilenameWord(Tokenizer& tokenizer); + explicit FilenameWord(std::string word): elements{std::move(word)} {} + + [[nodiscard]] bool empty() const {return elements.empty();} + void resolve(const ResolveContext& context); + + void collect_filenames(std::vector& filenames) const; + +private: + std::vector> elements; +}; + +#endif // EXPLICITFILENAME_H diff --git a/src/Pool.cc b/src/Pool.cc index 3fa091e..0ba1b1e 100644 --- a/src/Pool.cc +++ b/src/Pool.cc @@ -31,12 +31,14 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Pool.h" +#include "File.h" + Pool::Pool(std::string name, Tokenizer& tokenizer) : name{ std::move(name) } { tokenizer.expect(Tokenizer::TokenType::NEWLINE, Tokenizer::Skip::SPACE); bindings = Bindings{tokenizer}; } -void Pool::process(const File& file) { bindings.process(file); } +void Pool::process(const File& file) { bindings.resolve(file); } void Pool::print(std::ostream& stream) const { stream << std::endl << "pool " << name << std::endl; diff --git a/src/Pool.h b/src/Pool.h index 91993fe..04ed1a1 100644 --- a/src/Pool.h +++ b/src/Pool.h @@ -37,6 +37,8 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Bindings.h" #include "Variable.h" +class File; + class Pool { public: Pool() = default; diff --git a/src/ResolveContext.cc b/src/ResolveContext.cc new file mode 100644 index 0000000..f2a7f09 --- /dev/null +++ b/src/ResolveContext.cc @@ -0,0 +1,53 @@ +/* +ResolveContext.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "ResolveContext.h" + + +#include + +ResolveContext ResolveContext::resolving(const std::string& name) const { + if (resolving_variables.contains(name)) { + throw Exception("circular variable definition involving %s", name.c_str()); // TODO: include all variables in cycle + } + + auto new_context = *this; + new_context.resolving_variables.insert(name); + return new_context; +} + +const Variable* ResolveContext::get_variable(const std::string& name) const { + const auto variable = scope.get_variable(name); + if (variable) { + variable->resolve(*this); + } + return variable; +} diff --git a/src/ResolveContext.h b/src/ResolveContext.h new file mode 100644 index 0000000..fbd5809 --- /dev/null +++ b/src/ResolveContext.h @@ -0,0 +1,54 @@ +/* +ResolveContext.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RESOLVECONTEXT_H +#define RESOLVECONTEXT_H + +#include + +#include "Scope.h" + +class ResolveContext { +public: + explicit ResolveContext(const Scope& scope): scope{scope} {} + + [[nodiscard]] ResolveContext resolving(const std::string& name) const; + [[nodiscard]] const Variable* get_variable(const std::string& name) const; + + const Scope& scope; + +private: + std::unordered_set resolving_variables; +}; + + + +#endif //RESOLVECONTEXT_H diff --git a/src/Rule.cc b/src/Rule.cc index 80e5538..c9a1113 100644 --- a/src/Rule.cc +++ b/src/Rule.cc @@ -31,12 +31,16 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Rule.h" -Rule::Rule(std::string name, Tokenizer& tokenizer) : name{ std::move(name) } { +#include "File.h" + +Rule::Rule(const File* file, std::string name, Tokenizer& tokenizer) : ScopedDirective{file}, name{std::move(name)} { tokenizer.expect(Tokenizer::TokenType::NEWLINE, Tokenizer::Skip::SPACE); bindings = Bindings{tokenizer}; } -void Rule::process(const File& file) { bindings.process(file); } +Rule::Rule(const File* file, std::string name, Bindings bindings): ScopedDirective{file, std::move(bindings)}, name{std::move(name)} {} + +void Rule::process(const File& file) { bindings.resolve(file); } void Rule::print(std::ostream& stream) const { stream << std::endl << "rule " << name << std::endl; diff --git a/src/Rule.h b/src/Rule.h index 693df91..241dc9e 100644 --- a/src/Rule.h +++ b/src/Rule.h @@ -34,21 +34,22 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include "Bindings.h" +#include "ScopedDirective.h" #include "Variable.h" -class Rule { +class File; + +class Rule: public ScopedDirective { public: Rule() = default; - Rule(std::string name, Tokenizer& tokenizer); - Rule(std::string name, Bindings bindings): name{std::move(name)}, bindings{std::move(bindings)} {} + Rule(const File* file, std::string name, Tokenizer& tokenizer); + Rule(const File* file, std::string name, Bindings bindings); void process(const File& file); void print(std::ostream& stream) const; private: std::string name; - Bindings bindings; }; #endif // RULE_H diff --git a/src/Scope.cc b/src/Scope.cc new file mode 100644 index 0000000..0e338d0 --- /dev/null +++ b/src/Scope.cc @@ -0,0 +1,83 @@ +/* +Scope.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Scope.h" + +#include "File.h" + +Variable* Scope::get_variable(const std::string& name) const { + auto scope = this; + while (scope) { + auto it = scope->bindings.find(name); + if (it != scope->bindings.end()) { + return it->second.get(); + } + scope = scope->next; + } + + return {}; +} + +const File* Scope::get_file() const { + auto scope = this; + while (scope) { + if (const auto file = dynamic_cast(scope)) { + return file; + } + scope = scope->next; + } + + return {}; +} + +const Scope* Scope::top() const { + auto scope = this; + + while (!scope->is_top()) { + scope = scope->next; + } + + return scope; +} + +const File* Scope::as_file() const { + return dynamic_cast(this); +} + +bool Scope::is_output_file(const std::filesystem::path& file) const { + if (auto top_file = top()->as_file()) { + return top_file->is_output(file); + } + else { + // TODO: internal error? + return false; + } +} diff --git a/src/Scope.h b/src/Scope.h new file mode 100644 index 0000000..b289808 --- /dev/null +++ b/src/Scope.h @@ -0,0 +1,62 @@ +#ifndef SCOPE_H +#define SCOPE_H + +/* +Scope.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Bindings.h" + +class File; + +class Scope { +public: + Scope() = default; + explicit Scope(const Scope* next): next{next} {} + Scope(const Scope* next, Bindings bindings): next{next}, bindings{std::move(bindings)} {} + virtual ~Scope() = default; + + [[nodiscard]] bool is_top() const {return !next;} + [[nodiscard]] const Scope* top() const; + [[nodiscard]] const File* as_file() const; + [[nodiscard]] bool is_file() const {return as_file();} + + [[nodiscard]] Variable* get_variable(const std::string& name) const; + [[nodiscard]] const File* get_file() const; + [[nodiscard]] bool is_output_file(const std::filesystem::path& file) const; + + virtual void polymorphic() const {} + +protected: + const Scope* next{}; + Bindings bindings{}; +}; + +#endif //SCOPE_H diff --git a/src/ScopedDirective.cc b/src/ScopedDirective.cc index 8d2f805..3da3b5d 100644 --- a/src/ScopedDirective.cc +++ b/src/ScopedDirective.cc @@ -31,8 +31,7 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ScopedDirective.h" +#include "File.h" -#include - -void ScopedDirective::parse_bindings(Tokenizer& tokenizer) { -} +ScopedDirective::ScopedDirective(const File* file): Scope(file) {} +ScopedDirective::ScopedDirective(const File* file, Bindings bindings): Scope{file, std::move(bindings)} {} diff --git a/src/ScopedDirective.h b/src/ScopedDirective.h index 463f5cc..5f264b1 100644 --- a/src/ScopedDirective.h +++ b/src/ScopedDirective.h @@ -29,22 +29,19 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef SCOPEDDIRECTIVE_H -#define SCOPEDDIRECTIVE_H - -#include +#ifndef SCOPED_DIRECTIVE_H +#define SCOPED_DIRECTIVE_H +#include "Scope.h" #include "Variable.h" -class ScopedDirective { +class ScopedDirective: public Scope { public: - void process(const File& file); - - protected: - void parse_bindings(Tokenizer& tokenizer); + ScopedDirective() = default; + explicit ScopedDirective(const File* file); + ScopedDirective(const File* file, Bindings bindings); - std::unordered_map bindings; + void process(const File& file); }; - -#endif // SCOPEDDIRECTIVE_H +#endif // SCOPED_DIRECTIVE_H diff --git a/src/Text.cc b/src/Text.cc index ef286ce..9a51a9b 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -34,93 +34,15 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Exception.h" #include "File.h" -// clang-format: off -const std::unordered_map Text::typemap = { - { Tokenizer::TokenType::IMPLICIT_DEPENDENCY, ElementType::PUNCTUATION }, - { Tokenizer::TokenType::ORDER_DEPENDENCY, ElementType::PUNCTUATION }, - { Tokenizer::TokenType::SPACE, ElementType::WHITESPACE }, - { Tokenizer::TokenType::VALIDATION_DEPENDENCY, ElementType::PUNCTUATION }, -}; - -// clang-format: on - -Text::Element::Element(const Tokenizer::Token& token) { - const auto it = typemap.find(token.type); - if (it != typemap.end()) { - type = it->second; - } - else { - type = ElementType::WORD; - } - value = token.string(); -} - -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) { +Text::Text(Tokenizer& tokenizer) { tokenizer.skip_space(); - while (const auto token = tokenizer.next()) { - switch (token.type) { - case Tokenizer::TokenType::NEWLINE: - if (terminator == Tokenizer::TokenType::COLON) { - throw Exception("missing ':'"); - } - else if (terminator == Tokenizer::TokenType::END_SCOPE) { - emplace_back(ElementType::WHITESPACE, " "); - } - else { - return; - } - break; - - case Tokenizer::TokenType::END_SCOPE: - return; - - case Tokenizer::TokenType::VARIABLE_REFERENCE: - emplace_back(ElementType::VARIABLE, token.value); - break; - - case Tokenizer::TokenType::COLON: - if (terminator == Tokenizer::TokenType::COLON) { - return; - } - // fallthrough - default: - emplace_back(token); - break; + while (true) { + words.emplace_back(tokenizer); + auto token = tokenizer.next(); + if (token.type == Tokenizer::TokenType::NEWLINE) { + break; } + words.emplace_back(token.string()); } } @@ -130,54 +52,21 @@ 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.variable) { - element.variable->print_use(stream); - } - else { - stream << "$" << element.value; - } - } - else { - stream << element.string(); - } + for (auto& word: words) { + stream << word; } } -void Text::process(const File& file) { - for (auto& element : *this) { - 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.variable && element.variable->is_list) { - element.variable->value.collect_words(words); - } - } - else if (element.is_word() || element.is_build_file()) { - words.insert(element.value); - } +void Text::resolve(const ResolveContext& context) { + for (auto& word : words) { + word.resolve(context); } } std::string Text::string() const { auto result = std::string{}; - for (auto& element : *this) { + for (auto& element : words) { result += element.string(); } diff --git a/src/Text.h b/src/Text.h index f7cee22..ede553e 100644 --- a/src/Text.h +++ b/src/Text.h @@ -33,12 +33,12 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define TEXT_H #include -#include #include #include "Tokenizer.h" +#include "Word.h" -class File; +class ResolveContext; class Variable; /* @@ -52,62 +52,21 @@ class Variable; */ class Text { public: - enum class ElementType { - BUILD_FILE, - SOURCE_FILE, - PUNCTUATION, - VARIABLE, - WHITESPACE, - WORD - }; - class Element { - public: - Element(const Tokenizer::Token& token); - 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; - const Variable *variable{}; - }; - Text() = default; - Text(Tokenizer& tokenizer, Tokenizer::TokenType terminator); - explicit Text(std::string value): Text{std::vector{Element{ElementType::WORD, std::move(value)}}} {} - explicit Text(std::vector elements): elements{std::move(elements)} {} + Text(Tokenizer& tokenizer); + explicit Text(std::string value): Text{std::vector{Word{std::move(value)}}} {} + explicit Text(std::vector elements): words{std::move(elements)} {} - void append(const Text& other) {elements.insert(elements.end(), other.elements.begin(), other.elements.end());} - void emplace_back(ElementType type, std::string value) { elements.emplace_back(type, std::move(value)); } - void emplace_back(const Element& element) { elements.emplace_back(element); } + void append(const Text& other) {words.insert(words.end(), other.words.begin(), other.words.end());} + void emplace_back(const Word& element) { words.emplace_back(element); } void print(std::ostream& stream) const; - void process(const File& file); - void collect_words(std::unordered_set& words) const; - [[nodiscard]] bool empty() const {return elements.empty();} + void resolve(const ResolveContext& scope); + [[nodiscard]] bool empty() const {return words.empty();} [[nodiscard]] std::string string() const; - [[nodiscard]] std::vector::iterator begin() { return elements.begin(); } - - [[nodiscard]] std::vector::iterator end() { return elements.end(); } - - [[nodiscard]] std::vector::const_iterator begin() const { return elements.begin(); } - - [[nodiscard]] std::vector::const_iterator end() const { return elements.end(); } - - Element& operator[](size_t index) { return elements[index]; } - - const Element& operator[](size_t index) const { return elements[index]; } - private: - std::vector elements; - - static const std::unordered_map typemap; + std::vector words; }; std::ostream& operator<<(std::ostream& stream, const Text& text); diff --git a/src/TextParser.cc b/src/TextParser.cc new file mode 100644 index 0000000..5622a2d --- /dev/null +++ b/src/TextParser.cc @@ -0,0 +1,110 @@ +/* +TextParser.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "TextParser.h" + + +#include "File.h" +#include + +Text TextParser::parse() { + tokenizer.skip_space(); + parse_tokens(); + end_word(); + return std::move(text); +} + +void TextParser::parse_tokens() { + while (const auto token = tokenizer.next()) { + switch (token.type) { + case Tokenizer::TokenType::NEWLINE: + if (terminator == Tokenizer::TokenType::COLON) { + throw Exception("missing ':'"); + } + else if (terminator == Tokenizer::TokenType::END_SCOPE) { + end_word(); + text.emplace_back(Text::ElementType::WHITESPACE, " "); + } + else { + return; + } + break; + + case Tokenizer::TokenType::END_SCOPE: + return; + + case Tokenizer::TokenType::VARIABLE_REFERENCE: + if (token.value == "in" || token.value == "out") { + end_word(true); + text.emplace_back(Text::ElementType::VARIABLE, token.value); + } + else if (const auto variable = file.find_variable(token.value)) { + if (variable->is_list) { + if (!current_word.empty()) { + // TODO: warn: expanding list variable in middle of word + end_word(); + } + text.append(variable->value); + } + else { + current_word += variable->value.string(); + } + } + break; + + case Tokenizer::TokenType::COLON: + case Tokenizer::TokenType::IMPLICIT_DEPENDENCY: + case Tokenizer::TokenType::ORDER_DEPENDENCY: + case Tokenizer::TokenType::VALIDATION_DEPENDENCY: + if (terminator == token.type) { + return; + } + text.emplace_back(Text::ElementType::PUNCTUATION, token.value); + break; + + case Tokenizer::TokenType::SPACE: + end_word(); + text.emplace_back(Text::ElementType::WHITESPACE, token.value); + break; + + default: + text.emplace_back(Text::ElementType::WORD, token.value); + break; + } + } +} + +void TextParser::end_word(bool partial) { + if (!current_word.empty()) { + text.emplace_back(partial || current_word_is_partial ? Text::ElementType::PARTIAL : Text::ElementType::WORD, current_word); + } + current_word_is_partial = partial; +} diff --git a/src/TextParser.h b/src/TextParser.h new file mode 100644 index 0000000..f2175f6 --- /dev/null +++ b/src/TextParser.h @@ -0,0 +1,60 @@ +/* +TextParser.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef TEXTPARSER_H +#define TEXTPARSER_H + +#include "Text.h" +#include "Tokenizer.h" + +class TextParser { +public: + TextParser(Tokenizer& tokenizer, Tokenizer::TokenType terminator, const File& file, bool expand_variables): tokenizer{tokenizer}, file{file}, expand_variables{expand_variables}, terminator{terminator} {} + + Text parse(); + +private: + void end_word(bool partial = false); + void parse_tokens(); + + Tokenizer& tokenizer; + const File& file; + bool expand_variables; + Tokenizer::TokenType terminator; + + bool current_word_is_partial{false}; + std::string current_word; + Text text; +}; + + + +#endif //TEXTPARSER_H diff --git a/src/TextVariable.cc b/src/TextVariable.cc new file mode 100644 index 0000000..69c9cc8 --- /dev/null +++ b/src/TextVariable.cc @@ -0,0 +1,40 @@ +/* +TextVariable.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "TextVariable.h" + +void TextVariable::resolve_sub(const ResolveContext& context) { + value.resolve(context); +} + +void TextVariable::print_definition(std::ostream& stream) const { + stream << name << " = " << value << std::endl; +} diff --git a/src/TextVariable.h b/src/TextVariable.h new file mode 100644 index 0000000..c9bf755 --- /dev/null +++ b/src/TextVariable.h @@ -0,0 +1,52 @@ +/* +TextVariable.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef TEXTVARIABLE_H +#define TEXTVARIABLE_H + +#include "Variable.h" +#include "Text.h" + +class TextVariable: public Variable { +public: + TextVariable(std::string name, Tokenizer& tokenizer): Variable(std::move(name)), value(tokenizer) {} + TextVariable(std::string name, Text value): Variable(std::move(name)), value{std::move(value)} {} + + void resolve_sub(const ResolveContext& scope) override; + void print_definition(std::ostream& stream) const override; + void print_use(std::ostream& stream) const override {} // TODO + [[nodiscard]] std::string string() const override {return value.string();} + +private: + Text value; +}; + +#endif //TEXTVARIABLE_H diff --git a/src/Tokenizer.cc b/src/Tokenizer.cc index 64500d8..db80e7f 100644 --- a/src/Tokenizer.cc +++ b/src/Tokenizer.cc @@ -58,10 +58,12 @@ std::unordered_map Tokenizer::special_characters {'@', CharacterType::PUNCTUATION}, {'_', CharacterType::SIMPLE_VARIABLE}, {'|', CharacterType::PUNCTUATION}, + {'{', CharacterType::PUNCTUATION}, + {'}', CharacterType::PUNCTUATION} }; // clang-format on -Tokenizer::Tokenizer(const std::filesystem::path& filename): filename{filename}, source{filename} { +Tokenizer::Tokenizer(const std::filesystem::path& filename) : filename{ filename }, source{ filename } { if (source.fail()) { throw Exception("can't open '%s'", filename.c_str()); } @@ -90,6 +92,9 @@ std::string Tokenizer::Token::type_name(TokenType type) { case TokenType::BEGIN_SCOPE: return "{"; + case TokenType::BEGIN_FILENAME: + return "{{"; + case TokenType::BUILD: return "build"; @@ -102,6 +107,9 @@ std::string Tokenizer::Token::type_name(TokenType type) { case TokenType::END: return "END"; + case TokenType::END_FILENAME: + return "}}"; + case TokenType::END_SCOPE: return "}"; @@ -138,6 +146,8 @@ std::string Tokenizer::Token::type_name(TokenType type) { case TokenType::WORD: return "word"; } + + throw Exception("invalid token type"); } Tokenizer::Token Tokenizer::expect(TokenType type, Skip skip) { @@ -231,6 +241,17 @@ Tokenizer::Token Tokenizer::get_next() { return Token{ TokenType::IMPLICIT_DEPENDENCY }; } + case '{': + case '}': + beggining_of_line = false; + if (source.get() == c) { + return Token{ c == '{' ? TokenType::BEGIN_FILENAME : TokenType::END_FILENAME }; + } + else { + source.unget(); + return tokenize_word(c); + } + case '$': beggining_of_line = false; return tokenize_dollar(); diff --git a/src/Tokenizer.h b/src/Tokenizer.h index ed3dd30..44ad04f 100644 --- a/src/Tokenizer.h +++ b/src/Tokenizer.h @@ -61,11 +61,13 @@ class Tokenizer { enum class TokenType { ASSIGN, ASSIGN_LIST, + BEGIN_FILENAME, BEGIN_SCOPE, BUILD, COLON, DEFAULT, END, + END_FILENAME, END_SCOPE, IMPLICIT_DEPENDENCY, INCLUDE, @@ -86,6 +88,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_variable_refrence() const {return type == TokenType::VARIABLE_REFERENCE;} [[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);} diff --git a/src/Variable.cc b/src/Variable.cc index a3c7dce..0a77273 100644 --- a/src/Variable.cc +++ b/src/Variable.cc @@ -31,39 +31,20 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Variable.h" -Variable::Variable(std::string name, bool is_list, Tokenizer& tokenizer) : name{ std::move(name) }, is_list{ is_list } { - auto terminator = Tokenizer::TokenType::NEWLINE; - tokenizer.skip_space(); - if (is_list) { - auto token = tokenizer.next(); - if (token.type == Tokenizer::TokenType::NEWLINE) { - token = tokenizer.next(); - if (token.type == Tokenizer::TokenType::BEGIN_SCOPE) { - terminator = Tokenizer::TokenType::END_SCOPE; - } - else { - tokenizer.unget(token); - return; - } - } - } +#include "FilenameVariable.h" +#include "ResolveContext.h" +#include "TextVariable.h" - value = Text{tokenizer, terminator}; +const FilenameVariable* Variable::as_filename() const { + return dynamic_cast(this); } -void Variable::process(const File& file) { - value.process(file); -} +const TextVariable* Variable::as_text() const { return dynamic_cast(this); } -void Variable::print_definition(std::ostream& stream) const { - stream << name << " = " << value; -} - -void Variable::print_use(std::ostream& stream) const { - if (is_list) { - stream << value; - } - else { - stream << "${" << name << "}"; +void Variable::resolve(const ResolveContext& context) { + if (resolved) { + return; } + resolve_sub(context.resolving(name)); + resolved = true; } diff --git a/src/Variable.h b/src/Variable.h index aa5ec21..c904e27 100644 --- a/src/Variable.h +++ b/src/Variable.h @@ -34,25 +34,33 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include "Text.h" #include "Tokenizer.h" - -class File; +class ResolveContext; +class FilenameVariable; +class TextVariable; class Variable { public: + explicit Variable(std::string name): name{std::move(name)} {} Variable() = default; - Variable(std::string name, bool is_list, Tokenizer& tokenizer); - Variable(std::string name, bool is_list, Text value): name{std::move(name)}, is_list{is_list}, value{std::move(value)} {} + virtual ~Variable() = default; + + [[nodiscard]] const FilenameVariable* as_filename() const; + [[nodiscard]] const TextVariable* as_text() const; + [[nodiscard]] bool is_filename() const {return as_filename();} + [[nodiscard]] bool is_text() const {return as_text();} - void process(const File& file); - void print_definition(std::ostream& stream) const; - void print_use(std::ostream& stream) const; + void resolve(const ResolveContext& context); + virtual void resolve_sub(const ResolveContext& context) = 0; + virtual void print_definition(std::ostream& stream) const = 0; + virtual void print_use(std::ostream& stream) const = 0; + [[nodiscard]] virtual std::string string() const = 0; std::string name; - bool is_list = false; - Text value; + +private: + bool resolved{false}; }; diff --git a/src/VariableReference.cc b/src/VariableReference.cc new file mode 100644 index 0000000..571d8c4 --- /dev/null +++ b/src/VariableReference.cc @@ -0,0 +1,41 @@ +/* +VariableReference.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "VariableReference.h" + + +#include "File.h" + +void VariableReference::resolve(const ResolveContext& context) { + if (!is_resolved()) { + variable = context.get_variable(name); + } +} diff --git a/src/VariableReference.h b/src/VariableReference.h new file mode 100644 index 0000000..e68e116 --- /dev/null +++ b/src/VariableReference.h @@ -0,0 +1,55 @@ +#ifndef VARIABLE_REFERENCE_H +#define VARIABLE_REFERENCE_H + +/* +VariableReference.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "ResolveContext.h" +#include "Variable.h" + +class VariableReference { + public: + explicit VariableReference(std::string name) : name{ std::move(name) } {} + + [[nodiscard]] bool is_filename_variable() const {return variable && variable->is_filename();} + [[nodiscard]] bool is_resolved() const {return variable;} + [[nodiscard]] bool is_text_variable() const {return variable && variable->is_text();} + + void resolve(const ResolveContext& context); + + const Variable* variable{}; + + std::string name; +}; + +#endif // VARIABLE_REFERENCE_H diff --git a/src/Word.cc b/src/Word.cc new file mode 100644 index 0000000..7a97e07 --- /dev/null +++ b/src/Word.cc @@ -0,0 +1,159 @@ +/* +Word.cc -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Word.h" + +#include + +#include "FilenameVariable.h" +#include "Exception.h" + +Word::Word(Tokenizer& tokenizer) { + std::string string; + + while (auto token = tokenizer.next()) { + if (token.is_whitespace()) { + tokenizer.unget(token); + break; + } + + if (token.is_variable_refrence()) { + if (!string.empty()) { + elements.emplace_back(string); + string = ""; + } + elements.emplace_back(VariableReference(token.value)); + } + else if (token.type == Tokenizer::TokenType::BEGIN_FILENAME) { + tokenizer.unget(token); + elements.emplace_back(FilenameWord(tokenizer)); + } + else { + string += token.value; + } + } + + if (!string.empty()) { + elements.emplace_back(string); + } +} + +std::string Word::string() const { + auto stream = std::stringstream{}; + + stream << *this; + + return stream.str(); +} + +void Word::print(std::ostream& stream) const { + std::string prefix; + std::string postfix; + std::string* current_string = &prefix; + const VariableReference* filename_variable{}; + const FilenameWord* filename_word{}; + + for (auto& element: elements) { + if (std::holds_alternative(element)) { + *current_string += std::get(element); + } + else if (std::holds_alternative(element)) { + auto& variable = std::get(element); + + if (variable.is_resolved()) { + if (variable.variable->is_text()) { + *current_string += variable.variable->string(); + } + else { + if (filename_variable || filename_word) { + throw Exception("multiple file names in word not allowed"); + } + filename_variable = &variable; + current_string = &postfix; + } + } + else { + *current_string += "$" + variable.name; + } + } + else if (std::holds_alternative(element)) { + if (filename_variable || filename_word) { + throw Exception("multiple file names in word not allowed"); + } + filename_word = &std::get(element); + current_string = &postfix; + } + } + + if (filename_variable || filename_word) { + std::vector filenames; + if (filename_variable) { + filename_variable->variable->as_filename()->collect_filenames(filenames); + } + else { + filename_word->collect_filenames(filenames); + } + auto first = true; + for (const auto& filename: filenames) { + if (first) { + first = false; + } + else { + stream << " "; + } + stream << prefix << filename << postfix; + } + } + else { + stream << prefix; + } +} + +void Word::resolve(const ResolveContext& context) { + for (auto& element : elements) { + if (std::holds_alternative(element)) { + auto& variable_reference = std::get(element); + variable_reference.resolve(context); + if (variable_reference.is_text_variable()) { + element = variable_reference.variable->string(); + } + } + else if (std::holds_alternative(element)) { + auto& filename = std::get(element); + filename.resolve(context); + } + } +} + +std::ostream& operator<<(std::ostream& stream, const Word& word) { + word.print(stream); + return stream; +} diff --git a/src/Word.h b/src/Word.h new file mode 100644 index 0000000..63c46c4 --- /dev/null +++ b/src/Word.h @@ -0,0 +1,70 @@ +#ifndef WORD_H +#define WORD_H +#include + +/* +Word.h -- + +Copyright (C) Dieter Baron + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include + +#include "FilenameWord.h" +#include "Filename.h" +#include "VariableReference.h" + +class FilenameWord; + +class Word { + public: + Word(Tokenizer& tokenizer); + explicit Word(std::string text) {elements.emplace_back(std::move(text));}; + explicit Word(VariableReference variable_reference) {elements.emplace_back(variable_reference);} + Word() = default; + + [[nodiscard]] bool empty() const { return elements.empty(); } + + [[nodiscard]] std::string string() const; + void print(std::ostream& stream) const; + + 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));} + + std::vector> elements; +}; + +std::ostream& operator<<(std::ostream& stream, const Word& word); + +#endif // WORD_H diff --git a/tests/implicit-output.test b/tests/implicit-output.test index d2cba38..0528707 100644 --- a/tests/implicit-output.test +++ b/tests/implicit-output.test @@ -10,20 +10,18 @@ rule a build output | implicit: a input | output end-of-inline-data file build/build.ninja {} <> -top_source_directory = .. -source_directory = .. -top_build_directory = . -build_directory = . +# This file is automatically created by fast-ninja from ../build.fninja +# Do not edit. rule a command = a $in $out flags = --verbose rule fast-ninja - command = fast-ninja $top_source_directory + command = fast-ninja .. generator = 1 -build $build_directory/output | $build_directory/implicit : a $source_directory/input | $build_directory/output +build output | implicit : a ../input | output -build ./build.ninja : fast-ninja ../build.fninja +build build.ninja : fast-ninja ../build.fninja end-of-inline-data diff --git a/tests/rule.test b/tests/rule.test index 15fe404..7d386fe 100644 --- a/tests/rule.test +++ b/tests/rule.test @@ -5,18 +5,16 @@ rule a flags = --verbose end-of-inline-data file build/build.ninja {} <> -top_source_directory = .. -source_directory = .. -top_build_directory = . -build_directory = . +# This file is automatically created by fast-ninja from ../build.fninja +# Do not edit. rule a command = a $in $out flags = --verbose rule fast-ninja - command = fast-ninja $top_source_directory + command = fast-ninja .. generator = 1 -build ./build.ninja : fast-ninja ../build.fninja +build build.ninja : fast-ninja ../build.fninja end-of-inline-data diff --git a/tests/subninja.test b/tests/subninja.test new file mode 100644 index 0000000..430e3ac --- /dev/null +++ b/tests/subninja.test @@ -0,0 +1,48 @@ +arguments .. +file input <> +main input +end-of-inline-data +file src/input <> +source input +end-of-inline-data +file build.fninja <> +rule a + command = a $in $out + flags = --verbose + +build middle: a input +build output: a middle + +subninja src/build.fninja +end-of-inline-data +file src/build.fninja <> +build output: a ../output input ../input +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 $out + flags = --verbose + +rule fast-ninja + command = fast-ninja .. + generator = 1 + +build middle : a ../input + +build output : a middle + +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/output : a output ../src/input ../input +end-of-inline-data