Skip to content

Commit 717a2b8

Browse files
gh-45: Add FLT infinities, NaN.
1 parent 48e1932 commit 717a2b8

File tree

4 files changed

+82
-4
lines changed

4 files changed

+82
-4
lines changed

docs/SPECIFICATION.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@
139139

140140
`FLT` literals MUST NOT begin with the radix point (so `.1` is invalid). A leading `-` MAY prefix a `FLT` literal using the same rules as for integers (the dash is part of the literal and is not an operator).
141141
142+
In addition to binary fixed-point forms, the language also recognizes two special `FLT` literal tokens: `INF` and `NaN`. These tokens are matched case-sensitively and are part of the `FLT` literal class. `INF` denotes IEEE-754 infinity and `NaN` denotes a quiet Not-a-Number. `NaN` MUST NOT be negative; `-INF` (the dash token followed by `INF`) is permitted and denotes negative infinity. When `FLT` values are rendered as source, printed via `PRINT`, or serialized by the interpreter, `INF`, `-INF`, and `NaN` MUST appear exactly as shown. Arithmetic and comparison involving these values follow IEEE-754 semantics.
143+
142144
String literal: a sequence of characters enclosed in either double quotation marks (`"`) or single quotation marks (`'`). A string opened with one delimiter MUST be closed with the same delimiter. Newlines are not permitted inside string literals.
143145
144146
String literals support backslash escape codes. Unknown escape codes (or invalid hexadecimal literals in `\x`/`\u`/`\U`) raise syntax errors at parse time.

src/builtins.c

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ static char* int_to_binary_str(int64_t val) {
143143
// Helper: convert float to binary string
144144
static char* flt_to_binary_str(double val) {
145145
char buf[128];
146+
if (isnan(val)) {
147+
return strdup("NaN");
148+
}
149+
if (isinf(val)) {
150+
return strdup(signbit(val) ? "-INF" : "INF");
151+
}
146152
int is_negative = val < 0;
147153
if (is_negative) val = -val;
148154

@@ -877,7 +883,14 @@ static void ser_expr(JsonBuf* jb, SerCtx* ctx, Interpreter* interp, Expr* expr)
877883
json_obj_field(jb, &first, "loc");
878884
ser_loc(jb, expr->line, expr->column);
879885
json_obj_field(jb, &first, "value");
880-
jb_append_fmt(jb, "%.17g", expr->as.flt_value);
886+
if (isnan(expr->as.flt_value)) {
887+
jb_append_json_string(jb, "NaN");
888+
} else if (isinf(expr->as.flt_value)) {
889+
if (signbit(expr->as.flt_value)) jb_append_json_string(jb, "-INF");
890+
else jb_append_json_string(jb, "INF");
891+
} else {
892+
jb_append_fmt(jb, "%.17g", expr->as.flt_value);
893+
}
881894
json_obj_field(jb, &first, "literal_type");
882895
jb_append_json_string(jb, "FLT");
883896
jb_append_char(jb, '}');
@@ -1410,14 +1423,21 @@ static void ser_value(JsonBuf* jb, SerCtx* ctx, Interpreter* interp, Value v) {
14101423
return;
14111424
}
14121425
case VAL_FLT: {
1413-
char buf[64];
1414-
snprintf(buf, sizeof(buf), "%.17g", v.as.f);
14151426
jb_append_char(jb, '{');
14161427
bool first = true;
14171428
json_obj_field(jb, &first, "t");
14181429
jb_append_json_string(jb, "FLT");
14191430
json_obj_field(jb, &first, "v");
1420-
jb_append_json_string(jb, buf);
1431+
if (isnan(v.as.f)) {
1432+
jb_append_json_string(jb, "NaN");
1433+
} else if (isinf(v.as.f)) {
1434+
if (signbit(v.as.f)) jb_append_json_string(jb, "-INF");
1435+
else jb_append_json_string(jb, "INF");
1436+
} else {
1437+
char buf[64];
1438+
snprintf(buf, sizeof(buf), "%.17g", v.as.f);
1439+
jb_append_json_string(jb, buf);
1440+
}
14211441
jb_append_char(jb, '}');
14221442
return;
14231443
}
@@ -3739,6 +3759,10 @@ static Value builtin_flt(Interpreter* interp, Value* args, int argc, Expr** arg_
37393759
if (s == NULL || *s == '\0') {
37403760
return value_flt(0.0);
37413761
}
3762+
// Accept special textual FLT values
3763+
if (strcmp(s, "INF") == 0) return value_flt(INFINITY);
3764+
if (strcmp(s, "-INF") == 0) return value_flt(-INFINITY);
3765+
if (strcmp(s, "NaN") == 0) return value_flt(NAN);
37423766
bool neg = false;
37433767
if (*s == '-') {
37443768
neg = true;

src/parser.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "parser.h"
2+
#include <math.h>
23
#include <stdio.h>
34
#include <string.h>
45
#include <stdlib.h>
@@ -109,6 +110,32 @@ static bool parse_param_list(Parser* parser, ParamList* params) {
109110

110111
static Expr* parse_primary(Parser* parser) {
111112
Token token = parser->current_token;
113+
// Recognize FLT literal names `INF` and `NaN` as primary expressions
114+
if (parser->current_token.type == TOKEN_IDENT) {
115+
if (strcmp(parser->current_token.literal, "INF") == 0) {
116+
Token t = parser->current_token;
117+
advance(parser);
118+
return expr_flt(INFINITY, t.line, t.column);
119+
}
120+
if (strcmp(parser->current_token.literal, "NaN") == 0) {
121+
Token t = parser->current_token;
122+
advance(parser);
123+
return expr_flt(NAN, t.line, t.column);
124+
}
125+
}
126+
// Support negative INF written as `-INF` (but disallow `-NaN`)
127+
if (parser->current_token.type == TOKEN_DASH && parser->next_token.type == TOKEN_IDENT) {
128+
if (strcmp(parser->next_token.literal, "INF") == 0) {
129+
Token dash = parser->current_token;
130+
advance(parser); // consume '-'
131+
advance(parser); // consume 'INF'
132+
return expr_flt(-INFINITY, dash.line, dash.column);
133+
}
134+
if (strcmp(parser->next_token.literal, "NaN") == 0) {
135+
report_error(parser, "NaN must not be negative");
136+
return NULL;
137+
}
138+
}
112139
if (parser->current_token.type == TOKEN_ASYNC) {
113140
Token kw = parser->current_token;
114141
advance(parser);

tests/test2.pre

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,31 @@ DEL(f_quarter)
2727
DEL(f_threeq)
2828
PRINT("Floats: PASS\n")
2929

30+
PRINT("Testing INF/NaN literals and conversions...")
31+
FLT: f_inf = INF
32+
FLT: f_ninf = -INF
33+
FLT: f_nan = NaN
34+
ASSERT(ISFLT(f_inf))
35+
ASSERT(ISFLT(f_ninf))
36+
ASSERT(ISFLT(f_nan))
37+
38+
ASSERT(EQ(f_inf, INF))
39+
ASSERT(EQ(f_ninf, -INF))
40+
ASSERT(NOT(EQ(f_nan, NaN))) ! NaN is not equal to itself
41+
42+
TNS: tflts = TFLT(["INF", "-INF", "NaN"])
43+
ASSERT(ISFLT(tflts[1]))
44+
ASSERT(ISFLT(tflts[10]))
45+
ASSERT(ISFLT(tflts[11]))
46+
ASSERT(EQ(tflts[1], INF))
47+
ASSERT(EQ(tflts[10], -INF))
48+
ASSERT(NOT(EQ(tflts[11], NaN)))
49+
DEL(tflts)
50+
DEL(f_inf)
51+
DEL(f_ninf)
52+
DEL(f_nan)
53+
PRINT("INF/NaN: PASS\n")
54+
3055
! String literals
3156
PRINT("Testing strings...")
3257
STR: s1 = "Hello, World!"

0 commit comments

Comments
 (0)