Files
kappa/src/dsl/lexer.cpp
T
huntedbytheirs 4f1ce17ec8 feat: add repos DSL, index format, and kappa index command
- Add repos { } block to system config with named repos, channels,
  mirrors, and priority

- Add index "name" { } file format for package indexes

- Add kappa index <dir> subcommand that scans .kap files and
  generates index.kap

- parse_index() and build_index() in DSL module

- format_index() roundtrip support in tools module

- validate and format subcommands handle all three formats
  (package, config, index) with automatic detection

- Backward compatible: remotes = [...] still works unchanged
2026-08-04 13:48:25 -04:00

214 lines
7.1 KiB
C++

#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},
{"conflicts", TokenType::KwConflicts},
{"features", TokenType::KwFeatures},
{"config", TokenType::KwConfig},
{"const", TokenType::KwConst},
{"license", TokenType::KwLicense},
{"patches", TokenType::KwPatches},
{"env", TokenType::KwEnv},
{"service", TokenType::KwService},
{"sha256", TokenType::KwSha256},
{"sha512", TokenType::KwSha512},
{"md5", TokenType::KwMd5},
{"assert", TokenType::KwAssert},
{"import", TokenType::KwImport},
{"prepare", TokenType::KwPrepare},
{"build", TokenType::KwBuild},
{"check", TokenType::KwCheck},
{"install", TokenType::KwInstall},
{"uninstall", TokenType::KwUninstall},
{"true", TokenType::KwTrue},
{"false", TokenType::KwFalse},
{"index", TokenType::KwIndex},
{"repos", TokenType::KwRepos},
};
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::Plus: 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::KwConflicts: return "conflicts";
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::KwService: return "service";
case TokenType::KwSha256: return "sha256";
case TokenType::KwSha512: return "sha512";
case TokenType::KwMd5: return "md5";
case TokenType::KwAssert: return "assert";
case TokenType::KwImport: return "import";
case TokenType::KwPrepare: return "prepare";
case TokenType::KwBuild: return "build";
case TokenType::KwCheck: return "check";
case TokenType::KwInstall: return "install";
case TokenType::KwUninstall: return "uninstall";
case TokenType::KwTrue: return "true";
case TokenType::KwFalse: return "false";
case TokenType::KwIndex: return "index";
case TokenType::KwRepos: return "repos";
}
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 == ']' || 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 == '[' || 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::Plus, "+", 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