- 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
214 lines
7.1 KiB
C++
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
|