diff --git a/src/pk_compiler.c b/src/pk_compiler.c index bbd82e6..fdbe3e1 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -814,7 +814,7 @@ static void eatNumber(Compiler* compiler) { char c = *parser->token_start; // Binary literal. - if (c == '0' && peekChar(parser) == 'b') { + if (c == '0' && ((peekChar(parser) == 'b') || (peekChar(parser) == 'B'))) { eatChar(parser); // Consume '0b' uint64_t bin = 0; @@ -845,7 +845,8 @@ static void eatNumber(Compiler* compiler) { } value = VAR_NUM((double)bin); - } else if (c == '0' && peekChar(parser) == 'x') { + } else if (c == '0' && + ((peekChar(parser) == 'x') || (peekChar(parser) == 'X'))) { eatChar(parser); // Consume '0x' uint64_t hex = 0; @@ -883,14 +884,17 @@ static void eatNumber(Compiler* compiler) { } } else { // Regular number literal. + while (utilIsDigit(peekChar(parser))) { eatChar(parser); } - if (peekChar(parser) == '.' && utilIsDigit(peekNextChar(parser))) { - matchChar(parser, '.'); - while (utilIsDigit(peekChar(parser))) { - eatChar(parser); + if (c != '.') { // Number starts with a decimal point. + if (peekChar(parser) == '.' && utilIsDigit(peekNextChar(parser))) { + matchChar(parser, '.'); + while (utilIsDigit(peekChar(parser))) { + eatChar(parser); + } } } diff --git a/src/pk_core.c b/src/pk_core.c index 52eb001..0690b16 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -769,8 +769,20 @@ static void _ctorBool(PKVM* vm) { static void _ctorNumber(PKVM* vm) { double value; - if (!validateNumeric(vm, ARG(1), &value, "Argument 1")) return; - RET(VAR_NUM(value)); + if (isNumeric(ARG(1), &value)) { + RET(VAR_NUM(value)); + } + + if (IS_OBJ_TYPE(ARG(1), OBJ_STRING)) { + String* str = (String*) AS_OBJ(ARG(1)); + double value; + const char* err = utilToNumber(str->data, &value); + if (err == NULL) RET(VAR_NUM(value)); + VM_SET_ERROR(vm, newString(vm, err)); + RET(VAR_NULL); + } + + VM_SET_ERROR(vm, newString(vm, "Argument must be numeric or string.")); } static void _ctorString(PKVM* vm) { diff --git a/src/pk_utils.c b/src/pk_utils.c index 577292a..8ee7eae 100644 --- a/src/pk_utils.c +++ b/src/pk_utils.c @@ -6,6 +6,11 @@ #include "pk_utils.h" +#include +#include +#include +#include + // Function implementation, see utils.h for description. int utilPowerOf2Ceil(int n) { n--; @@ -94,6 +99,122 @@ uint32_t utilHashString(const char* string) { #undef FNV_offset_basis_32_bit } +const char* utilToNumber(const char* str, double* num) { +#define IS_HEX_CHAR(c) \ + (('0' <= (c) && (c) <= '9') || \ + ('a' <= (c) && (c) <= 'f')) + +#define IS_BIN_CHAR(c) (((c) == '0') || ((c) == '1')) +#define IS_DIGIT(c) (('0' <= (c)) && ((c) <= '9')) +#define INVALID_NUMERIC_STRING "Invalid numeric string." + + assert((str != NULL) && (num != NULL)); + uint32_t length = (uint32_t) strlen(str); + + // Consume the sign. + double sign = +1; + if (*str == '-') { + sign = -1; + str++; + } else if (*str == '+') { + str++; + } + + // Binary String. + if (length >= 3 && + ((strncmp(str, "0b", 2) == 0) || (strncmp(str, "0B", 2) == 0))) { + + uint64_t bin = 0; + const char* c = str + 2; + + if (*c == '\0') return INVALID_NUMERIC_STRING; + + do { + if (*c == '\0') { + *num = sign * bin; + return NULL; + }; + + if (!IS_BIN_CHAR(*c)) return INVALID_NUMERIC_STRING; + if ((c - str) > (68 /*STR_BIN_BUFF_SIZE*/ - 2)) { // -2 for '-, \0'. + return "Binary literal is too long."; + } + + bin = (bin << 1) | (*c - '0'); + c++; + + } while (true); + } + + // Hex String. + if (length >= 3 && + ((strncmp(str, "0x", 2) == 0) || (strncmp(str, "0X", 2) == 0))) { + + uint64_t hex = 0; + const char* c = str + 2; + + if (*c == '\0') return INVALID_NUMERIC_STRING; + + do { + if (*c == '\0') { + *num = sign * hex; + return NULL; + }; + + if (!IS_HEX_CHAR(*c)) return INVALID_NUMERIC_STRING; + if ((c - str) > (20 /*STR_HEX_BUFF_SIZE*/ - 2)) { // -2 for '-, \0'. + return "Hex literal is too long."; + } + + uint8_t digit = ('0' <= *c && *c <= '9') + ? (uint8_t)(*c - '0') + : (uint8_t)((*c - 'a') + 10); + + hex = (hex << 4) | digit; + c++; + + } while (true); + } + + // Regular number. + if (*str == '\0') return INVALID_NUMERIC_STRING; + + const char* c = str; + do { + + while (IS_DIGIT(*c)) c++; + if (*c == '.') { // TODO: allowing "1." as a valid float.? + c++; + while (IS_DIGIT(*c)) c++; + } + + if (*c == '\0') break; // Done. + + if (*c == 'e' || *c == 'E') { + c++; + if (*c == '+' || *c == '-') c++; + + if (!IS_DIGIT(*c)) return INVALID_NUMERIC_STRING; + while (IS_DIGIT(*c)) c++; + if (*c != '\0') return INVALID_NUMERIC_STRING; + } + + if (*c != '\0') return INVALID_NUMERIC_STRING; + + } while (false); + + errno = 0; + *num = atof(str) * sign; + if (errno == ERANGE) return "Numeric string is too long."; + + return NULL; + +#undef INVALID_NUMERIC_STRING +#undef IS_DIGIT +#undef IS_HEX_CHAR +#undef IS_BIN_CHAR +} + /**************************************************************************** * UTF8 * ****************************************************************************/ diff --git a/src/pk_utils.h b/src/pk_utils.h index 2b47161..9ee8e51 100644 --- a/src/pk_utils.h +++ b/src/pk_utils.h @@ -38,6 +38,11 @@ uint32_t utilHashNumber(double num); // Generate a has code for [string]. uint32_t utilHashString(const char* string); +// Convert the string to number. On success it'll return NULL and set the +// [num] value. Otherwise it'll return a C literal string containing the error +// message. +const char* utilToNumber(const char* str, double* num); + /**************************************************************************** * UTF8 * ****************************************************************************/ diff --git a/tests/lang/builtin_ty.pk b/tests/lang/builtin_ty.pk index 3ab39ae..d581854 100644 --- a/tests/lang/builtin_ty.pk +++ b/tests/lang/builtin_ty.pk @@ -2,7 +2,7 @@ ## Builtin types attributes and methods tests. ############################################################################### -## INTEGER +## Number ############################################################################### sum = 0 @@ -11,6 +11,10 @@ sum = 0 end assert(sum == 0 + 1 + 2 + 3 + 4) +assert(Number("-.23e+6") == -.23e6) +assert(Number("0b10100101") == 0b10100101) +assert(Number("-0Xabcdef123") == -0xabcdef123) + ############################################################################### ## STRING ###############################################################################