Files
kappa/src/dsl/system.cpp
T
huntedbytheirs 6c993d2d17 fix: Phase 3+4 review — deterministic hash, EINTR, env restore, installer wiring
- std::hash→FNV-1a (deterministic across runs, null-byte-separated)
- waitpid EINTR retry loop (prevents zombie processes under signals)
- setenv save/restore between builds (prevents cross-package env leaks)
- install::install() wired into kappa build CLI (store + DB + generations)
- Zero-padded generation filenames (gen-0001, lexicographic sort correct)
- Hardcoded paths fixed: /tmp/kappa-build→paths::temp_dir()/build
- Hardcoded destdir: /kappa/temp/destdir→paths::temp_dir()/destdir
- ensure_directories() called at main() startup
- build subcommand: -j N, --root wired end-to-end
2026-07-30 05:14:51 -04:00

454 lines
17 KiB
C++

#include "kappa/dsl/system.hpp"
#include "kappa/dsl/error.hpp"
#include "kappa/dsl/lexer.hpp"
#include <filesystem>
#include <format>
#include <fstream>
#include <sstream>
#include <stdexcept>
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