parser: add inherit keyword; initial string interpolation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icdada04a72728e8881e2db4254bd88546a6a6964
This commit is contained in:
raf 2026-02-21 19:47:04 +03:00
commit 32f74c29fd
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -54,9 +54,9 @@ static std::pair<std::string, std::string> run_command(const std::string& cmd) {
struct Token {
enum Type {
LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET,
IDENT, STRING, PATH, INT, BOOL,
LET, IN, REC, IF, THEN, ELSE, ASSERT, WITH,
DOT, SEMICOLON, COLON, AT, COMMA,
IDENT, STRING, STRING_INTERP, PATH, INT, BOOL,
LET, IN, REC, IF, THEN, ELSE, ASSERT, WITH, INHERIT,
DOT, SEMICOLON, COLON, EQUALS, AT, COMMA, QUESTION, ELLIPSIS,
// Operators
PLUS, MINUS, STAR, SLASH, CONCAT,
EQEQ, NE, LT, GT, LE, GE,
@ -97,6 +97,7 @@ public:
tokens.push_back(TOKEN(EQEQ));
pos += 2; col += 2;
}
else if (c == '=') { emit(TOKEN(EQUALS)); }
else if (c == '!' && pos + 1 < input.size() && input[pos + 1] == '=') {
tokens.push_back(TOKEN(NE));
pos += 2; col += 2;
@ -139,7 +140,16 @@ public:
else if (c == '<') { emit(TOKEN(LT)); }
else if (c == '>') { emit(TOKEN(GT)); }
else if (c == '!') { emit(TOKEN(NOT)); }
else if (c == '.') { emit(TOKEN(DOT)); }
else if (c == '.') {
// Check for ellipsis (...)
if (pos + 2 < input.size() && input[pos + 1] == '.' && input[pos + 2] == '.') {
tokens.push_back(TOKEN(ELLIPSIS));
pos += 3; col += 3;
} else {
emit(TOKEN(DOT));
}
}
else if (c == '?') { emit(TOKEN(QUESTION)); }
else if (c == '-') {
// Check if it's a negative number or minus operator
if (pos + 1 < input.size() && isdigit(input[pos + 1])) {
@ -189,6 +199,8 @@ private:
void tokenize_string() {
pos++;
std::string s;
bool has_interp = false;
while (pos < input.size() && input[pos] != '"') {
if (input[pos] == '\\' && pos + 1 < input.size()) {
pos++;
@ -198,15 +210,24 @@ private:
case 'r': s += '\r'; break;
case '"': s += '"'; break;
case '\\': s += '\\'; break;
case '$': s += '$'; break; // Escaped $
default: s += input[pos]; break;
}
pos++;
} else if (input[pos] == '$' && pos + 1 < input.size() && input[pos + 1] == '{') {
// Found interpolation marker
has_interp = true;
s += input[pos]; // Keep $ in raw string
pos++;
} else {
s += input[pos];
pos++;
}
pos++;
}
pos++;
tokens.push_back({Token::STRING, s, line, col});
Token::Type type = has_interp ? Token::STRING_INTERP : Token::STRING;
tokens.push_back({type, s, line, col});
col += s.size() + 2;
}
@ -246,6 +267,7 @@ private:
else if (ident == "else") type = Token::ELSE;
else if (ident == "assert") type = Token::ASSERT;
else if (ident == "with") type = Token::WITH;
else if (ident == "inherit") type = Token::INHERIT;
else if (ident == "true") type = Token::BOOL;
else if (ident == "false") type = Token::BOOL;
@ -323,6 +345,10 @@ public:
}
std::shared_ptr<Node> parse_expr() {
// Try to parse lambda
auto lambda = try_parse_lambda();
if (lambda) return lambda;
if (consume(Token::IF)) {
auto cond = parse_expr();
expect(Token::THEN);
@ -500,6 +526,12 @@ public:
return std::make_shared<Node>(ConstStringNode(t.value));
}
if (t.type == Token::STRING_INTERP) {
Token str_token = current();
advance();
return parse_string_interp(str_token.value);
}
if (t.type == Token::PATH) {
advance();
return std::make_shared<Node>(ConstPathNode(t.value));
@ -524,12 +556,45 @@ public:
continue;
}
// Handle inherit keyword
if (consume(Token::INHERIT)) {
std::shared_ptr<Node> source;
// Check for (expr) form
if (consume(Token::LPAREN)) {
source = parse_expr();
expect(Token::RPAREN);
}
// Parse identifier list
while (current().type == Token::IDENT) {
Token name = current();
advance();
if (source) {
// inherit (expr) x → x = expr.x
auto select = std::make_shared<Node>(SelectNode(
source,
std::make_shared<Node>(ConstStringNode(name.value))
));
attrs.attrs.push_back({name.value, select});
} else {
// inherit x → x = x
auto var = std::make_shared<Node>(VarNode(0, name.value));
attrs.attrs.push_back({name.value, var});
}
}
expect(Token::SEMICOLON);
continue;
}
if (current().type == Token::IDENT || current().type == Token::STRING) {
Token key = current();
advance();
std::string key_str = key.value;
if (consume(Token::COLON)) {
if (consume(Token::EQUALS)) {
auto value = parse_expr();
attrs.attrs.push_back({key_str, value});
} else if (consume(Token::AT)) {
@ -541,6 +606,11 @@ public:
if (consume(Token::COMMA)) continue;
if (consume(Token::SEMICOLON)) continue;
// If we get here and haven't handled the token, break
if (current().type != Token::RBRACE && current().type != Token::EOF_) {
break;
}
}
expect(Token::RBRACE);
@ -575,22 +645,50 @@ public:
}
void parse_bindings(std::vector<std::pair<std::string, std::shared_ptr<Node>>>& bindings) {
while (current().type == Token::IDENT || current().type == Token::LBRACE) {
if (current().type == Token::LBRACE) {
auto inherit = parse_expr();
(void)inherit;
while (current().type == Token::IDENT || current().type == Token::INHERIT) {
// Handle inherit keyword
if (consume(Token::INHERIT)) {
std::shared_ptr<Node> source;
// Check for (expr) form
if (consume(Token::LPAREN)) {
source = parse_expr();
expect(Token::RPAREN);
}
// Parse identifier list
while (current().type == Token::IDENT) {
Token name = current();
advance();
if (source) {
// inherit (expr) x → x = expr.x
auto select = std::make_shared<Node>(SelectNode(
source,
std::make_shared<Node>(ConstStringNode(name.value))
));
bindings.push_back({name.value, select});
} else {
// inherit x → x = x
auto var = std::make_shared<Node>(VarNode(0, name.value));
bindings.push_back({name.value, var});
}
}
expect(Token::SEMICOLON);
continue;
}
if (current().type != Token::IDENT) break;
Token key = current();
expect(Token::IDENT);
advance();
if (consume(Token::AT)) {
auto pattern = parse_expr();
auto value = parse_expr();
bindings.push_back({key.value, value});
} else {
expect(Token::COLON);
expect(Token::EQUALS);
auto value = parse_expr();
bindings.push_back({key.value, value});
}
@ -598,6 +696,208 @@ public:
if (!consume(Token::SEMICOLON)) break;
}
}
// Try to parse lambda, return nullptr if not a lambda
std::shared_ptr<Node> try_parse_lambda() {
size_t saved_pos = pos;
// Check for named pattern: arg@{ ... }:
std::optional<std::string> named_arg;
if (current().type == Token::IDENT) {
Token name = current();
advance();
if (consume(Token::AT)) {
named_arg = name.value;
} else if (consume(Token::COLON)) {
// Simple lambda: x: body
auto body = parse_expr();
auto lambda = LambdaNode(1, body);
lambda.param_name = name.value;
return std::make_shared<Node>(std::move(lambda));
} else {
// Not a lambda, restore position
pos = saved_pos;
return nullptr;
}
}
// Check for pattern: { ... }:
if (current().type == Token::LBRACE) {
advance();
// Parse pattern fields
struct Field {
std::string name;
std::optional<std::shared_ptr<Node>> default_val;
};
std::vector<Field> fields;
bool has_ellipsis = false;
while (current().type != Token::RBRACE && current().type != Token::EOF_) {
if (consume(Token::ELLIPSIS)) {
has_ellipsis = true;
if (consume(Token::COMMA)) continue;
break;
}
if (current().type == Token::IDENT) {
Token field_name = current();
advance();
Field field;
field.name = field_name.value;
// Check for default value
if (consume(Token::QUESTION)) {
field.default_val = parse_expr();
}
fields.push_back(field);
if (consume(Token::COMMA)) continue;
break;
} else {
break;
}
}
expect(Token::RBRACE);
if (!consume(Token::COLON)) {
// Not a lambda, restore
pos = saved_pos;
return nullptr;
}
// Parse body
auto body = parse_expr();
// Desugar pattern to lambda with let bindings
// { a, b ? x }: body → arg: let a = arg.a; b = if arg ? a then arg.a else x; in body
std::string arg_name = named_arg.value_or("_arg");
auto arg_var = std::make_shared<Node>(VarNode(0, arg_name));
std::vector<std::pair<std::string, std::shared_ptr<Node>>> bindings;
for (const auto& field : fields) {
// Create arg.field selection
auto select = std::make_shared<Node>(SelectNode(
arg_var,
std::make_shared<Node>(ConstStringNode(field.name))
));
if (field.default_val) {
// if arg ? field then arg.field else default
auto has_attr = std::make_shared<Node>(HasAttrNode(
arg_var,
std::make_shared<Node>(ConstStringNode(field.name))
));
auto if_node = std::make_shared<Node>(IfNode(
has_attr,
select,
*field.default_val
));
bindings.push_back({field.name, if_node});
} else {
bindings.push_back({field.name, select});
}
}
// If named pattern, also bind the argument name
if (named_arg) {
bindings.push_back({*named_arg, arg_var});
}
// Create let expression
auto let = LetNode(body);
let.bindings = std::move(bindings);
auto let_node = std::make_shared<Node>(std::move(let));
// Create lambda
auto lambda = LambdaNode(1, let_node);
lambda.param_name = arg_name;
return std::make_shared<Node>(std::move(lambda));
}
// Not a lambda
pos = saved_pos;
return nullptr;
}
std::shared_ptr<Node> parse_string_interp(const std::string& raw) {
std::vector<std::shared_ptr<Node>> parts;
size_t i = 0;
std::string current_str;
while (i < raw.size()) {
if (raw[i] == '$' && i + 1 < raw.size() && raw[i + 1] == '{') {
// Save current string part if any
if (!current_str.empty()) {
parts.push_back(std::make_shared<Node>(ConstStringNode(current_str)));
current_str.clear();
}
// Find matching }
i += 2; // Skip ${
int depth = 1;
size_t expr_start = i;
while (i < raw.size() && depth > 0) {
if (raw[i] == '{') depth++;
else if (raw[i] == '}') depth--;
if (depth > 0) i++;
}
// Parse the expression
std::string expr_str = raw.substr(expr_start, i - expr_start);
// Tokenize and parse the expression
Lexer lexer(expr_str);
auto expr_tokens = lexer.tokenize();
// Save current state
auto saved_tokens = tokens;
auto saved_pos = pos;
// Parse expression
tokens = expr_tokens;
pos = 0;
auto expr = parse_expr();
// Restore state
tokens = saved_tokens;
pos = saved_pos;
// Convert to string using toString builtin
auto to_string = std::make_shared<Node>(VarNode(0, "toString"));
auto str_expr = std::make_shared<Node>(AppNode(to_string, expr));
parts.push_back(str_expr);
i++; // Skip }
} else {
current_str += raw[i];
i++;
}
}
// Add remaining string part
if (!current_str.empty()) {
parts.push_back(std::make_shared<Node>(ConstStringNode(current_str)));
}
// Build concatenation tree
if (parts.empty()) {
return std::make_shared<Node>(ConstStringNode(""));
}
auto result = parts[0];
for (size_t j = 1; j < parts.size(); j++) {
result = std::make_shared<Node>(BinaryOpNode(BinaryOp::CONCAT, result, parts[j]));
}
return result;
}
};
Parser::Parser() : pImpl(std::make_unique<Impl>()) {}