Serenity Operating System
at master 128 lines 3.7 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#pragma once 8 9#include <AK/DeprecatedString.h> 10#include <AK/GenericLexer.h> 11#include <AK/HashMap.h> 12#include <AK/StringBuilder.h> 13 14namespace AK { 15 16class SourceGenerator { 17 AK_MAKE_NONCOPYABLE(SourceGenerator); 18 19public: 20 using MappingType = HashMap<StringView, DeprecatedString>; 21 22 explicit SourceGenerator(StringBuilder& builder, char opening = '@', char closing = '@') 23 : m_builder(builder) 24 , m_opening(opening) 25 , m_closing(closing) 26 { 27 } 28 explicit SourceGenerator(StringBuilder& builder, MappingType const& mapping, char opening = '@', char closing = '@') 29 : m_builder(builder) 30 , m_mapping(mapping) 31 , m_opening(opening) 32 , m_closing(closing) 33 { 34 } 35 36 SourceGenerator(SourceGenerator&&) = default; 37 38 SourceGenerator fork() { return SourceGenerator { m_builder, m_mapping, m_opening, m_closing }; } 39 40 void set(StringView key, DeprecatedString value) 41 { 42 if (key.contains(m_opening) || key.contains(m_closing)) { 43 warnln("SourceGenerator keys cannot contain the opening/closing delimiters `{}` and `{}`. (Keys are only wrapped in these when using them, not when setting them.)", m_opening, m_closing); 44 VERIFY_NOT_REACHED(); 45 } 46 m_mapping.set(key, move(value)); 47 } 48 49 DeprecatedString get(StringView key) const 50 { 51 auto result = m_mapping.get(key); 52 if (!result.has_value()) { 53 warnln("No key named `{}` set on SourceGenerator", key); 54 VERIFY_NOT_REACHED(); 55 } 56 return result.release_value(); 57 } 58 59 StringView as_string_view() const { return m_builder.string_view(); } 60 DeprecatedString as_string() const { return m_builder.to_deprecated_string(); } 61 62 void append(StringView pattern) 63 { 64 GenericLexer lexer { pattern }; 65 66 while (!lexer.is_eof()) { 67 // FIXME: It is a bit inconvenient, that 'consume_until' also consumes the 'stop' character, this makes 68 // the method less generic because there is no way to check if the 'stop' character ever appeared. 69 auto const consume_until_without_consuming_stop_character = [&](char stop) { 70 return lexer.consume_while([&](char ch) { return ch != stop; }); 71 }; 72 73 m_builder.append(consume_until_without_consuming_stop_character(m_opening)); 74 75 if (lexer.consume_specific(m_opening)) { 76 auto const placeholder = consume_until_without_consuming_stop_character(m_closing); 77 78 if (!lexer.consume_specific(m_closing)) 79 VERIFY_NOT_REACHED(); 80 81 m_builder.append(get(placeholder)); 82 } else { 83 VERIFY(lexer.is_eof()); 84 } 85 } 86 } 87 88 void appendln(StringView pattern) 89 { 90 append(pattern); 91 m_builder.append('\n'); 92 } 93 94 template<size_t N> 95 DeprecatedString get(char const (&key)[N]) 96 { 97 return get(StringView { key, N - 1 }); 98 } 99 100 template<size_t N> 101 void set(char const (&key)[N], DeprecatedString value) 102 { 103 set(StringView { key, N - 1 }, value); 104 } 105 106 template<size_t N> 107 void append(char const (&pattern)[N]) 108 { 109 append(StringView { pattern, N - 1 }); 110 } 111 112 template<size_t N> 113 void appendln(char const (&pattern)[N]) 114 { 115 appendln(StringView { pattern, N - 1 }); 116 } 117 118private: 119 StringBuilder& m_builder; 120 MappingType m_mapping; 121 char m_opening, m_closing; 122}; 123 124} 125 126#if USING_AK_GLOBALLY 127using AK::SourceGenerator; 128#endif