Skip to content

Commit a25c86e

Browse files
gh-13: Fix ROUND.
1 parent 485f915 commit a25c86e

4 files changed

Lines changed: 123 additions & 69 deletions

File tree

src/builtins.c

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5476,30 +5476,55 @@ static Value builtin_fprod(Interpreter* interp, Value* args, int argc, Expr** ar
54765476
static Value builtin_round(Interpreter* interp, Value* args, int argc, Expr** arg_nodes, Env* env, int line, int col) {
54775477
(void)arg_nodes; (void)env;
54785478
EXPECT_NUM(args[0], "ROUND", interp, line, col);
5479-
5479+
5480+
// Signature: ROUND(x, ndigits = 0, mode = "floor")
54805481
int64_t places = 0;
5481-
if (argc >= 2) {
5482+
const char* mode = "floor";
5483+
5484+
if (argc >= 2 && args[1].type != VAL_NULL) {
54825485
EXPECT_INT(args[1], "ROUND", interp, line, col);
54835486
places = args[1].as.i;
54845487
}
5485-
5488+
if (argc >= 3 && args[2].type != VAL_NULL) {
5489+
if (args[2].type != VAL_STR) {
5490+
RUNTIME_ERROR(interp, "ROUND expects STR mode", line, col);
5491+
}
5492+
mode = args[2].as.s;
5493+
if (!mode) mode = "floor";
5494+
}
5495+
5496+
// INT behavior: keep prior semantics (ndigits >= 0 is a no-op; ndigits < 0 rounds toward zero to multiple of 2^(-ndigits)).
54865497
if (args[0].type == VAL_INT) {
54875498
if (places >= 0) {
54885499
return value_int(args[0].as.i);
54895500
}
5490-
// Negative places: round to that power of 2
5491-
int64_t factor = 1LL << (-places);
5501+
int64_t shift = -places;
5502+
if (shift >= 63) {
5503+
// 2^shift exceeds int64 range; rounding to such a large factor yields 0.
5504+
return value_int(0);
5505+
}
5506+
int64_t factor = 1LL << shift;
54925507
return value_int((args[0].as.i / factor) * factor);
54935508
}
5494-
5509+
54955510
double val = args[0].as.f;
5496-
if (places >= 0) {
5497-
double factor = (double)(1LL << places);
5498-
return value_flt(round(val * factor) / factor);
5511+
double factor = pow(2.0, (double)places);
5512+
double scaled = val * factor;
5513+
double rs;
5514+
5515+
if (strcmp(mode, "floor") == 0) {
5516+
rs = floor(scaled);
5517+
} else if (strcmp(mode, "ceiling") == 0 || strcmp(mode, "ceil") == 0) {
5518+
rs = ceil(scaled);
5519+
} else if (strcmp(mode, "zero") == 0) {
5520+
rs = (scaled >= 0.0) ? floor(scaled) : ceil(scaled);
5521+
} else if (strcmp(mode, "logical") == 0 || strcmp(mode, "half-up") == 0) {
5522+
rs = round(scaled);
54995523
} else {
5500-
double factor = (double)(1LL << (-places));
5501-
return value_flt(round(val / factor) * factor);
5524+
RUNTIME_ERROR(interp, "Unknown ROUND mode", line, col);
55025525
}
5526+
5527+
return value_flt(rs / factor);
55035528
}
55045529

55055530
// INV (1/x)
@@ -6702,6 +6727,14 @@ static Value builtin_parallel(Interpreter* interp, Value* args, int argc, Expr**
67026727
}
67036728

67046729

6730+
static const char* builtin_params_round[] = {"x", "ndigits", "mode"};
6731+
static const char* builtin_params_bytes[] = {"x", "endian"};
6732+
static const char* builtin_params_split[] = {"s", "delimiter"};
6733+
static const char* builtin_params_match[] = {"value", "template", "typing", "recurse", "shape"};
6734+
static const char* builtin_params_readfile[] = {"path", "coding"};
6735+
static const char* builtin_params_writefile[] = {"data", "path", "coding"};
6736+
static const char* builtin_params_pause[] = {"thr", "seconds"};
6737+
67056738
static BuiltinFunction builtins_table[] = {
67066739
// Arithmetic
67076740
{"ADD", 2, 2, builtin_add},
@@ -6720,7 +6753,7 @@ static BuiltinFunction builtins_table[] = {
67206753
{"GCD", 2, 2, builtin_gcd},
67216754
{"LCM", 2, 2, builtin_lcm},
67226755
{"INV", 1, 1, builtin_inv},
6723-
{"ROUND", 1, 3, builtin_round},
6756+
{"ROUND", 1, 3, builtin_round, builtin_params_round, 3},
67246757

67256758
// Coercing arithmetic
67266759
{"IADD", 2, 2, builtin_iadd},
@@ -6783,7 +6816,7 @@ static BuiltinFunction builtins_table[] = {
67836816
{"INT", 1, 1, builtin_int},
67846817
{"FLT", 1, 1, builtin_flt},
67856818
{"STR", 1, 1, builtin_str},
6786-
{"BYTES", 1, 2, builtin_bytes},
6819+
{"BYTES", 1, 2, builtin_bytes, builtin_params_bytes, 2},
67876820
{"SER", 1, 1, builtin_ser},
67886821
{"UNSER", 1, 1, builtin_unser},
67896822

@@ -6804,13 +6837,13 @@ static BuiltinFunction builtins_table[] = {
68046837
{"REPLACE", 3, 3, builtin_replace},
68056838
{"STRIP", 2, 2, builtin_strip},
68066839
{"JOIN", 1, -1, builtin_join},
6807-
{"SPLIT", 1, 2, builtin_split},
6840+
{"SPLIT", 1, 2, builtin_split, builtin_params_split, 2},
68086841
{"IN", 2, 2, builtin_in},
68096842
{"KEYS", 1, 1, builtin_keys},
68106843
{"VALUES", 1, 1, builtin_values},
68116844
{"KEYIN", 2, 2, builtin_keyin},
68126845
{"VALUEIN", 2, 2, builtin_valuein},
6813-
{"MATCH", 2, 5, builtin_match},
6846+
{"MATCH", 2, 5, builtin_match, builtin_params_match, 5},
68146847
{"ILEN", 1, 1, builtin_ilen},
68156848
{"LEN", 0, -1, builtin_len},
68166849

@@ -6819,16 +6852,16 @@ static BuiltinFunction builtins_table[] = {
68196852
{"INPUT", 0, 1, builtin_input},
68206853
{"SHUSH", 0, 0, builtin_shush},
68216854
{"UNSHUSH", 0, 0, builtin_unshush},
6822-
{"READFILE", 1, 2, builtin_readfile},
6823-
{"WRITEFILE", 2, 3, builtin_writefile},
6855+
{"READFILE", 1, 2, builtin_readfile, builtin_params_readfile, 2},
6856+
{"WRITEFILE", 2, 3, builtin_writefile, builtin_params_writefile, 3},
68246857
{"CL", 1, 1, builtin_cl},
68256858
{"EXISTFILE", 1, 1, builtin_existfile},
68266859
{"DELETEFILE", 1, 1, builtin_deletefile},
68276860
{"RUN", 1, 1, builtin_run},
68286861
{"ARGV", 0, 0, builtin_argv},
68296862
{"PARALLEL", 1, -1, builtin_parallel},
68306863
{"AWAIT", 1, 1, builtin_await},
6831-
{"PAUSE", 1, 2, builtin_pause},
6864+
{"PAUSE", 1, 2, builtin_pause, builtin_params_pause, 2},
68326865
{"RESUME", 1, 1, builtin_resume},
68336866
{"PAUSED", 1, 1, builtin_paused},
68346867
{"STOP", 1, 1, builtin_stop},

src/builtins.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ typedef struct {
1313
int min_args;
1414
int max_args; // -1 for variadic
1515
Value (*impl)(Interpreter* interp, Value* args, int argc, Expr** arg_nodes, Env* env, int line, int col);
16+
// Optional: parameter names for keyword argument binding.
17+
// If NULL/0, the builtin does not accept keyword arguments.
18+
const char** param_names;
19+
int param_count;
1620
} BuiltinFunction;
1721

1822
// Initialize the builtins table

src/interpreter.c

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -118,42 +118,12 @@ static void* safe_malloc(size_t size) {
118118
return ptr;
119119
}
120120

121-
static int builtin_kw_index(const char* func_name, const char* kw) {
122-
if (!func_name || !kw) return -1;
123-
if (strcmp(func_name, "READFILE") == 0 || strcmp(func_name, "WRITEFILE") == 0) {
124-
if (strcmp(kw, "coding") == 0) return 1;
125-
return -1;
126-
}
127-
if (strcmp(func_name, "MATCH") == 0) {
128-
if (strcmp(kw, "typing") == 0) return 2;
129-
if (strcmp(kw, "recurse") == 0) return 3;
130-
if (strcmp(kw, "shape") == 0) return 4;
131-
return -1;
132-
}
133-
if (strcmp(func_name, "CONV") == 0) {
134-
if (strcmp(kw, "stride_w") == 0) return 2;
135-
if (strcmp(kw, "stride_h") == 0) return 3;
136-
if (strcmp(kw, "pad_w") == 0) return 4;
137-
if (strcmp(kw, "pad_h") == 0) return 5;
138-
if (strcmp(kw, "bias") == 0) return 6;
139-
return -1;
140-
}
141-
if (strcmp(func_name, "ROUND") == 0) {
142-
if (strcmp(kw, "mode") == 0) return 1;
143-
if (strcmp(kw, "ndigits") == 0) return 2;
144-
return -1;
145-
}
146-
if (strcmp(func_name, "SPLIT") == 0) {
147-
if (strcmp(kw, "delimiter") == 0) return 1;
148-
return -1;
149-
}
150-
if (strcmp(func_name, "BYTES") == 0) {
151-
if (strcmp(kw, "endian") == 0) return 1;
152-
return -1;
153-
}
154-
if (strcmp(func_name, "PAUSE") == 0) {
155-
if (strcmp(kw, "seconds") == 0) return 1;
156-
return -1;
121+
static int builtin_param_index(BuiltinFunction* builtin, const char* kw) {
122+
if (!builtin || !kw) return -1;
123+
if (!builtin->param_names || builtin->param_count <= 0) return -1;
124+
for (int i = 0; i < builtin->param_count; i++) {
125+
const char* pn = builtin->param_names[i];
126+
if (pn && strcmp(pn, kw) == 0) return i;
157127
}
158128
return -1;
159129
}
@@ -493,15 +463,24 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
493463
Value* args = NULL;
494464
Expr** arg_nodes = NULL;
495465

496-
// For builtins, allow keywords only if explicitly supported
466+
// For builtins, keywords are supported only if the builtin declares param names.
467+
if (kwc > 0 && (!builtin->param_names || builtin->param_count <= 0)) {
468+
interp->error = strdup("Keyword arguments not supported for builtin function");
469+
interp->error_line = expr->line;
470+
interp->error_col = expr->column;
471+
return value_null();
472+
}
473+
474+
// Reject duplicate keyword names (order-independent)
497475
if (kwc > 0) {
498-
for (int i = 0; i < kwc; i++) {
499-
char* k = expr->as.call.kw_names[i];
500-
if (builtin_kw_index(func_name, k) < 0) {
501-
interp->error = strdup("Unknown keyword for builtin function");
502-
interp->error_line = expr->line;
503-
interp->error_col = expr->column;
504-
return value_null();
476+
for (int k = 0; k < kwc; k++) {
477+
for (int m = 0; m < k; m++) {
478+
if (strcmp(expr->as.call.kw_names[m], expr->as.call.kw_names[k]) == 0) {
479+
interp->error = strdup("Duplicate keyword argument");
480+
interp->error_line = expr->line;
481+
interp->error_col = expr->column;
482+
return value_null();
483+
}
505484
}
506485
}
507486
}
@@ -511,8 +490,14 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
511490
if (kwc > 0) {
512491
for (int i = 0; i < kwc; i++) {
513492
char* k = expr->as.call.kw_names[i];
514-
int idx = builtin_kw_index(func_name, k);
515-
if (idx >= 0 && idx + 1 > max_slot) max_slot = idx + 1;
493+
int idx = builtin_param_index(builtin, k);
494+
if (idx < 0) {
495+
interp->error = strdup("Unknown keyword argument");
496+
interp->error_line = expr->line;
497+
interp->error_col = expr->column;
498+
return value_null();
499+
}
500+
if (idx + 1 > max_slot) max_slot = idx + 1;
516501
}
517502
}
518503

@@ -543,19 +528,19 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
543528
for (int k = 0; k < kwc; k++) {
544529
char* name = expr->as.call.kw_names[k];
545530
Expr* valnode = expr->as.call.kw_args.items[k];
546-
int idx = builtin_kw_index(func_name, name);
531+
int idx = builtin_param_index(builtin, name);
547532
if (idx < 0) {
548-
interp->error = strdup("Unknown keyword for builtin function");
533+
interp->error = strdup("Unknown keyword argument");
549534
interp->error_line = expr->line;
550535
interp->error_col = expr->column;
551536
for (int j = 0; j < max_slot; j++) value_free(args[j]);
552537
free(args);
553538
free(arg_nodes);
554539
return value_null();
555540
}
556-
// Duplicate positional/keyword error
557-
if (idx < pos_argc && arg_nodes[idx] != NULL) {
558-
interp->error = strdup("Duplicate argument for parameter 'coding'");
541+
// Duplicate positional/keyword or duplicate keyword->slot
542+
if (idx < max_slot && arg_nodes[idx] != NULL) {
543+
interp->error = strdup("Duplicate argument for parameter");
559544
interp->error_line = expr->line;
560545
interp->error_col = expr->column;
561546
for (int j = 0; j < max_slot; j++) value_free(args[j]);

test2.pre

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,38 @@ ASSERT(ISSTR(os_name))
691691
DEL(os_name)
692692
PRINT("System: PASS\n")
693693

694+
PRINT("Testing ROUND...")
695+
696+
# half-way cases (binary digits) - ndigits = 2 (binary `10`)
697+
FLT: r_half = 0.101
698+
ASSERT(EQ(ROUND(r_half, ndigits = 10), 0.10))
699+
ASSERT(EQ(ROUND(r_half, mode = "ceiling", ndigits = 10), 0.11))
700+
ASSERT(EQ(ROUND(r_half, mode = "ceil", ndigits = 10), 0.11))
701+
ASSERT(EQ(ROUND(r_half, mode = "zero", ndigits = 10), 0.10))
702+
ASSERT(EQ(ROUND(r_half, mode = "logical", ndigits = 10), 0.11))
703+
ASSERT(EQ(ROUND(r_half, mode = "half-up", ndigits = 10), 0.11))
704+
# two-argument form: second arg INT -> ndigits with default "floor"
705+
ASSERT(EQ(ROUND(0.101, 10), 0.10))
706+
707+
# negative half-case: sign effects differ for floor/zero/half-up
708+
FLT: rn_half = -0.101
709+
ASSERT(EQ(ROUND(rn_half, ndigits = 10), -0.11))
710+
ASSERT(EQ(ROUND(rn_half, mode = "zero", ndigits = 10), -0.10))
711+
ASSERT(EQ(ROUND(rn_half, mode = "logical", ndigits = 10), -0.11))
712+
713+
# negative ndigits (round to left of radix point)
714+
FLT: three = 11.0
715+
ASSERT(EQ(ROUND(three, ndigits = -1), 10.0))
716+
ASSERT(EQ(ROUND(three, mode = "logical", ndigits = -1), 100.0))
717+
ASSERT(EQ(ROUND(three, -1), 10.0))
718+
719+
FLT: neg_three = -11.0
720+
ASSERT(EQ(ROUND(neg_three, ndigits = -1), -100.0))
721+
ASSERT(EQ(ROUND(neg_three, mode = "zero", ndigits = -1), -10.0))
722+
ASSERT(EQ(ROUND(neg_three, mode = "logical", ndigits = -1), -100.0))
723+
724+
PRINT("ROUND: PASS\n")
725+
694726
PRINT("Testing CL...")
695727
# Simple cross-platform check: `echo` should succeed and return 0
696728
ASSERT(EQ(CL("echo hello"), 0))

0 commit comments

Comments
 (0)