feat: DSL parser, eval system, and project structure

- Hand-written lexer + recursive-descent parser for .kap package definitions
- Package constructs: const, license, provides, outputs, patches, depends
  (version-constrained, feature-gated, output-targeted), features (enabled/
  forced/flag), config files (default/replace/merge with cfg interpolation),
  env (?= soft-set), prepare/build/check/install phases
- Evaluator: recursive  resolver for prefix, jobopts, destdir, userargs,
  cfg.*, feature.*, package vars
- Comments: // single-line, /* */ multi-line
- /kappa/{bin,temp,db,system,system/builds} path layout
- Clang 22, C++23, CMake + vcpkg (tomlplusplus)
- .clangd + .clang-tidy configured
This commit is contained in:
2026-07-29 22:16:16 -04:00
parent 6fd31b694b
commit a8b53681fc
20 changed files with 1282 additions and 0 deletions
+191
View File
@@ -0,0 +1,191 @@
#include "kappa/dsl/lexer.hpp"
#include <cctype>
#include <stdexcept>
#include <unordered_map>
namespace kappa::dsl {
static const std::unordered_map<std::string_view, TokenType> keywords = {
{"package", TokenType::KwPackage},
{"version", TokenType::KwVersion},
{"source", TokenType::KwSource},
{"depends", TokenType::KwDepends},
{"provides", TokenType::KwProvides},
{"outputs", TokenType::KwOutputs},
{"features", TokenType::KwFeatures},
{"config", TokenType::KwConfig},
{"const", TokenType::KwConst},
{"license", TokenType::KwLicense},
{"patches", TokenType::KwPatches},
{"env", TokenType::KwEnv},
{"prepare", TokenType::KwPrepare},
{"build", TokenType::KwBuild},
{"check", TokenType::KwCheck},
{"install", TokenType::KwInstall},
{"true", TokenType::KwTrue},
{"false", TokenType::KwFalse},
};
std::string_view token_name(TokenType type) {
switch (type) {
case TokenType::Eof: return "EOF";
case TokenType::Newline: return "newline";
case TokenType::Lbrace: return "{";
case TokenType::Rbrace: return "}";
case TokenType::Equals: return "=";
case TokenType::Lbracket: return "[";
case TokenType::Rbracket: return "]";
case TokenType::Comma: return ",";
case TokenType::String: return "string";
case TokenType::Ident: return "ident";
case TokenType::KwPackage: return "package";
case TokenType::KwVersion: return "version";
case TokenType::KwSource: return "source";
case TokenType::KwDepends: return "depends";
case TokenType::KwProvides: return "provides";
case TokenType::KwOutputs: return "outputs";
case TokenType::KwFeatures: return "features";
case TokenType::KwConfig: return "config";
case TokenType::KwConst: return "const";
case TokenType::KwLicense: return "license";
case TokenType::KwPatches: return "patches";
case TokenType::KwEnv: return "env";
case TokenType::KwPrepare: return "prepare";
case TokenType::KwBuild: return "build";
case TokenType::KwCheck: return "check";
case TokenType::KwInstall: return "install";
case TokenType::KwTrue: return "true";
case TokenType::KwFalse: return "false";
}
return "?";
}
Lexer::Lexer(std::string_view source) : source_(source) {}
char Lexer::peek() const {
return pos_ < source_.size() ? source_[pos_] : '\0';
}
char Lexer::advance() {
if (pos_ >= source_.size()) { return '\0'; }
char c = source_[pos_++];
if (c == '\n') { ++line_; col_ = 1; }
else { ++col_; }
return c;
}
bool Lexer::match(char c) {
if (peek() == c) { advance(); return true; }
return false;
}
void Lexer::skip_whitespace() {
while (std::isspace(static_cast<unsigned char>(peek())) && peek() != '\n') {
advance();
}
}
bool Lexer::skip_comment() {
if (peek() != '/') { return false; }
if (pos_ + 1 >= source_.size()) { return false; }
char next = source_[pos_ + 1];
if (next == '/') {
advance(); advance(); // skip //
while (pos_ < source_.size() && peek() != '\n') { advance(); }
return true;
}
if (next == '*') {
advance(); advance(); // skip /*
while (pos_ + 1 < source_.size()) {
if (peek() == '*' && source_[pos_ + 1] == '/') {
advance(); advance(); // skip */
return true;
}
advance();
}
return true; // unterminated — consume to EOF
}
return false;
}
Token Lexer::next() {
while (true) {
skip_whitespace();
if (skip_comment()) { continue; }
token_start_line_ = line_;
token_start_col_ = col_;
if (pos_ >= source_.size()) { return {TokenType::Eof, "", line_, col_}; }
char c = peek();
if (c == '\n') {
advance();
return {TokenType::Newline, "\n", token_start_line_, token_start_col_};
}
if (c == '"') { return scan_string(); }
if (c == '{' || c == '}' || c == '=' || c == '[' || c == ']' || c == ',') {
return scan_symbol();
}
return scan_ident();
}
}
Token Lexer::scan_ident() {
std::string lexeme;
while (pos_ < source_.size()) {
char c = peek();
if (std::isspace(static_cast<unsigned char>(c))) { break; }
if (c == '"' || c == '[' || c == ']' || c == ',') { break; }
lexeme += advance();
}
auto it = keywords.find(lexeme);
if (it != keywords.end()) {
return {it->second, lexeme, token_start_line_, token_start_col_};
}
return {TokenType::Ident, lexeme, token_start_line_, token_start_col_};
}
Token Lexer::scan_string() {
advance(); // opening "
std::string lexeme;
while (pos_ < source_.size()) {
char c = peek();
if (c == '"') { advance(); break; }
if (c == '\\') {
advance();
if (pos_ < source_.size()) {
char esc = advance();
switch (esc) {
case 'n': lexeme += '\n'; break;
case 't': lexeme += '\t'; break;
case '\\': lexeme += '\\'; break;
case '"': lexeme += '"'; break;
default: lexeme += esc; break;
}
}
} else {
lexeme += advance();
}
}
return {TokenType::String, lexeme, token_start_line_, token_start_col_};
}
Token Lexer::scan_symbol() {
char c = advance();
switch (c) {
case '{': return {TokenType::Lbrace, "{", token_start_line_, token_start_col_};
case '}': return {TokenType::Rbrace, "}", token_start_line_, token_start_col_};
case '=': return {TokenType::Equals, "=", token_start_line_, token_start_col_};
case '[': return {TokenType::Lbracket, "[", token_start_line_, token_start_col_};
case ']': return {TokenType::Rbracket, "]", token_start_line_, token_start_col_};
case ',': return {TokenType::Comma, ",", token_start_line_, token_start_col_};
default: return {TokenType::Eof, "", token_start_line_, token_start_col_};
}
}
} // namespace kappa::dsl