diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d3678b..0f507fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,9 @@ jobs: repository: pcsx-redux/nugget path: nugget + - name: Number primitives + run: cc -Wall -I src -o /tmp/numbers tests/numbers.c && /tmp/numbers + - name: Library run: make -C src psx -j2 diff --git a/src/llibc.c b/src/llibc.c index 31fb7d2..8b4765b 100644 --- a/src/llibc.c +++ b/src/llibc.c @@ -122,65 +122,57 @@ static const inline int strisspace(char c) { c == '\v'; } +static int digitval(unsigned char c) { + if ((c >= '0') && (c <= '9')) return c - '0'; + if ((c >= 'a') && (c <= 'z')) return c - 'a' + 10; + if ((c >= 'A') && (c <= 'Z')) return c - 'A' + 10; + return 36; +} + +/* +** Same contract as strtol: *endptr is set on every return, and points at +** nptr when no digit was read. +*/ long int luaA_strtol(const char *nptr, char **endptr, int base) { + const char *s = nptr; + int negative = 0; + int digits = 0; + unsigned long int r = 0; + + if (endptr) { + *endptr = (char *)nptr; + } if (base < 0 || base == 1 || base >= 37) { return 0; } - if ((*nptr == '0') && ((*(nptr + 1) == 'x') || (*(nptr + 1) == 'X'))) { - if ((base != 0) && (base != 16)) { - return 0; - } + while (strisspace(*s)) { + s++; + } + if ((*s == '+') || (*s == '-')) { + negative = *s == '-'; + s++; + } + + if (((base == 0) || (base == 16)) && (s[0] == '0') && + ((s[1] == 'x') || (s[1] == 'X')) && (digitval(s[2]) < 16)) { base = 16; - nptr += 2; - } else if (*nptr == '0') { - if ((base != 0) && (base != 8) && (*(nptr + 1) != 0)) { - return 0; - } - base = 8; - nptr++; + s += 2; } else if (base == 0) { - base = 10; + base = (*s == '0') ? 8 : 10; } - int got_something = 0; - int negative = 0; - unsigned long int r = 0; - - char c; - char maxLC = 'a' + (base - 10) - 1; - char maxUC = 'A' + (base - 10) - 1; - - while ((c = *nptr++)) { - if (strisspace(c) && !got_something) { - continue; - } - if (((c == '+') || (c == '-')) && !got_something) { - got_something = 1; - if (c == '-') { - negative = 1; - } - continue; - } - if ((c >= '0') && (c <= '9')) { - c -= '0'; - } else if ((c >= 'a') && (c <= maxLC)) { - c -= 'a' - 10; - } else if ((c >= 'A') && (c <= maxUC)) { - c -= 'A' - 10; - } else { + for (;; s++) { + int d = digitval(*s); + if (d >= base) { break; } - if (c >= base) { - break; - } - got_something = 1; - r *= base; - r += c; + r = r * base + d; + digits = 1; } - if (endptr) { - *endptr = (char *)nptr - 1; + if (endptr && digits) { + *endptr = (char *)s; } return negative ? -r : r; } diff --git a/src/luaconf.h b/src/luaconf.h index c73c9b8..3998fae 100644 --- a/src/luaconf.h +++ b/src/luaconf.h @@ -454,12 +454,23 @@ long int luaA_strtol(const char *nptr, char **endptr, int base); /* the following operations need the math library */ #if defined(lobject_c) || defined(lvm_c) #define luai_nummod(L,a,b) ((a) % (b)) +/* +** Integer power. A negative exponent gives the truncated quotient +** 1 / a^-b, which is 0 unless |a| is 1, and 0 for 0^-b. Overflow wraps. +*/ static inline long luai_numpowimpl(long a, long b) { - long r = a; - for (unsigned i = 0; i < b; i++) { - r *= a; + unsigned long r = 1, x = (unsigned long)a; + if (b < 0) { + if (a == 1) return 1; + if (a == -1) return (b & 1) ? -1 : 1; + return 0; + } + while (b) { + if (b & 1) r *= x; + x *= x; + b >>= 1; } - return r; + return (long)r; } #define luai_numpow(L,a,b) (luai_numpowimpl((a),(b))) #endif diff --git a/tests/numbers.c b/tests/numbers.c new file mode 100644 index 0000000..fd62615 --- /dev/null +++ b/tests/numbers.c @@ -0,0 +1,74 @@ +/* + * Host checks for the integer number primitives: luai_numpow (constant + * folding and OP_POW) and luaA_strtol (lexer numerals and tonumber). + * + * cc -I src -o numbers tests/numbers.c && ./numbers + */ + +#define lobject_c +#include "luaconf.h" +#include "llibc.c" + +#include +#include + +static int failures = 0; + +static void pow_case(long a, long b, long expected) { + long got = luai_numpowimpl(a, b); + if (got != expected) { + printf("FAIL %ld^%ld = %ld, expected %ld\n", a, b, got, expected); + failures++; + } +} + +static void strtol_case(const char *s, int base, long expected, int consumed) { + char *end = (char *)0x1; /* anything but s, so an unset endptr shows */ + long got = luaA_strtol(s, &end, base); + if ((got != expected) || (end != s + consumed)) { + printf("FAIL strtol(\"%s\", %d) = %ld, end +%ld, expected %ld, end +%d\n", + s, base, got, (long)(end - s), expected, consumed); + failures++; + } +} + +int main(void) { + pow_case(2, 10, 1024); + pow_case(3, 0, 1); + pow_case(0, 0, 1); + pow_case(-3, 3, -27); + pow_case(10, 9, 1000000000); + pow_case(2, -1, 0); + pow_case(1, -5, 1); + pow_case(-1, -3, -1); + pow_case(-1, -4, 1); + pow_case(0, -1, 0); + + strtol_case("10", 10, 10, 2); + strtol_case("010", 10, 10, 3); + strtol_case("00", 10, 0, 2); + strtol_case("0", 10, 0, 1); + strtol_case("0.5", 10, 0, 1); + strtol_case("-010", 10, -10, 4); + strtol_case(" 42 ", 10, 42, 4); + strtol_case("", 10, 0, 0); + strtol_case(" ", 10, 0, 0); + strtol_case("-", 10, 0, 0); + strtol_case("abc", 10, 0, 0); + strtol_case("0x1F", 16, 31, 4); + strtol_case("-0x10", 16, -16, 5); + strtol_case("0x", 16, 0, 1); + strtol_case("0x1F", 10, 0, 1); + strtol_case("017", 0, 15, 3); + strtol_case("0x1f", 0, 31, 4); + strtol_case("17", 0, 17, 2); + strtol_case("z", 36, 35, 1); + strtol_case("1", 1, 0, 0); + + if (failures) { + printf("%d failure(s)\n", failures); + return 1; + } + printf("numbers: all checks passed\n"); + return 0; +} diff --git a/tests/sample.lua b/tests/sample.lua index 1b71f3e..a0ede68 100644 --- a/tests/sample.lua +++ b/tests/sample.lua @@ -1,3 +1,5 @@ local squares = {} for i = 1, 10 do squares[i] = i * i end +-- Folded at compile time on the console: 1024 + 10. +squares.folded = 2^10 + 010 return squares