parser: add inherit keyword; initial string interpolation
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Icdada04a72728e8881e2db4254bd88546a6a6964
This commit is contained in:
parent
7a74735213
commit
32f74c29fd
1 changed files with 313 additions and 13 deletions
|
|
@ -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>()) {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue