Skip to content

Commit fc4aa9f

Browse files
gh-61: Allow DEL calls on MAP index.
1 parent 3723537 commit fc4aa9f

File tree

4 files changed

+155
-20
lines changed

4 files changed

+155
-20
lines changed

src/builtins.c

Lines changed: 140 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5409,29 +5409,150 @@ static Value builtin_signature(Interpreter* interp, Value* args, int argc, Expr*
54095409

54105410
static Value builtin_del(Interpreter* interp, Value* args, int argc, Expr** arg_nodes, Env* env, int line, int col) {
54115411
(void)args;
5412-
5413-
if (argc != 1 || arg_nodes[0]->type != EXPR_IDENT) {
5412+
if (argc != 1) {
54145413
RUNTIME_ERROR(interp, "DEL expects an identifier", line, col);
54155414
}
5416-
5417-
const char* name = arg_nodes[0]->as.ident;
5418-
EnvEntry* entry = env_get_entry(env, name);
5419-
if (!entry || !entry->initialized) {
5420-
char buf[128];
5421-
snprintf(buf, sizeof(buf), "Cannot delete undefined identifier '%s'", name);
5422-
RUNTIME_ERROR(interp, buf, line, col);
5423-
}
5424-
if (entry->frozen || entry->permafrozen) {
5425-
char buf[128];
5426-
snprintf(buf, sizeof(buf), "Cannot delete frozen identifier '%s'", name);
5427-
RUNTIME_ERROR(interp, buf, line, col);
5415+
5416+
Expr* target = arg_nodes[0];
5417+
5418+
/* Case 1: plain identifier – delete the binding */
5419+
if (target->type == EXPR_IDENT) {
5420+
const char* name = target->as.ident;
5421+
EnvEntry* entry = env_get_entry(env, name);
5422+
if (!entry || !entry->initialized) {
5423+
char buf[128];
5424+
snprintf(buf, sizeof(buf), "Cannot delete undefined identifier '%s'", name);
5425+
RUNTIME_ERROR(interp, buf, line, col);
5426+
}
5427+
if (entry->frozen || entry->permafrozen) {
5428+
char buf[128];
5429+
snprintf(buf, sizeof(buf), "Cannot delete frozen identifier '%s'", name);
5430+
RUNTIME_ERROR(interp, buf, line, col);
5431+
}
5432+
if (!env_delete(env, name)) {
5433+
char buf[128];
5434+
snprintf(buf, sizeof(buf), "Cannot delete identifier '%s'", name);
5435+
RUNTIME_ERROR(interp, buf, line, col);
5436+
}
5437+
return value_int(0);
54285438
}
5429-
if (!env_delete(env, name)) {
5430-
char buf[128];
5431-
snprintf(buf, sizeof(buf), "Cannot delete identifier '%s'", name);
5432-
RUNTIME_ERROR(interp, buf, line, col);
5439+
5440+
/* Case 2: indexed expression – support deleting map entries like DEL(m<k>) or DEL(m<k1,k2>) */
5441+
if (target->type == EXPR_INDEX) {
5442+
/* collect chain of index nodes (possibly nested) */
5443+
size_t chain_len = 0;
5444+
Expr* walker = target;
5445+
while (walker && walker->type == EXPR_INDEX) {
5446+
chain_len++;
5447+
walker = walker->as.index.target;
5448+
}
5449+
if (!walker || walker->type != EXPR_IDENT) {
5450+
RUNTIME_ERROR(interp, "DEL expects an identifier or indexed identifier", line, col);
5451+
}
5452+
5453+
const char* base_name = walker->as.ident;
5454+
Value base_val = value_null();
5455+
DeclType base_type = TYPE_UNKNOWN;
5456+
bool base_initialized = false;
5457+
if (!env_get(env, base_name, &base_val, &base_type, &base_initialized) || !base_initialized) {
5458+
char buf[128];
5459+
snprintf(buf, sizeof(buf), "Cannot delete mapping from undefined identifier '%s'", base_name);
5460+
RUNTIME_ERROR(interp, buf, line, col);
5461+
}
5462+
5463+
Expr** nodes = malloc(sizeof(Expr*) * (chain_len ? chain_len : 1));
5464+
if (!nodes) {
5465+
value_free(base_val);
5466+
RUNTIME_ERROR(interp, "Out of memory", line, col);
5467+
}
5468+
walker = target;
5469+
for (size_t i = 0; i < chain_len; i++) {
5470+
nodes[i] = walker;
5471+
walker = walker->as.index.target;
5472+
}
5473+
5474+
/* operate on the in-memory copy and write back at end */
5475+
Value* cur = &base_val;
5476+
5477+
for (int ni = (int)chain_len - 1; ni >= 0; ni--) {
5478+
Expr* node = nodes[ni];
5479+
ExprList* indices = &node->as.index.indices;
5480+
if (indices->count == 0) {
5481+
free(nodes);
5482+
value_free(base_val);
5483+
RUNTIME_ERROR(interp, "Empty index list", line, col);
5484+
}
5485+
5486+
if (cur->type != VAL_MAP) {
5487+
free(nodes);
5488+
value_free(base_val);
5489+
RUNTIME_ERROR(interp, "Attempted map deletion on non-map value", node->line, node->column);
5490+
}
5491+
5492+
for (size_t i = 0; i < indices->count; i++) {
5493+
Expr* it = indices->items[i];
5494+
Value key = eval_expr(interp, it, env);
5495+
if (interp->error) {
5496+
/* propagate evaluation error */
5497+
char* em = interp->error;
5498+
int el = interp->error_line;
5499+
int ec = interp->error_col;
5500+
clear_error(interp);
5501+
free(nodes);
5502+
value_free(base_val);
5503+
RUNTIME_ERROR(interp, em, el, ec);
5504+
}
5505+
if (!(key.type == VAL_INT || key.type == VAL_STR || key.type == VAL_FLT)) {
5506+
value_free(key);
5507+
free(nodes);
5508+
value_free(base_val);
5509+
RUNTIME_ERROR(interp, "Map index must be INT, FLT or STR", it->line, it->column);
5510+
}
5511+
5512+
bool last_key_in_node = (i + 1 == indices->count);
5513+
bool last_node_in_chain = (ni == 0);
5514+
5515+
if (last_node_in_chain && last_key_in_node) {
5516+
/* final key: perform deletion on current map */
5517+
value_map_delete(cur, key);
5518+
value_free(key);
5519+
/* write back modified base into environment */
5520+
if (!env_assign(env, base_name, base_val, TYPE_UNKNOWN, false)) {
5521+
free(nodes);
5522+
value_free(base_val);
5523+
RUNTIME_ERROR(interp, "Cannot write back to identifier (frozen?)", line, col);
5524+
}
5525+
free(nodes);
5526+
value_free(base_val);
5527+
return value_int(0);
5528+
}
5529+
5530+
/* descend into child slot without creating missing entries */
5531+
Value* slot = value_map_get_ptr(cur, key, false);
5532+
value_free(key);
5533+
if (!slot) {
5534+
/* intermediate missing -> nothing to delete (no-op) */
5535+
free(nodes);
5536+
value_free(base_val);
5537+
return value_int(0);
5538+
}
5539+
if (slot->type != VAL_MAP) {
5540+
free(nodes);
5541+
value_free(base_val);
5542+
RUNTIME_ERROR(interp, "Attempted nested map indexing on non-map value", it->line, it->column);
5543+
}
5544+
/* descend */
5545+
cur = slot;
5546+
}
5547+
}
5548+
5549+
/* unreachable, but keep cleanup */
5550+
free(nodes);
5551+
value_free(base_val);
5552+
return value_int(0);
54335553
}
5434-
return value_int(0);
5554+
5555+
RUNTIME_ERROR(interp, "DEL expects an identifier", line, col);
54355556
}
54365557

54375558
static Value builtin_freeze(Interpreter* interp, Value* args, int argc, Expr** arg_nodes, Env* env, int line, int col) {

src/interpreter.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ static ExecResult make_ok(Value v) {
716716
}
717717

718718
// Clear the interpreter error after handling it
719-
static void clear_error(Interpreter* interp) {
719+
void clear_error(Interpreter* interp) {
720720
if (interp->error) {
721721
free(interp->error);
722722
interp->error = NULL;

src/interpreter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ void interpreter_reset_traceback(Interpreter* interp, Env* top_env);
129129
// Functions needed by builtins.c
130130
Value eval_expr(Interpreter* interp, Expr* expr, Env* env);
131131
int value_truthiness(Value v);
132+
// Clear interpreter error (used by builtins & helpers)
133+
void clear_error(Interpreter* interp);
132134
// Expose indexed-assignment helper so builtins can reuse it
133135
ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Value rhs, int stmt_line, int stmt_col);
134136
// Restart a finished thread `thr_val` by re-launching its stored body/env.

tests/test2.pre

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,18 @@ ASSERT(EQ(m<"nested", "b">, 0d2))
151151
ASSERT(NOT(m<"nope">))
152152
PRINT("Maps: PASS\n")
153153

154+
! Regression test for GH-61: ensure DEL works on map index targets
155+
PRINT("Testing DEL on map indexing (regression GH-61)...")
156+
MAP: reg = <"foo" = 0d1, "bar" = 0d2, "nested" = <"a" = 0d10>>
157+
ASSERT(EQ(reg<"foo">, 0d1))
158+
DEL(reg<"foo">)
159+
ASSERT(NOT(reg<"foo">))
160+
! Deleting nested key
161+
DEL(reg<"nested", "a">)
162+
ASSERT(NOT(reg<"nested", "a">))
163+
DEL(reg)
164+
PRINT("DEL map-index: PASS\n")
165+
154166
! Additional map builtins tests
155167
ASSERT(KEYIN("foo", m))
156168
ASSERT(NOT(KEYIN("nope", m)))

0 commit comments

Comments
 (0)