Skip to content

Commit 6346554

Browse files
Remove base prefix from decimal numbers (closes #240)
1 parent 6d1f362 commit 6346554

7 files changed

Lines changed: 159 additions & 63 deletions

File tree

‎README.md‎

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ Prefix is a programming language focused on explicit, readable code.
1111
! ---------- modular inverse via the extended Euclidean algorithm ----------
1212
func int modinv(int a, int m){
1313
a = MOD(a, m)
14-
if(LT(a, 0d0)){ a = +(a, m) }
14+
if(LT(a, 0)){ a = +(a, m) }
1515
int r0 = m
1616
int r1 = a
17-
int t0 = 0d0
18-
int t1 = 0d1
19-
while(NEQ(r1, 0d0)){
17+
int t0 = 0
18+
int t1 = 1
19+
while(NEQ(r1, 0)){
2020
int q = /(r0, r1)
2121
int nr = -(r0, *(q, r1))
2222
int nt = -(t0, *(q, t1))
@@ -25,11 +25,11 @@ func int modinv(int a, int m){
2525
t0 = t1
2626
t1 = nt
2727
}
28-
if(NEQ(r0, 0d1)){
28+
if(NEQ(r0, 0)){
2929
THROW("modinv: arguments not coprime")
3030
}
3131
int inv = MOD(t0, m)
32-
if(LT(inv, 0d0)){ inv = +(inv, m) }
32+
if(LT(inv, 0)){ inv = +(inv, m) }
3333
return(inv)
3434
}
3535
@@ -38,9 +38,9 @@ func int modinv(int a, int m){
3838
! The modulus is kept below 2^31 so that products of two consecutive
3939
! differences (each < m) never overflow Prefix's signed 64-bit int.
4040
int m = 0x186A1
41-
int a = 0d16807
42-
int c = 0d12345
43-
int seed = 0d48271
41+
int a = 16807
42+
int c = 12345
43+
int seed = 48271
4444
4545
func int step(int s){
4646
return(MOD(+( *(a, s), c), m))
@@ -49,7 +49,7 @@ func int step(int s){
4949
! ---------- collect observed outputs (all the attacker sees) ----------
5050
map obs
5151
int s = seed
52-
for(i, 0d8){
52+
for(i, 8){
5353
obs<i> = s
5454
s = step(s)
5555
}
@@ -61,18 +61,18 @@ DEL("s")
6161
! u_k = t_{k+1} * t_{k-1} - t_k^2 is a multiple of m. GCD of several
6262
! |u_k| yields m.
6363
map d
64-
for(i, 0d7){
65-
d<i> = -(obs<+(i, 0d1)>, obs<i>)
64+
for(i, 7){
65+
d<i> = -(obs<+(i, 1)>, obs<i>)
6666
}
6767
68-
int mrec = 0d0
69-
int k = 0d2
70-
while(LTE(k, 0d6)){
71-
int lo = -(k, 0d1)
72-
int hi = +(k, 0d1)
68+
int mrec = 0
69+
int k = 2
70+
while(LTE(k, 6)){
71+
int lo = -(k, 1)
72+
int hi = +(k, 1)
7373
int tk = d<k>
7474
mrec = GCD(mrec, ABS(-( *(d<hi>, d<lo>), *(tk, tk) )))
75-
k = +(k, 0d1)
75+
k = +(k, 1)
7676
}
7777
DEL("d")
7878
DEL("k")
@@ -100,11 +100,11 @@ DEL("x1")
100100
DEL("modinv")
101101
102102
! ---------- verify by regenerating the whole sequence ----------
103-
int ok = 0d1
103+
int ok = 1
104104
int v = x0
105-
for(i, 0d8){
105+
for(i, 8){
106106
if(NEQ(v, obs<i>)){
107-
ok = 0d0
107+
ok = 0
108108
}
109109
v = MOD(+( *(arec, v), crec), mrec)
110110
}
@@ -124,7 +124,7 @@ DEL("step")
124124
PRINT("next output predicted: ", next_pred)
125125
PRINT("next output actual : ", next_true)
126126
127-
ASSERT(EQ(ok, 0d1))
127+
ASSERT(EQ(ok, 1))
128128
ASSERT(EQ(mrec, m))
129129
ASSERT(EQ(arec, a))
130130
ASSERT(EQ(crec, c))

‎docs/SPECIFICATION.html‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@
6464

6565
Identifiers MUST be non-empty and case-sensitive. Variables and user-defined functions share a single flat namespace, so one name MUST NOT denote both a variable and a function. A user-defined function name MUST NOT conflict with any built-in operator or function name.
6666

67-
Identifiers MUST NOT contain non-ASCII characters or any of the following characters: `{`, `}`, `[`, `]`, `(`, `)`, `=`, `,`, `!`, `~`, or `@`. The first character of an identifier MUST NOT be `0`, and the first two characters of an identifier MUST NOT be `-0`.
67+
Identifiers MUST NOT contain non-ASCII characters or any of the following characters: `{`, `}`, `[`, `]`, `(`, `)`, `=`, `,`, `!`, `~`, or `@`. The first character of an identifier MUST NOT be a decimal digit (`0`-`9`), and the first two characters of an identifier MUST NOT be `-0`.
6868

69-
The first identifier character MAY be a letter `A-Z` or `a-z`, a decimal digit `1-9`, `*`, or one of `/`, `$`, `%`, `&`, `_`, `+`, `|`, `?`, or `-`. If the first identifier character is `-`, the second character MAY be any otherwise valid identifier character except `0`. Subsequent identifier characters MAY additionally include the digit `0`, `*`, and `-`. The exact spelling `-INF` is reserved by [4.3.2](#432-special-values). This permissive ASCII-only character set preserves an unambiguous distinction between identifiers and numeric literals, which MUST begin with a `0`-prefixed base marker.
69+
The first identifier character MAY be a letter `A-Z` or `a-z`, `*`, or one of `/`, `$`, `%`, `&`, `_`, `+`, `|`, `?`, or `-`. If the first identifier character is `-`, the second character MUST be valid as an opening character. Subsequent identifier characters MAY additionally include the decimal digits `0-9`, `*`, and `-`. The exact spelling `-INF` is reserved by [4.3.2](#432-special-values). This permissive ASCII-only character set preserves an unambiguous distinction between identifiers and numeric literals, which MUST begin with a decimal digit.
7070

7171
---
7272

@@ -268,7 +268,9 @@
268268
269269
#### 4.2.1 Integer literals
270270
271-
`int` literals MUST include a base prefix. Valid prefixes are `0b`, `0o`, `0d`, `0x`, `0t`, `0c`, `0s`, and `0rNN` where `NN` is two decimal digits and `2 <= NN <= 64`.
271+
Non-decimal `int` literals MUST include a base prefix. Valid prefixes are `0b`, `0o`, `0d`, `0x`, `0t`, `0c`, `0s`, and `0rNN` where `NN` is two decimal digits and `2 <= NN <= 64`.
272+
273+
An `int` literal that begins with a decimal digit (`0`-`9`) and does NOT include a base prefix MUST be evaluated as a decimal (base-10) literal. A numeric literal that begins with a decimal digit and includes a base prefix (that is, one or more digits precede the base prefix) MUST raise an error.
272274
273275
`int` literals MUST support negative values via prefixing with a `-` before the base prefix, and positive values with no sign. `int` literals MUST NOT support a `+` prefix.
274276
@@ -290,9 +292,11 @@
290292
291293
#### 4.3.1 Floating-point literals
292294
293-
`float` literals MUST include a base prefix using the same prefix forms as `int` literals.
295+
Non-decimal `float` literals MUST include a base prefix using the same prefix forms as `int` literals.
296+
297+
A `float` literal that begins with a decimal digit (`0`-`9`) and does NOT include a base prefix MUST be evaluated as a decimal (base-10) literal. A `float` literal that begins with a decimal digit and includes a base prefix (that is, one or more digits precede the base prefix) MUST raise an error.
294298
295-
`float` literals MUST consist of a prefixed integer part, a radix point (`.`), and a fractional part. The integer and fractional parts MUST each contain at least one digit in the selected base.
299+
`float` literals MUST consist of an integer part (preceded by the base prefix, if present), a radix point (`.`), and a fractional part. The integer and fractional parts MUST each contain at least one digit in the selected base.
296300
297301
`float` literals MUST support negative values via prefixing with a `-` before the base prefix, and positive values with no sign. `float` literals MUST NOT support a `+` prefix.
298302

‎src/lexer.c‎

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ static bool is_number_body_char(char c) {
462462
}
463463

464464
static bool is_identifier_start_char(char c) {
465-
return strchr("abcdefghijklmnopqrstuvwxyz123456789/ABCDEFGHIJKLMNOPQRSTUVWXYZ$%&_+|?*", c) != NULL;
465+
return strchr("abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ$%&_+|?*", c) != NULL;
466466
}
467467

468468
static bool is_identifier_body_char(char c) {
@@ -650,7 +650,8 @@ Token lexer_next_token(Lexer *lexer) {
650650
return number_token(lexer, true);
651651
}
652652

653-
if ((peek_next(lexer) == '0') || matches_reserved_signed_special(lexer, lookahead, "INF") ||
653+
if ((isdigit((unsigned char)peek_next(lexer))) ||
654+
matches_reserved_signed_special(lexer, lookahead, "INF") ||
654655
matches_reserved_signed_special(lexer, lookahead, "NaN")) {
655656
advance(lexer);
656657
return make_token(lexer, TOKEN_DASH, "-", 1);
@@ -659,7 +660,7 @@ Token lexer_next_token(Lexer *lexer) {
659660
return identifier_token(lexer);
660661
}
661662

662-
if (c == '0' && is_base_prefix_char(peek_next(lexer))) {
663+
if (isdigit((unsigned char)c)) {
663664
return number_token(lexer, false);
664665
}
665666

@@ -752,40 +753,43 @@ static Token number_token(Lexer *lexer, bool is_negative_start) {
752753
value[len_val++] = '-';
753754
}
754755

755-
if (peek(lexer) != '0') {
756-
free(value);
757-
return error_token(lexer, "Invalid numeric literal; expected base prefix");
758-
}
759-
if (len_val + 1 >= capacity) {
760-
capacity *= 2;
761-
value = safe_realloc(value, capacity);
762-
}
763-
value[len_val++] = advance(lexer); // 0
756+
bool prefixed = (peek(lexer) == '0' && is_base_prefix_char(peek_next(lexer)));
764757

765-
if (!is_base_prefix_char(peek(lexer))) {
758+
if (is_negative_start && !prefixed) {
766759
free(value);
767-
return error_token(lexer, "Invalid numeric literal base prefix");
768-
}
769-
if (len_val + 1 >= capacity) {
770-
capacity *= 2;
771-
value = safe_realloc(value, capacity);
760+
return error_token(lexer, "Invalid numeric literal; expected base prefix");
772761
}
773-
char pref = advance(lexer);
774-
value[len_val++] = pref;
775762

776-
if (pref == 'r') {
777-
if (!isdigit((unsigned char)peek(lexer)) || !isdigit((unsigned char)peek_next(lexer))) {
778-
free(value);
779-
return error_token(lexer, "0r literals require two decimal base digits");
763+
if (prefixed) {
764+
if (len_val + 1 >= capacity) {
765+
capacity *= 2;
766+
value = safe_realloc(value, capacity);
780767
}
781-
if (len_val + 2 >= capacity) {
768+
value[len_val++] = advance(lexer); // 0
769+
770+
if (len_val + 1 >= capacity) {
782771
capacity *= 2;
783772
value = safe_realloc(value, capacity);
784773
}
785-
value[len_val++] = advance(lexer);
786-
value[len_val++] = advance(lexer);
774+
char pref = advance(lexer);
775+
value[len_val++] = pref;
776+
777+
if (pref == 'r') {
778+
if (!isdigit((unsigned char)peek(lexer)) || !isdigit((unsigned char)peek_next(lexer))) {
779+
free(value);
780+
return error_token(lexer, "0r literals require two decimal base digits");
781+
}
782+
if (len_val + 2 >= capacity) {
783+
capacity *= 2;
784+
value = safe_realloc(value, capacity);
785+
}
786+
value[len_val++] = advance(lexer);
787+
value[len_val++] = advance(lexer);
788+
}
787789
}
788790

791+
size_t digits_start = len_val;
792+
789793
bool has_any = false;
790794
bool has_dot = false;
791795
bool has_frac = false;
@@ -841,6 +845,21 @@ static Token number_token(Lexer *lexer, bool is_negative_start) {
841845
return error_token(lexer, "Float literal requires fractional digits");
842846
}
843847

848+
if (!prefixed) {
849+
for (size_t i = digits_start; i < len_val; i++) {
850+
if (value[i] == '0' && i + 1 < len_val && is_base_prefix_char(value[i + 1])) {
851+
free(value);
852+
return error_token(lexer, "Numeric literal beginning with a digit must not include a base prefix");
853+
}
854+
}
855+
for (size_t i = digits_start; i < len_val; i++) {
856+
if (value[i] != '.' && !isdigit((unsigned char)value[i])) {
857+
free(value);
858+
return error_token(lexer, "Invalid digit in decimal literal");
859+
}
860+
}
861+
}
862+
844863
value[len_val] = '\0';
845864
Token t = {(int)has_dot ? TOKEN_FLOAT : TOKEN_NUMBER, value, start_line, start_col};
846865
return t;

‎src/parser.c‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ static int parse_prefixed_int_literal(const char *lit, int64_t *out_value, int *
469469

470470
size_t prefix_len = 0;
471471
int base = base_from_literal_prefix(s, &prefix_len);
472+
if (base < 0 && isdigit((unsigned char)s[0])) {
473+
base = 10;
474+
prefix_len = 0;
475+
}
472476
if (base < 2 || base > 64) {
473477
return 0;
474478
}
@@ -510,6 +514,10 @@ static int parse_prefixed_float_literal(const char *lit, double *out_value, int
510514

511515
size_t prefix_len = 0;
512516
int base = base_from_literal_prefix(s, &prefix_len);
517+
if (base < 0 && isdigit((unsigned char)s[0])) {
518+
base = 10;
519+
prefix_len = 0;
520+
}
513521
if (base < 2 || base > 64) {
514522
return 0;
515523
}

‎tests/cases/lexer/identifiers.yaml‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,6 @@ passing:
5454
bool x = TRUE
5555
bool y = TRUE
5656
bool z = TRUE
57-
bool 2 = TRUE
58-
bool 3 = TRUE
59-
bool 4 = TRUE
60-
bool 5 = TRUE
61-
bool 6 = TRUE
62-
bool 7 = TRUE
63-
bool 8 = TRUE
64-
bool 9 = TRUE
65-
bool 1 = TRUE
6657
bool / = TRUE
6758
bool $ = TRUE
6859
bool % = TRUE
@@ -106,3 +97,19 @@ failing:
10697
source: |
10798
bool 0x = TRUE
10899
100+
- name: digit-starts-ident
101+
language: Prefix
102+
source: |
103+
bool 1x = TRUE
104+
105+
- name: nine-starts-ident
106+
language: Prefix
107+
source: |
108+
bool 9lives = TRUE
109+
110+
- name: dash-before-digit
111+
language: Prefix
112+
source: |
113+
bool -1abc = TRUE
114+
115+

‎tests/cases/literals/flt.yaml‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,33 @@ passing:
1717
ASSERT(EQ(d, 0xA.F))
1818
ASSERT(EQ(e, 0r16F.8))
1919
20+
- name: flt-decimal-no-prefix
21+
language: Prefix
22+
source: |
23+
float a = 1.5
24+
float b = 0.25
25+
float c = 12.75
26+
27+
ASSERT(EQ(a, 0d1.5))
28+
ASSERT(EQ(b, 0d0.25))
29+
ASSERT(EQ(c, 0d12.75))
30+
2031
failing:
2132
- name: flt-invalid-digit
2233
language: Prefix
2334
source: |
2435
float a = 0b2.0
2536
37+
- name: flt-digit-before-prefix
38+
language: Prefix
39+
source: |
40+
float a = 10x1.F
41+
42+
- name: flt-decimal-invalid-digit
43+
language: Prefix
44+
source: |
45+
float a = 1.5F
46+
2647
- name: flt-negative-nan
2748
language: Prefix
2849
source: |

0 commit comments

Comments
 (0)