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:
@@ -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
|
||||
Reference in New Issue
Block a user