#include "kappa/dsl/system.hpp" #include "kappa/dsl/error.hpp" #include "kappa/dsl/lexer.hpp" #include #include #include #include #include namespace kappa::dsl { class SysParser { public: explicit SysParser(std::string_view source) : lexer_(source) { advance(); } SystemConfig parse(); private: void advance(); Token consume(TokenType type); void skip_newlines(); bool at(TokenType type) const { return current_.type == type; } bool parse_bool(); void parse_system_block(SystemConfig& cfg); void parse_packages_block(SystemConfig& cfg); void parse_services_block(SystemConfig& cfg); void parse_boot_block(SystemConfig& cfg); void parse_users_block(SystemConfig& cfg); std::string consume_ident(); std::string consume_string(); Token current_; Lexer lexer_; }; void SysParser::advance() { current_ = lexer_.next(); } void SysParser::skip_newlines() { while (at(TokenType::Newline)) { advance(); } } Token SysParser::consume(TokenType type) { if (!at(type)) { if (type == TokenType::Rbrace && at(TokenType::Eof)) { throw ParseError(current_.line, current_.col, msg_unclosed_block("block")); } if (type == TokenType::String && at(TokenType::Ident)) { throw ParseError(current_.line, current_.col, msg_not_a_string()); } throw ParseError(current_.line, current_.col, msg_expected(token_name(type), token_name(current_.type))); } Token t = std::move(current_); advance(); return t; } std::string SysParser::consume_ident() { if (at(TokenType::Ident) || at(TokenType::KwEnv) || at(TokenType::KwService) || at(TokenType::KwAssert) || at(TokenType::KwImport) || at(TokenType::KwSha256) || at(TokenType::KwSha512) || at(TokenType::KwMd5) || at(TokenType::KwConfig) || at(TokenType::KwFeatures) || at(TokenType::KwVersion) || at(TokenType::KwSource) || at(TokenType::KwLicense) || at(TokenType::KwPatches) || at(TokenType::KwConst) || at(TokenType::KwPackage) || at(TokenType::KwDepends) || at(TokenType::KwProvides) || at(TokenType::KwOutputs) || at(TokenType::KwPrepare) || at(TokenType::KwBuild) || at(TokenType::KwCheck) || at(TokenType::KwInstall) || at(TokenType::KwTrue) || at(TokenType::KwFalse)) { auto lexeme = current_.lexeme; advance(); return lexeme; } throw ParseError(current_.line, current_.col, std::format("expected identifier, got '{}'", token_name(current_.type))); } std::string SysParser::consume_string() { if (at(TokenType::KwTrue)) { advance(); return "true"; } if (at(TokenType::KwFalse)) { advance(); return "false"; } if (at(TokenType::Ident)) { auto v = current_.lexeme; advance(); return v; } return consume(TokenType::String).lexeme; } bool SysParser::parse_bool() { if (at(TokenType::KwTrue)) { advance(); return true; } if (at(TokenType::KwFalse)) { advance(); return false; } throw ParseError(current_.line, current_.col, std::format("expected 'true' or 'false', got '{}'", token_name(current_.type))); } SystemConfig SysParser::parse() { skip_newlines(); SystemConfig cfg; while (!at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Eof)) { break; } auto kw = consume_ident(); if (kw == "imports") { consume(TokenType::Equals); consume(TokenType::Lbracket); skip_newlines(); while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) { cfg.imports.push_back(consume(TokenType::String).lexeme); skip_newlines(); if (at(TokenType::Comma)) { consume(TokenType::Comma); } skip_newlines(); } consume(TokenType::Rbracket); } else if (kw == "assert") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } Assertion a; a.message = consume(TokenType::String).lexeme; consume(TokenType::Ident); // ":" a.field = current_.lexeme; advance(); if (at(TokenType::Equals)) { advance(); if (at(TokenType::Equals)) { a.op = "=="; advance(); } else { a.op = "="; } } else if (at(TokenType::Ident) && current_.lexeme == "!") { advance(); consume(TokenType::Equals); a.op = "!="; } else { a.op = current_.lexeme; advance(); } if (at(TokenType::String)) { a.value = consume(TokenType::String).lexeme; } else if (at(TokenType::KwTrue)) { a.value = "true"; advance(); } else if (at(TokenType::KwFalse)) { a.value = "false"; advance(); } else { a.value = current_.lexeme; advance(); } cfg.assertions.push_back(std::move(a)); skip_newlines(); } consume(TokenType::Rbrace); } else if (kw == "system") { parse_system_block(cfg); } else if (kw == "packages") { parse_packages_block(cfg); } else if (kw == "services") { parse_services_block(cfg); } else if (kw == "boot") { parse_boot_block(cfg); } else if (kw == "users") { parse_users_block(cfg); } else { throw ParseError(current_.line, current_.col, std::format("unknown section '{}'", kw)); } } return cfg; } void SysParser::parse_system_block(SystemConfig& cfg) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Rbrace)) { break; } auto key = consume_ident(); if (key == "hostname") { consume(TokenType::Equals); cfg.system.hostname = consume_string(); } else if (key == "timezone") { consume(TokenType::Equals); cfg.system.timezone = consume_string(); } else if (key == "env") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); bool soft = false; if (at(TokenType::Ident) && current_.lexeme == "?") { advance(); consume(TokenType::Equals); soft = true; } else { consume(TokenType::Equals); } cfg.system.env.push_back({std::move(k), consume_string(), soft}); skip_newlines(); } consume(TokenType::Rbrace); } else if (key == "config") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); consume(TokenType::Equals); cfg.system.config[k] = consume_string(); skip_newlines(); } consume(TokenType::Rbrace); } else if (key == "features") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); consume(TokenType::Equals); cfg.system.features[k] = FeatureDef{parse_bool(), false, ""}; skip_newlines(); } consume(TokenType::Rbrace); } else if (key == "rollback") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); consume(TokenType::Equals); if (k == "keep") { cfg.system.rollback.keep = parse_int(current_.line, current_.col, current_.lexeme); advance(); } skip_newlines(); } consume(TokenType::Rbrace); } else { throw ParseError(current_.line, current_.col, std::format("unknown system key '{}'", key)); } } consume(TokenType::Rbrace); } void SysParser::parse_packages_block(SystemConfig& cfg) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Rbrace)) { break; } PackageRef pkg; pkg.name = consume_ident(); if (at(TokenType::Lbrace)) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Rbrace)) { break; } auto key = consume_ident(); if (key == "version") { consume(TokenType::Equals); pkg.version = consume_string(); } else if (key == "features") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); consume(TokenType::Equals); pkg.features[k] = FeatureDef{parse_bool(), false, ""}; skip_newlines(); } consume(TokenType::Rbrace); } else if (key == "config") { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto k = consume_ident(); consume(TokenType::Equals); pkg.config[k] = current_.lexeme; advance(); skip_newlines(); } consume(TokenType::Rbrace); } } consume(TokenType::Rbrace); } cfg.packages.push_back(std::move(pkg)); } consume(TokenType::Rbrace); } void SysParser::parse_services_block(SystemConfig& cfg) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Rbrace)) { break; } ServiceRef svc; svc.name = consume_ident(); if (at(TokenType::Lbrace)) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto key = consume_ident(); if (key == "enable") { consume(TokenType::Equals); svc.enable = parse_bool(); } else { consume(TokenType::Equals); svc.config[key] = current_.lexeme; advance(); } skip_newlines(); } consume(TokenType::Rbrace); } cfg.services.push_back(std::move(svc)); } consume(TokenType::Rbrace); } void SysParser::parse_boot_block(SystemConfig& cfg) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Rbrace)) { break; } auto key = consume_ident(); consume(TokenType::Equals); auto val = consume_string(); if (key == "kernel") { cfg.boot.kernel = std::move(val); } else if (key == "init") { cfg.boot.init = std::move(val); } else if (key == "efi") { cfg.boot.efi = std::move(val); } else if (key == "swap") { cfg.boot.swap = std::move(val); } else if (key == "root") { cfg.boot.root = std::move(val); } else if (key == "bootloader"){ cfg.boot.bootloader = std::move(val); } else { cfg.boot.params[key] = std::move(val); } skip_newlines(); } consume(TokenType::Rbrace); } void SysParser::parse_users_block(SystemConfig& cfg) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { skip_newlines(); if (at(TokenType::Rbrace)) { break; } UserRef u; u.name = consume_ident(); if (at(TokenType::Lbrace)) { consume(TokenType::Lbrace); skip_newlines(); while (!at(TokenType::Rbrace) && !at(TokenType::Eof)) { if (at(TokenType::Newline)) { advance(); continue; } auto key = consume_ident(); if (key == "shell") { consume(TokenType::Equals); u.shell = consume_string(); } else if (key == "groups") { consume(TokenType::Equals); consume(TokenType::Lbracket); skip_newlines(); while (!at(TokenType::Rbracket) && !at(TokenType::Eof)) { u.groups.push_back(consume_string()); skip_newlines(); if (at(TokenType::Comma)) { consume(TokenType::Comma); } skip_newlines(); } consume(TokenType::Rbracket); } else { consume(TokenType::Equals); u.extra[key] = consume_string(); } skip_newlines(); } consume(TokenType::Rbrace); } cfg.users.push_back(std::move(u)); } consume(TokenType::Rbrace); } SystemConfig parse_system_config(std::string_view source) { SysParser p(source); return p.parse(); } static void merge_config(SystemConfig& base, SystemConfig&& imported) { if (!imported.system.hostname.empty()) { base.system.hostname = std::move(imported.system.hostname); } if (!imported.system.timezone.empty()) { base.system.timezone = std::move(imported.system.timezone); } for (auto& e : imported.system.env) { base.system.env.push_back(std::move(e)); } for (auto& [k, v] : imported.system.config) { base.system.config[k] = std::move(v); } for (auto& [k, v] : imported.system.features) { base.system.features[k] = std::move(v); } if (imported.system.rollback.keep > 0) { base.system.rollback.keep = imported.system.rollback.keep; } for (auto& p : imported.packages) { base.packages.push_back(std::move(p)); } for (auto& s : imported.services) { base.services.push_back(std::move(s)); } for (auto& u : imported.users) { base.users.push_back(std::move(u)); } if (!imported.boot.kernel.empty()) { base.boot.kernel = std::move(imported.boot.kernel); } if (!imported.boot.init.empty()) { base.boot.init = std::move(imported.boot.init); } if (!imported.boot.efi.empty()) { base.boot.efi = std::move(imported.boot.efi); } if (!imported.boot.swap.empty()) { base.boot.swap = std::move(imported.boot.swap); } if (!imported.boot.root.empty()) { base.boot.root = std::move(imported.boot.root); } if (!imported.boot.bootloader.empty()) { base.boot.bootloader = std::move(imported.boot.bootloader); } for (auto& [k, v] : imported.boot.params) { base.boot.params[k] = std::move(v); } } SystemConfig resolve_imports(const SystemConfig& cfg, const std::string& base_dir) { auto resolved = cfg; for (auto& import_path : cfg.imports) { auto full_path = std::filesystem::path(base_dir) / import_path; std::ifstream in(full_path); if (!in) { continue; } std::ostringstream buf; buf << in.rdbuf(); auto imported = parse_system_config(buf.str()); merge_config(resolved, resolve_imports(imported, base_dir)); } resolved.imports.clear(); return resolved; } } // namespace kappa::dsl