///$(which true);FLAGS="-g -Wall -Wextra --std=c++23 -O3";c++ $FLAGS -o weave "$0" || { echo "Compilation failed"; exit 1; }; ./weave; exit 0 #include #include #include #include #include #include #include #include #include #include #include #include #include // Type aliases for convenience using u8 = uint8_t; using u16 = uint16_t; using u32 = uint32_t; using u64 = uint64_t; using usize = std::size_t; using i8 = int8_t; using i16 = int16_t; using i32 = int32_t; using i64 = int64_t; using isize = std::ptrdiff_t; using f32 = float; using f64 = double; template using RefT = std::reference_wrapper; template struct overloads : Ts... { using Ts::operator()...; }; class CowStr { std::variant data_; public: CowStr() : data_(std::string_view{}) {} CowStr(std::string_view s) : data_(s) {} CowStr(const char* s) : data_(std::string_view{s}) {} CowStr(std::string s) : data_(std::move(s)) {} [[nodiscard]] bool is_borrowed() const { return std::holds_alternative(data_); } [[nodiscard]] bool is_owned() const { return std::holds_alternative(data_); } [[nodiscard]] std::string_view view() const { if (const auto* borrowed = std::get_if(&data_)) { return *borrowed; } return std::get(data_); } [[nodiscard]] const char* c_str() { if (auto* borrowed = std::get_if(&data_)) { data_ = std::string(*borrowed); } return std::get(data_).c_str(); } [[nodiscard]] std::string into_owned() && { if (auto* owned = std::get_if(&data_)) { return std::move(*owned); } return std::string(std::get(data_)); } [[nodiscard]] std::string to_owned() const { if (const auto* owned = std::get_if(&data_)) { return *owned; } return std::string(std::get(data_)); } std::string& to_mut() { if (auto* borrowed = std::get_if(&data_)) { data_ = std::string(*borrowed); } return std::get(data_); } }; namespace json { static void append_utf8(std::string &out, u32 cp) { if (cp <= 0x7F) { out.push_back(static_cast(cp)); } else if (cp <= 0x7FF) { out.push_back(static_cast(0xC0 | ((cp >> 6) & 0x1F))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } else if (cp <= 0xFFFF) { out.push_back(static_cast(0xE0 | ((cp >> 12) & 0x0F))); out.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } else { out.push_back(static_cast(0xF0 | ((cp >> 18) & 0x07))); out.push_back(static_cast(0x80 | ((cp >> 12) & 0x3F))); out.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); out.push_back(static_cast(0x80 | (cp & 0x3F))); } } static int hex_value(char c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return 10 + (c - 'a'); if ('A' <= c && c <= 'F') return 10 + (c - 'A'); return -1; } // Unescape a JSON string literal body (without surrounding quotes). // Throws std::runtime_error on malformed escape sequences. std::string json_unescape(const std::string &in) { std::string out; out.reserve(in.size()); for (size_t i = 0; i < in.size(); ++i) { char c = in[i]; if (c != '\\') { out.push_back(c); continue; } // Escape sequence if (++i >= in.size()) throw std::runtime_error("Invalid escape: trailing backslash"); char e = in[i]; switch (e) { case '"': out.push_back('"'); break; case '\\': out.push_back('\\'); break; case '/': out.push_back('/'); break; case 'b': out.push_back('\b'); break; case 'f': out.push_back('\f'); break; case 'n': out.push_back('\n'); break; case 'r': out.push_back('\r'); break; case 't': out.push_back('\t'); break; case 'u': { // parse 4 hex digits if (i + 4 >= in.size()) throw std::runtime_error("Invalid \\u escape: too short"); int v = 0; for (int k = 1; k <= 4; ++k) { int hv = hex_value(in[i + k]); if (hv < 0) throw std::runtime_error("Invalid hex digit in \\u escape"); v = (v << 4) | hv; } i += 4; // consumed 4 hex digits uint32_t codepoint = static_cast(v); // handle UTF-16 surrogate pair if (0xD800 <= codepoint && codepoint <= 0xDBFF) { // high surrogate; expect another \uXXXX for low surrogate if (i + 2 >= in.size() || in[i + 1] != '\\' || in[i + 2] != 'u') throw std::runtime_error("Missing low surrogate after high surrogate"); // parse low surrogate if (i + 6 >= in.size()) throw std::runtime_error("Invalid low surrogate \\u escape"); int lv = 0; for (int k = 3; k <= 6; ++k) { int hv = hex_value(in[i + k]); if (hv < 0) throw std::runtime_error("Invalid hex digit in low surrogate"); lv = (lv << 4) | hv; } i += 6; // consumed '\' 'u' and 4 hex digits (positions i+1..i+6) uint32_t low = static_cast(lv); if (!(0xDC00 <= low && low <= 0xDFFF)) throw std::runtime_error("Invalid low surrogate value"); // combine surrogates to code point codepoint = 0x10000 + (((codepoint - 0xD800) << 10) | (low - 0xDC00)); } append_utf8(out, codepoint); break; } default: throw std::runtime_error(std::string("Invalid escape character: \\") + e); } } return out; } std::string json_escape(const std::string_view &str) { std::ostringstream ss; for (const auto &c : str) { switch (c) { case '\"': ss << "\\\""; break; case '\\': ss << "\\\\"; break; case '\b': ss << "\\b"; break; case '\f': ss << "\\f"; break; case '\n': ss << "\\n"; break; case '\r': ss << "\\r"; break; case '\t': ss << "\\t"; break; default: if (c >= 0 && c <= 0x1F) { ss << "\\u" << std::hex << std::uppercase << (int)c << std::dec << std::nouppercase; } else { ss << c; } } } return ss.str(); } enum class JsonType { Null, Boolean, Number, String, Array, Object, }; struct JsonValue; struct JsonObject { std::unordered_map members; JsonObject(std::unordered_map members) : members(std::move(members)) {} auto get_field(const std::string_view &key) const -> std::optional>; auto get_field_mut(const std::string_view &key) -> std::optional>; auto insert_field(const std::string_view &key, const JsonValue &value) -> void; }; struct JsonNull {}; struct JsonNumber { std::string_view value; JsonNumber(std::string_view value) : value(value) {} }; struct JsonValue { std::variant, JsonObject> value; JsonValue() : value(JsonNull{}) {} JsonValue(JsonNull null) : value(null) {} JsonValue(bool boolean) : value(boolean) {} JsonValue(const std::string_view& str) : value(str) {} JsonValue(JsonNumber number) : value(number) {} JsonValue(std::vector array) : value(std::move(array)) {} JsonValue(JsonObject object) : value(std::move(object)) {} auto is_null() const -> bool { return std::holds_alternative(value); } auto is_boolean() const -> bool { return std::holds_alternative(value); } auto is_string() const -> bool { return std::holds_alternative(value); } auto is_number() const -> bool { return std::holds_alternative(value); } auto is_array() const -> bool { return std::holds_alternative>(value); } auto is_object() const -> bool { return std::holds_alternative(value); } auto as_null() const -> std::optional { if (auto pval = std::get_if(&value)) { return *pval; } else { return std::nullopt; } } auto as_boolean() const -> std::optional { if (auto pval = std::get_if(&value)) { return *pval; } else { return std::nullopt; } } auto as_string() const -> std::optional { if (auto pval = std::get_if(&value)) { return *pval; } else { return std::nullopt; } } auto as_number() const -> std::optional> { if (auto pval = std::get_if(&value)) { return std::ref(*pval); } else { return std::nullopt; } } auto as_array() const -> std::optional>> { if (auto pval = std::get_if>(&value)) { return std::ref(*pval); } else { return std::nullopt; } } auto as_object() const -> std::optional> { if (auto pval = std::get_if(&value)) { return std::ref(*pval); } else { return std::nullopt; } } friend auto operator<<(std::ostream &os, const JsonValue& val) -> std::ostream &; }; auto operator<<(std::ostream &os, const JsonValue& val) -> std::ostream & { const auto visitor = overloads{ [&](JsonNull _) { os << "null"; }, [&](bool b) { os << (b ? "true" : "false"); }, [&](const JsonNumber &num) { os << num.value; }, [&](const std::string_view &str) { os << "\"" << str << "\""; }, [&](const JsonObject &obj) { os << "{"; auto first = true; for (const auto &[key, value] : obj.members) { if (!first) { os << ","; } os << "\"" << key << "\": " << value; first = false; } os << "}"; }, [&](const std::vector &array) { os << "["; auto first = true; for (const auto &value : array) { if (!first) { os << ","; } os << value; first = false; } os << "]"; } }; std::visit(visitor, val.value); return os; } auto JsonObject::get_field(const std::string_view &key) const -> std::optional> { auto it = members.find(key); if (it != members.end()) { return std::ref(it->second); } else { return std::nullopt; } } auto JsonObject::get_field_mut(const std::string_view &key) -> std::optional> { auto it = members.find(key); if (it != members.end()) { return std::ref(it->second); } else { return std::nullopt; } } auto JsonObject::insert_field(const std::string_view &key, const JsonValue &value) -> void { members[key] = value; } } struct Source { std::string_view path; std::string_view flags; std::string_view defines; Source(std::string_view path) : path(std::move(path)), flags(""), defines("") {} // rule of five Source(const Source &) = delete; Source(Source &&) = delete; Source &operator=(const Source &) = delete; Source &operator=(Source &&) = delete; }; struct Project { std::string_view name; std::vector sources; std::unordered_map project_defines; std::vector project_flags; // rule of five Project(const Project &) = delete; Project(Project &&) = delete; Project &operator=(const Project &) = delete; Project &operator=(Project &&) = delete; Project(std::string_view name) : name(std::move(name)), sources({}), project_defines({}), project_flags({}) {} auto add_source(std::string_view source) -> void { sources.push_back(std::move(source)); } auto into_compile_commands(const std::string_view& working_dir) -> json::JsonValue { std::vector compile_commands{}; const auto project_args = [&]() -> std::vector { std::vector args{}; for (const auto &flag : project_flags) { args.push_back(json::JsonValue(flag)); } for (const auto &[key, value] : project_defines) { args.push_back(json::JsonValue("-D" + key + "=" + value)); } return args; }(); for (const auto &source : sources) { std::printf("Processing source: %s\n", source.data()); auto fields = std::unordered_map{}; fields["directory"] = json::JsonValue(working_dir); fields["file"] = json::JsonValue(source); fields["arguments"] = json::JsonValue(std::vector{ json::JsonValue(std::string_view("g++")), json::JsonValue(std::string_view("-c")), json::JsonValue(std::string_view(source)), json::JsonValue(std::string_view("-o")), json::JsonValue(std::string_view(std::string(source) + std::string(".o"))), json::JsonValue(std::vector(project_args))}); compile_commands.push_back(json::JsonValue(json::JsonObject(fields))); } return compile_commands; } }; static auto ninja_escape(std::string_view s) -> std::string { std::string out; out.reserve(s.size()); for (char c : s) { switch (c) { case '$': out += "$$"; break; case ':': out += "$:"; break; case ' ': out += "$ "; break; case '\n': out += "$\n"; break; default: out.push_back(c); break; } } return out; } static auto shell_join_arguments(const std::vector &args) -> std::string { std::string out; bool first = true; for (const auto &arg0 : args) { if (auto&& arg = arg0.as_string()) { if (!first) { out.push_back(' '); } const bool needs_quotes = arg->find_first_of(" \t\n\"'\\$") != std::string_view::npos; if (!needs_quotes) { out.append(arg->begin(), arg->end()); } else { out.push_back('"'); for (char c : *arg) { if (c == '"' || c == '\\') { out.push_back('\\'); } out.push_back(c); } out.push_back('"'); } first = false; } else { throw std::runtime_error("compile_commands entry has non-string argument"); } } return out; } static auto default_object_path(std::string_view file) -> std::string { return std::string(file) + ".o"; } auto compile_commands_into_ninja(const json::JsonValue &compile_commands) -> void { if (auto arr = compile_commands.as_array()) { std::ostringstream ninja; ninja << "ninja_required_version = 1.3\n\n"; ninja << "rule cc\n"; ninja << " command = $cmd\n"; ninja << " description = CC $in\n\n"; for (const auto &entry : arr->get()) { const json::JsonObject& obj = entry.as_object().value(); const json::JsonValue& file_field = obj.get_field("file").value(); auto file = file_field.as_string().value(); auto output = default_object_path(file); if (const auto &output_field = obj.get_field("output")) { if (const auto out = output_field->get().as_string()) { output = std::string(*out); } else { throw std::runtime_error("'output' must be a string"); } } std::string cmd{}; if (auto args_field = obj.get_field("arguments")) { cmd = shell_join_arguments(args_field->get().as_array().value()); } else if (auto command_field = obj.get_field("command")) { cmd = command_field->get().as_string().value(); } else { throw std::runtime_error( "compile_commands entry must contain either 'arguments' or 'command'"); } ninja << "build " << ninja_escape(output) << ": cc " << ninja_escape(file) << "\n"; ninja << " cmd = " << ninja_escape(cmd) << "\n\n"; } std::cout << ninja.str(); } else { throw std::runtime_error("compile_commands root must be an array"); } } #include #include auto main() -> int { std::printf("Hello, weave!\n"); // Example usage Project project("MyProject"); project.add_source("src/main.cc"); project.project_flags.append_range(std::list{"-g", "-Wall", "-Wextra", "--std=c++26", "-O3"}); auto compile_commands_json = project.into_compile_commands("/var/code/cxx/loom/"); std::cout << compile_commands_json << std::endl; // std::ofstream os("compile_commands.json"); // os << compile_commands_json; // os.close(); return 0; }