diff --git a/.gitignore b/.gitignore index 0d418fdf..21236ca9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ local/ build/ +.vscode user*.* \ No newline at end of file diff --git a/premake5.lua b/premake5.lua index 8f6e5ecf..a7072040 100644 --- a/premake5.lua +++ b/premake5.lua @@ -100,6 +100,7 @@ include "src/Common.lua" include "src/Crypto.lua" include "src/Linker.lua" include "src/Parser.lua" +include "src/RawTemplater.lua" include "src/Unlinker.lua" include "src/Utils.lua" include "src/ZoneCode.lua" @@ -130,11 +131,16 @@ group "Components" ObjWriting:project() group "" +-- Tools group: All projects that compile into the final tools +group "BuildTools" + RawTemplater:project() + ZoneCodeGenerator:project() +group "" + -- Tools group: All projects that compile into the final tools group "Tools" Linker:project() Unlinker:project() - ZoneCodeGenerator:project() group "" group "Raw" diff --git a/raw/iw4/techniques/technique_test.technique b/raw/iw4/techniques/technique_test.technique new file mode 100644 index 00000000..e69de29b diff --git a/raw/iw4/techniques/technique_test.technique.template b/raw/iw4/techniques/technique_test.technique.template new file mode 100644 index 00000000..20ba92e0 --- /dev/null +++ b/raw/iw4/techniques/technique_test.technique.template @@ -0,0 +1,14 @@ +#pragma options TEST(asdf, bla) +#pragma switch TEST_SWITCH + +#ifdef TEST_SWITCH +#define SVAL "1" +#else +#define SVAL "0" +#endif + +#pragma filename "lemao_" + TEST + SVAL + ".txt" +HAHA TEST +#ifdef TEST_SWITCH +kekw +#endif \ No newline at end of file diff --git a/src/Parser/Parsing/Impl/Defines/DefinesExpressionMatchers.cpp b/src/Parser/Parsing/Impl/Defines/DefinesExpressionMatchers.cpp index e5638414..dd88914b 100644 --- a/src/Parser/Parsing/Impl/Defines/DefinesExpressionMatchers.cpp +++ b/src/Parser/Parsing/Impl/Defines/DefinesExpressionMatchers.cpp @@ -8,7 +8,7 @@ DefinesExpressionMatchers::DefinesExpressionMatchers() } DefinesExpressionMatchers::DefinesExpressionMatchers(const DefinesDirectiveParsingState* state) - : SimpleExpressionMatchers(false, false, true, true, false), + : SimpleExpressionMatchers(true, false, true, true, false), m_state(state) { } diff --git a/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp b/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp index da089080..87875911 100644 --- a/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp +++ b/src/Parser/Parsing/Impl/DefinesStreamProxy.cpp @@ -281,13 +281,13 @@ bool DefinesStreamProxy::MatchUndefDirective(const ParserLine& line, const unsig std::unique_ptr DefinesStreamProxy::ParseExpression(const std::string& expressionString) const { std::istringstream ss(expressionString); - ParserSingleInputStream inputStream(ss, "#if expression"); + ParserSingleInputStream inputStream(ss, "defines directive expression"); SimpleLexer::Config lexerConfig; lexerConfig.m_emit_new_line_tokens = false; lexerConfig.m_read_integer_numbers = true; lexerConfig.m_read_floating_point_numbers = true; - lexerConfig.m_read_strings = false; + lexerConfig.m_read_strings = true; SimpleExpressionMatchers().ApplyTokensToLexerConfig(lexerConfig); SimpleLexer lexer(&inputStream, std::move(lexerConfig)); diff --git a/src/Parser/Parsing/Impl/DefinesStreamProxy.h b/src/Parser/Parsing/Impl/DefinesStreamProxy.h index 9208d09f..948bd1e3 100644 --- a/src/Parser/Parsing/Impl/DefinesStreamProxy.h +++ b/src/Parser/Parsing/Impl/DefinesStreamProxy.h @@ -77,7 +77,6 @@ private: static void ExtractParametersFromDefineUsage(const ParserLine& line, unsigned parameterStart, unsigned& parameterEnd, std::vector& parameterValues); bool FindDefineForWord(const ParserLine& line, unsigned wordStart, unsigned wordEnd, Define*& value); - void ExpandDefines(ParserLine& line); public: explicit DefinesStreamProxy(IParserLineStream* stream); @@ -85,6 +84,8 @@ public: void AddDefine(Define define); void Undefine(const std::string& name); + void ExpandDefines(ParserLine& line); + _NODISCARD std::unique_ptr ParseExpression(const std::string& expressionString) const; ParserLine NextLine() override; diff --git a/src/Parser/Parsing/Impl/ParserSingleInputStream.cpp b/src/Parser/Parsing/Impl/ParserSingleInputStream.cpp index 2641a29e..e561b2c2 100644 --- a/src/Parser/Parsing/Impl/ParserSingleInputStream.cpp +++ b/src/Parser/Parsing/Impl/ParserSingleInputStream.cpp @@ -61,5 +61,5 @@ bool ParserSingleInputStream::IsOpen() const bool ParserSingleInputStream::Eof() const { - return !m_stream.eof(); + return m_stream.eof(); } diff --git a/src/RawTemplater.lua b/src/RawTemplater.lua new file mode 100644 index 00000000..c553d4c5 --- /dev/null +++ b/src/RawTemplater.lua @@ -0,0 +1,50 @@ +RawTemplater = {} + +function RawTemplater:include(includes) + if includes:handle(self:name()) then + includedirs { + path.join(ProjectFolder(), "RawTemplater") + } + Utils:include(includes) + end +end + +function RawTemplater:link(links) + +end + +function RawTemplater:use() + dependson(self:name()) +end + +function RawTemplater:name() + return "RawTemplater" +end + +function RawTemplater:project() + local folder = ProjectFolder() + local includes = Includes:create() + local links = Links:create() + + project(self:name()) + targetdir(TargetDirectoryBin) + location "%{wks.location}/src/%{prj.name}" + kind "ConsoleApp" + language "C++" + + files { + path.join(folder, "RawTemplater/**.h"), + path.join(folder, "RawTemplater/**.cpp") + } + vpaths { + ["*"] = { + path.join(folder, "RawTemplater"), + } + } + + self:include(includes) + Parser:include(includes) + + links:linkto(Parser) + links:linkall() +end diff --git a/src/RawTemplater/RawTemplater.cpp b/src/RawTemplater/RawTemplater.cpp new file mode 100644 index 00000000..baab615e --- /dev/null +++ b/src/RawTemplater/RawTemplater.cpp @@ -0,0 +1,68 @@ +#include "RawTemplater.h" + +#include +#include +#include + +#include "RawTemplaterArguments.h" +#include "Templating/Templater.h" + +namespace fs = std::filesystem; + +class RawTemplater::Impl +{ + RawTemplaterArguments m_args; + + _NODISCARD bool GenerateCode(const std::string& filename) const + { + std::ifstream file(filename); + if (!file.is_open()) + { + std::cerr << "Failed to open file \"" << filename << "\"\n"; + return false; + } + + templating::Templater templater(file, filename); + if (!m_args.m_output_directory.empty()) + return templater.TemplateToDirectory(m_args.m_output_directory); + + const fs::path filePath(filename); + const auto parentPath = filePath.parent_path(); + + return templater.TemplateToDirectory(parentPath.string()); + } + +public: + Impl() + = default; + + int Run(const int argc, const char** argv) + { + if (!m_args.Parse(argc, argv)) + return 1; + + for(const auto& inputFile : m_args.m_input_files) + { + if (!GenerateCode(inputFile)) + return 1; + } + + return 0; + } +}; + +RawTemplater::RawTemplater() +{ + m_impl = new Impl(); +} + +RawTemplater::~RawTemplater() +{ + delete m_impl; + m_impl = nullptr; +} + +int RawTemplater::Run(const int argc, const char** argv) const +{ + return m_impl->Run(argc, argv); +} diff --git a/src/RawTemplater/RawTemplater.h b/src/RawTemplater/RawTemplater.h new file mode 100644 index 00000000..c068f3c9 --- /dev/null +++ b/src/RawTemplater/RawTemplater.h @@ -0,0 +1,17 @@ +#pragma once + +class RawTemplater +{ + class Impl; + Impl* m_impl; + +public: + RawTemplater(); + ~RawTemplater(); + RawTemplater(const RawTemplater& other) = delete; + RawTemplater(RawTemplater&& other) noexcept = default; + RawTemplater& operator=(const RawTemplater& other) = delete; + RawTemplater& operator=(RawTemplater&& other) noexcept = default; + + int Run(int argc, const char** argv) const; +}; diff --git a/src/RawTemplater/RawTemplaterArguments.cpp b/src/RawTemplater/RawTemplaterArguments.cpp new file mode 100644 index 00000000..63434d12 --- /dev/null +++ b/src/RawTemplater/RawTemplaterArguments.cpp @@ -0,0 +1,105 @@ +#include "RawTemplaterArguments.h" + +#include "Utils/Arguments/CommandLineOption.h" +#include "Utils/Arguments/UsageInformation.h" + +const CommandLineOption* const OPTION_HELP = CommandLineOption::Builder::Create() + .WithShortName("?") + .WithLongName("help") + .WithDescription("Displays usage information.") + .Build(); + +const CommandLineOption* const OPTION_VERBOSE = CommandLineOption::Builder::Create() + .WithShortName("v") + .WithLongName("verbose") + .WithDescription("Outputs a lot more and more detailed messages.") + .Build(); + +const CommandLineOption* const OPTION_OUTPUT_FOLDER = CommandLineOption::Builder::Create() + .WithShortName("o") + .WithLongName("output") + .WithDescription("Specify the folder to save the generated files. Defaults to the current directory.") + .WithParameter("outputPath") + .Build(); + +const CommandLineOption* const OPTION_DEFINE = CommandLineOption::Builder::Create() + .WithShortName("d") + .WithLongName("define") + .WithDescription("Adds a define for the templating process. Can be of format define or define=value.") + .WithParameter("defineValue") + .Reusable() + .Build(); + +const CommandLineOption* const COMMAND_LINE_OPTIONS[] +{ + OPTION_HELP, + OPTION_VERBOSE, + OPTION_OUTPUT_FOLDER, + OPTION_DEFINE +}; + +RawTemplaterArguments::RawTemplaterArguments() + : m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v), + m_verbose(false) +{ +} + +void RawTemplaterArguments::PrintUsage() +{ + UsageInformation usage("RawTemplater.exe"); + + for (const auto* commandLineOption : COMMAND_LINE_OPTIONS) + { + usage.AddCommandLineOption(commandLineOption); + } + + usage.Print(); +} + +bool RawTemplaterArguments::Parse(const int argc, const char** argv) +{ + if (!m_argument_parser.ParseArguments(argc - 1, &argv[1])) + { + PrintUsage(); + return false; + } + + // Check if the user requested help + if (m_argument_parser.IsOptionSpecified(OPTION_HELP)) + { + PrintUsage(); + return false; + } + + m_input_files = m_argument_parser.GetArguments(); + if (m_input_files.empty()) + { + PrintUsage(); + return false; + } + + // -v; --verbose + m_verbose = m_argument_parser.IsOptionSpecified(OPTION_VERBOSE); + + // -o; --output + if (m_argument_parser.IsOptionSpecified(OPTION_OUTPUT_FOLDER)) + m_output_directory = m_argument_parser.GetValueForOption(OPTION_OUTPUT_FOLDER); + else + m_output_directory = "."; + + // -d; --define + if (m_argument_parser.IsOptionSpecified(OPTION_DEFINE)) + { + for (const auto& arg : m_argument_parser.GetParametersForOption(OPTION_DEFINE)) + { + const auto separator = arg.find('='); + + if (separator != std::string::npos) + m_defines.emplace_back(std::make_pair(arg.substr(0, separator), arg.substr(separator + 1))); + else + m_defines.emplace_back(std::make_pair(arg, std::string())); + } + } + + return true; +} diff --git a/src/RawTemplater/RawTemplaterArguments.h b/src/RawTemplater/RawTemplaterArguments.h new file mode 100644 index 00000000..614d1daa --- /dev/null +++ b/src/RawTemplater/RawTemplaterArguments.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include "Utils/Arguments/ArgumentParser.h" + +class RawTemplaterArguments +{ + ArgumentParser m_argument_parser; + + /** + * \brief Prints a command line usage help text for the RawTemplater tool to stdout. + */ + static void PrintUsage(); + +public: + bool m_verbose; + + std::vector m_input_files; + std::string m_output_directory; + + std::vector> m_defines; + + RawTemplaterArguments(); + + bool Parse(int argc, const char** argv); +}; \ No newline at end of file diff --git a/src/RawTemplater/Templating/DirectiveEscapeStreamProxy.cpp b/src/RawTemplater/Templating/DirectiveEscapeStreamProxy.cpp new file mode 100644 index 00000000..04fbbeca --- /dev/null +++ b/src/RawTemplater/Templating/DirectiveEscapeStreamProxy.cpp @@ -0,0 +1,38 @@ +#include "DirectiveEscapeStreamProxy.h" + +using namespace templating; + +DirectiveEscapeStreamProxy::DirectiveEscapeStreamProxy(IParserLineStream* stream) + : m_stream(stream) +{ +} + +ParserLine DirectiveEscapeStreamProxy::NextLine() +{ + auto line = m_stream->NextLine(); + + if (line.m_line.size() >= 2 && line.m_line[0] == '#' && line.m_line[1] == '#') + line.m_line = line.m_line.substr(1); + + return line; +} + +bool DirectiveEscapeStreamProxy::IncludeFile(const std::string& filename) +{ + return m_stream->IncludeFile(filename); +} + +void DirectiveEscapeStreamProxy::PopCurrentFile() +{ + m_stream->PopCurrentFile(); +} + +bool DirectiveEscapeStreamProxy::IsOpen() const +{ + return m_stream->IsOpen(); +} + +bool DirectiveEscapeStreamProxy::Eof() const +{ + return m_stream->Eof(); +} diff --git a/src/RawTemplater/Templating/DirectiveEscapeStreamProxy.h b/src/RawTemplater/Templating/DirectiveEscapeStreamProxy.h new file mode 100644 index 00000000..1a2ada98 --- /dev/null +++ b/src/RawTemplater/Templating/DirectiveEscapeStreamProxy.h @@ -0,0 +1,20 @@ +#pragma once +#include "Parsing/Impl/AbstractDirectiveStreamProxy.h" + +namespace templating +{ + class DirectiveEscapeStreamProxy final : public AbstractDirectiveStreamProxy + { + public: + explicit DirectiveEscapeStreamProxy(IParserLineStream* stream); + + ParserLine NextLine() override; + bool IncludeFile(const std::string& filename) override; + void PopCurrentFile() override; + _NODISCARD bool IsOpen() const override; + _NODISCARD bool Eof() const override; + + private: + IParserLineStream* const m_stream; + }; +} diff --git a/src/RawTemplater/Templating/Templater.cpp b/src/RawTemplater/Templating/Templater.cpp new file mode 100644 index 00000000..0f926ced --- /dev/null +++ b/src/RawTemplater/Templating/Templater.cpp @@ -0,0 +1,354 @@ +#include "Templater.h" + +#include +#include +#include +#include +#include + +#include "Utils/ClassUtils.h" +#include "DirectiveEscapeStreamProxy.h" +#include "TemplatingStreamProxy.h" +#include "Parsing/ParsingException.h" +#include "Parsing/Impl/DefinesStreamProxy.h" +#include "Parsing/Impl/ParserSingleInputStream.h" + +using namespace templating; +namespace fs = std::filesystem; + +namespace templating +{ + class TemplatingPass + { + public: + TemplatingPass() + : m_stream(nullptr) + { + } + + TemplatingPass(std::istream& stream, const std::string& fileName, ITemplaterControl* templaterControl) + { + m_base_stream = std::make_unique(stream, fileName); + + m_templating_proxy = std::make_unique(m_base_stream.get(), templaterControl); + m_defines_proxy = std::make_unique(m_templating_proxy.get()); + m_directive_escape_proxy = std::make_unique(m_defines_proxy.get()); + + m_templating_proxy->SetDefinesProxy(m_defines_proxy.get()); + m_stream = m_directive_escape_proxy.get(); + } + + std::unique_ptr m_base_stream; + std::unique_ptr m_templating_proxy; + std::unique_ptr m_defines_proxy; + std::unique_ptr m_directive_escape_proxy; + IParserLineStream* m_stream; + }; + + enum class TemplatingVariationType + { + OPTIONS, + SWITCH + }; + + class ITemplatingVariation + { + protected: + ITemplatingVariation() = default; + + public: + virtual ~ITemplatingVariation() = default; + ITemplatingVariation(const ITemplatingVariation& other) = default; + ITemplatingVariation(ITemplatingVariation&& other) noexcept = default; + ITemplatingVariation& operator=(const ITemplatingVariation& other) = default; + ITemplatingVariation& operator=(ITemplatingVariation&& other) noexcept = default; + + virtual const std::string& GetName() = 0; + virtual void Advance() = 0; + virtual void Apply(DefinesStreamProxy* definesProxy) = 0; + _NODISCARD virtual bool IsFinished() const = 0; + _NODISCARD virtual TemplatingVariationType GetVariationType() const = 0; + }; + + class SwitchVariation final : public ITemplatingVariation + { + public: + explicit SwitchVariation(std::string name) + : m_name(std::move(name)), + m_should_define(true), + m_finished(false) + { + } + + const std::string& GetName() override + { + return m_name; + } + + void Advance() override + { + if (!m_should_define) + m_finished = true; + else + m_should_define = false; + } + + void Apply(DefinesStreamProxy* definesProxy) override + { + if (m_should_define) + definesProxy->AddDefine(DefinesStreamProxy::Define(m_name, "1")); + } + + _NODISCARD bool IsFinished() const override + { + return m_finished; + } + + _NODISCARD TemplatingVariationType GetVariationType() const override + { + return TemplatingVariationType::SWITCH; + } + + std::string m_name; + bool m_should_define; + bool m_finished; + }; + + class OptionsVariation final : public ITemplatingVariation + { + public: + OptionsVariation(std::string name, std::vector values) + : m_name(std::move(name)), + m_values(std::move(values)), + m_value_offset(0u) + { + } + + const std::string& GetName() override + { + return m_name; + } + + void Advance() override + { + m_value_offset++; + } + + void Apply(DefinesStreamProxy* definesProxy) override + { + if (m_value_offset < m_values.size()) + definesProxy->AddDefine(DefinesStreamProxy::Define(m_name, m_values[m_value_offset])); + } + + _NODISCARD bool IsFinished() const override + { + return m_value_offset >= m_values.size(); + } + + _NODISCARD TemplatingVariationType GetVariationType() const override + { + return TemplatingVariationType::OPTIONS; + } + + std::string m_name; + std::vector m_values; + size_t m_value_offset; + }; + + class TemplaterControlImpl final : ITemplaterControl + { + public: + TemplaterControlImpl(std::istream& stream, std::string filename, const std::string& outputDirectory) + : m_stream(stream), + m_filename(std::move(filename)), + m_output_directory(outputDirectory), + m_first_line(true), + m_write_output_to_file(false) + { + fs::path filenamePath(m_filename); + m_default_output_file = (m_output_directory / filenamePath.replace_extension()).string(); + } + + bool RunNextPass() + { + m_stream.clear(); + m_stream.seekg(0, std::ios::beg); + + m_output_file = m_default_output_file; + m_current_pass = TemplatingPass(m_stream, m_filename, this); + + for (const auto& activeVariation : m_active_variations) + activeVariation->Apply(m_current_pass.m_defines_proxy.get()); + + while (!m_current_pass.m_stream->Eof()) + { + auto nextLine = m_current_pass.m_stream->NextLine(); + + if (m_write_output_to_file) + { + if (m_first_line) + m_first_line = false; + else + m_output_stream << '\n'; + + m_output_stream << nextLine.m_line; + } + else + { + if (m_first_line) + m_first_line = false; + else + m_output_cache << '\n'; + + m_output_cache << nextLine.m_line; + } + } + + if (!m_write_output_to_file) + { + m_output_stream = std::ofstream(m_output_file); + if (!m_output_stream.is_open()) + { + std::cerr << "Failed to open output file \"" << m_output_file << "\"\n"; + return false; + } + + const auto cachedData = m_output_cache.str(); + if (!cachedData.empty()) + m_output_stream << cachedData; + } + + m_first_line = true; + m_write_output_to_file = false; + m_output_cache.clear(); + m_output_cache.str(std::string()); + m_output_stream.close(); + + return true; + } + + void AdvanceActiveVariations() + { + while (!m_active_variations.empty()) + { + const auto& lastVariation = m_active_variations[m_active_variations.size() - 1]; + lastVariation->Advance(); + + if (lastVariation->IsFinished()) + { + m_active_variations_by_name.erase(lastVariation->GetName()); + m_active_variations.pop_back(); + } + else + break; + } + } + + _NODISCARD bool HasActiveVariations() const + { + return !m_active_variations.empty(); + } + + protected: + bool AddSwitch(std::string switchName) override + { + const auto existingVariation = m_active_variations_by_name.find(switchName); + if (existingVariation != m_active_variations_by_name.end()) + return existingVariation->second->GetVariationType() == TemplatingVariationType::SWITCH; + + auto switchVariation = std::make_unique(std::move(switchName)); + if (m_current_pass.m_defines_proxy) + switchVariation->Apply(m_current_pass.m_defines_proxy.get()); + m_active_variations_by_name.emplace(switchVariation->m_name, switchVariation.get()); + m_active_variations.emplace_back(std::move(switchVariation)); + return true; + } + + bool AddOptions(std::string optionsName, std::vector optionValues) override + { + const auto existingVariation = m_active_variations_by_name.find(optionsName); + if (existingVariation != m_active_variations_by_name.end()) + return existingVariation->second->GetVariationType() == TemplatingVariationType::SWITCH; + + auto optionsVariation = std::make_unique(std::move(optionsName), std::move(optionValues)); + if (m_current_pass.m_defines_proxy) + optionsVariation->Apply(m_current_pass.m_defines_proxy.get()); + m_active_variations_by_name.emplace(optionsVariation->m_name, optionsVariation.get()); + m_active_variations.emplace_back(std::move(optionsVariation)); + return true; + } + + bool SetFileName(const std::string& fileName) override + { + if (m_write_output_to_file) + return false; + + m_output_file = fileName; + m_output_stream = std::ofstream(m_output_file); + if (!m_output_stream.is_open()) + { + std::cerr << "Failed to open output file \"" << m_output_file << "\"\n"; + return false; + } + + m_write_output_to_file = true; + const auto cachedData = m_output_cache.str(); + if (!cachedData.empty()) + m_output_stream << cachedData; + m_output_cache.clear(); + m_output_cache.str(std::string()); + + return true; + } + + private: + std::vector> m_active_variations; + std::unordered_map m_active_variations_by_name; + TemplatingPass m_current_pass; + + std::istream& m_stream; + std::string m_filename; + std::string m_output_file; + std::string m_default_output_file; + const fs::path m_output_directory; + + bool m_first_line; + bool m_write_output_to_file; + std::ofstream m_output_stream; + std::ostringstream m_output_cache; + }; +} + +Templater::Templater(std::istream& stream, std::string fileName) + : m_stream(stream), + m_file_name(std::move(fileName)) +{ +} + +bool Templater::TemplateToDirectory(const std::string& outputDirectory) +{ + TemplaterControlImpl control(m_stream, m_file_name, outputDirectory); + + try + { + if (!control.RunNextPass()) + return false; + + control.AdvanceActiveVariations(); + while (control.HasActiveVariations()) + { + if (!control.RunNextPass()) + return false; + + control.AdvanceActiveVariations(); + } + } + catch (ParsingException& e) + { + std::cerr << "Error: " << e.FullMessage() << std::endl; + + return false; + } + + return true; +} diff --git a/src/RawTemplater/Templating/Templater.h b/src/RawTemplater/Templating/Templater.h new file mode 100644 index 00000000..24a13acd --- /dev/null +++ b/src/RawTemplater/Templating/Templater.h @@ -0,0 +1,19 @@ +#pragma once +#include + +#include "Parsing/IParserLineStream.h" + +namespace templating +{ + class Templater + { + public: + Templater(std::istream& stream, std::string fileName); + + bool TemplateToDirectory(const std::string& outputDirectory); + + private: + std::istream& m_stream; + std::string m_file_name; + }; +} diff --git a/src/RawTemplater/Templating/TemplatingStreamProxy.cpp b/src/RawTemplater/Templating/TemplatingStreamProxy.cpp new file mode 100644 index 00000000..110b727b --- /dev/null +++ b/src/RawTemplater/Templating/TemplatingStreamProxy.cpp @@ -0,0 +1,196 @@ +#include "TemplatingStreamProxy.h" + +#include + +#include "Parsing/ParsingException.h" + +using namespace templating; + +TemplatingStreamProxy::TemplatingStreamProxy(IParserLineStream* stream, ITemplaterControl* templaterControl) + : m_stream(stream), + m_templater_control(templaterControl), + m_defines_proxy(nullptr) +{ +} + +void TemplatingStreamProxy::SetDefinesProxy(DefinesStreamProxy* definesProxy) +{ + m_defines_proxy = definesProxy; +} + +bool TemplatingStreamProxy::MatchSwitchDirective(const ParserLine& line, const unsigned directiveStartPosition, const unsigned directiveEndPosition) const +{ + auto currentPosition = directiveStartPosition; + + if (directiveEndPosition - directiveStartPosition != std::char_traits::length(PRAGMA_DIRECTIVE) + || !MatchString(line, currentPosition, PRAGMA_DIRECTIVE, std::char_traits::length(PRAGMA_DIRECTIVE))) + { + return false; + } + + if (!SkipWhitespace(line, currentPosition)) + return false; + + if (!MatchString(line, currentPosition, SWITCH_PRAGMA_COMMAND, std::char_traits::length(SWITCH_PRAGMA_COMMAND))) + return false; + + if (!SkipWhitespace(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid switch directive."); + + const auto nameStartPosition = currentPosition; + if (!ExtractIdentifier(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid switch directive."); + + auto name = line.m_line.substr(nameStartPosition, currentPosition - nameStartPosition); + + std::cout << "Switch: \"" << name << "\"\n"; + m_templater_control->AddSwitch(std::move(name)); + return true; +} + +bool TemplatingStreamProxy::MatchOptionsDirective(const ParserLine& line, const unsigned directiveStartPosition, const unsigned directiveEndPosition) const +{ + auto currentPosition = directiveStartPosition; + + if (directiveEndPosition - directiveStartPosition != std::char_traits::length(PRAGMA_DIRECTIVE) + || !MatchString(line, currentPosition, PRAGMA_DIRECTIVE, std::char_traits::length(PRAGMA_DIRECTIVE))) + { + return false; + } + + if (!SkipWhitespace(line, currentPosition)) + return false; + + if (!MatchString(line, currentPosition, OPTIONS_PRAGMA_COMMAND, std::char_traits::length(OPTIONS_PRAGMA_COMMAND))) + return false; + + if (!SkipWhitespace(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + const auto nameStartPosition = currentPosition; + if (!ExtractIdentifier(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + auto name = line.m_line.substr(nameStartPosition, currentPosition - nameStartPosition); + + if (!MatchNextCharacter(line, currentPosition, '(')) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + std::vector options; + + if (!SkipWhitespace(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + bool firstArg = true; + while (!MatchNextCharacter(line, currentPosition, ')')) + { + if (!firstArg && !MatchNextCharacter(line, currentPosition, ',')) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + if (!SkipWhitespace(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + const auto optionStartPosition = currentPosition; + if (!ExtractIdentifier(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + std::ostringstream optionValueBuilder; + optionValueBuilder << '"' << line.m_line.substr(optionStartPosition, currentPosition - optionStartPosition) << '"'; + options.emplace_back(optionValueBuilder.str()); + + firstArg = false; + } + + std::cout << "Options: \"" << name << "\" with values:\n"; + for (const auto& option : options) + std::cout << " Value: " << option << "\n"; + + m_templater_control->AddOptions(std::move(name), std::move(options)); + return true; +} + +bool TemplatingStreamProxy::MatchFilenameDirective(const ParserLine& line, const unsigned directiveStartPosition, const unsigned directiveEndPosition) const +{ + auto currentPosition = directiveStartPosition; + + if (directiveEndPosition - directiveStartPosition != std::char_traits::length(PRAGMA_DIRECTIVE) + || !MatchString(line, currentPosition, PRAGMA_DIRECTIVE, std::char_traits::length(PRAGMA_DIRECTIVE))) + { + return false; + } + + if (!SkipWhitespace(line, currentPosition)) + return false; + + if (!MatchString(line, currentPosition, FILENAME_PRAGMA_COMMAND, std::char_traits::length(FILENAME_PRAGMA_COMMAND))) + return false; + + if (!SkipWhitespace(line, currentPosition)) + throw ParsingException(CreatePos(line, currentPosition), "Invalid options directive."); + + const auto expressionString = line.m_line.substr(currentPosition, line.m_line.size() - currentPosition); + if (expressionString.empty()) + throw ParsingException(CreatePos(line, currentPosition), "Cannot pragma filename without an expression."); + + ParserLine expressionStringAsLine(line.m_filename, line.m_line_number, expressionString); + m_defines_proxy->ExpandDefines(expressionStringAsLine); + + const auto expression = m_defines_proxy->ParseExpression(expressionStringAsLine.m_line); + if (!expression) + throw ParsingException(CreatePos(line, currentPosition), "Failed to parse pragma filename expression"); + + if (!expression->IsStatic()) + throw ParsingException(CreatePos(line, currentPosition), "pragma filename expression must be static"); + + const auto value = expression->EvaluateStatic(); + + if (value.m_type != SimpleExpressionValue::Type::STRING) + throw ParsingException(CreatePos(line, currentPosition), "pragma filename expression must evaluate to string"); + + std::cout << "Filename: \"" << *value.m_string_value << "\"\n"; + m_templater_control->SetFileName(*value.m_string_value); + return true; +} + +bool TemplatingStreamProxy::MatchDirectives(const ParserLine& line) +{ + unsigned directiveStartPos, directiveEndPos; + + if (!FindDirective(line, directiveStartPos, directiveEndPos)) + return false; + + directiveStartPos++; + + return MatchSwitchDirective(line, directiveStartPos, directiveEndPos) + || MatchOptionsDirective(line, directiveStartPos, directiveEndPos) + || MatchFilenameDirective(line, directiveStartPos, directiveEndPos); +} + +ParserLine TemplatingStreamProxy::NextLine() +{ + auto line = m_stream->NextLine(); + + while (MatchDirectives(line)) + line = m_stream->NextLine(); + + return line; +} + +bool TemplatingStreamProxy::IncludeFile(const std::string& filename) +{ + return m_stream->IncludeFile(filename); +} + +void TemplatingStreamProxy::PopCurrentFile() +{ + m_stream->PopCurrentFile(); +} + +bool TemplatingStreamProxy::IsOpen() const +{ + return m_stream->IsOpen(); +} + +bool TemplatingStreamProxy::Eof() const +{ + return m_stream->Eof(); +} diff --git a/src/RawTemplater/Templating/TemplatingStreamProxy.h b/src/RawTemplater/Templating/TemplatingStreamProxy.h new file mode 100644 index 00000000..d6170c7d --- /dev/null +++ b/src/RawTemplater/Templating/TemplatingStreamProxy.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Utils/ClassUtils.h" +#include "Parsing/Impl/AbstractDirectiveStreamProxy.h" +#include "Parsing/Impl/DefinesStreamProxy.h" + +namespace templating +{ + class ITemplaterControl + { + protected: + ITemplaterControl() = default; + + public: + virtual ~ITemplaterControl() = default; + ITemplaterControl(const ITemplaterControl& other) = default; + ITemplaterControl(ITemplaterControl&& other) noexcept = default; + ITemplaterControl& operator=(const ITemplaterControl& other) = default; + ITemplaterControl& operator=(ITemplaterControl&& other) noexcept = default; + + virtual bool AddSwitch(std::string switchName) = 0; + virtual bool AddOptions(std::string optionsName, std::vector optionValues) = 0; + virtual bool SetFileName(const std::string& fileName) = 0; + }; + + class TemplatingStreamProxy final : public AbstractDirectiveStreamProxy + { + public: + TemplatingStreamProxy(IParserLineStream* stream, ITemplaterControl* templaterControl); + + void SetDefinesProxy(DefinesStreamProxy* definesProxy); + + ParserLine NextLine() override; + bool IncludeFile(const std::string& filename) override; + void PopCurrentFile() override; + _NODISCARD bool IsOpen() const override; + _NODISCARD bool Eof() const override; + + private: + static constexpr const char* PRAGMA_DIRECTIVE = "pragma"; + static constexpr const char* SWITCH_PRAGMA_COMMAND = "switch"; + static constexpr const char* OPTIONS_PRAGMA_COMMAND = "options"; + static constexpr const char* FILENAME_PRAGMA_COMMAND = "filename"; + + _NODISCARD bool MatchSwitchDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition) const; + _NODISCARD bool MatchOptionsDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition) const; + _NODISCARD bool MatchFilenameDirective(const ParserLine& line, unsigned directiveStartPosition, unsigned directiveEndPosition) const; + bool MatchDirectives(const ParserLine& line); + + IParserLineStream* const m_stream; + ITemplaterControl* const m_templater_control; + DefinesStreamProxy* m_defines_proxy; + }; +} diff --git a/src/RawTemplater/main.cpp b/src/RawTemplater/main.cpp new file mode 100644 index 00000000..afdf94b9 --- /dev/null +++ b/src/RawTemplater/main.cpp @@ -0,0 +1,7 @@ +#include "RawTemplater.h" + +int main(const int argc, const char** argv) +{ + const RawTemplater rawTemplater; + return rawTemplater.Run(argc, argv); +} diff --git a/tools/scripts/raw.lua b/tools/scripts/raw.lua index 9bab116f..2444808f 100644 --- a/tools/scripts/raw.lua +++ b/tools/scripts/raw.lua @@ -39,7 +39,7 @@ function Raw:project() } } - filter "files:**" + filter "files:not **/*.template" buildmessage 'Copying rawfile %{file.relpath}' buildcommands { -- Relpath contains two .. so build/raw is getting reverted in the target path @@ -50,4 +50,14 @@ function Raw:project() "%{cfg.targetdir}/build/raw/%{file.relpath}" } filter {} + + filter "files:**/*.template" + buildmessage 'Templating %{file.relpath}' + buildcommands { + "echo \"%{cfg.targetdir}/build/raw/%{file.reldirectory}\"" + } + buildoutputs { + "%{cfg.targetdir}/build/raw/%{file.relpath}" + } + filter {} end