aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.md11
-rw-r--r--Makefile2
-rw-r--r--api/api.md6
-rw-r--r--api/integers.md6
-rw-r--r--api/integers.yaml12
-rw-r--r--man/man3/tomo-Int.34
-rw-r--r--man/man3/tomo-Int.parse.38
-rw-r--r--src/compile/functions.c3
-rw-r--r--src/environment.c18
-rw-r--r--src/stdlib/bigint.c119
-rw-r--r--src/stdlib/bigint.h2
-rw-r--r--src/stdlib/bytes.c4
-rw-r--r--src/stdlib/bytes.h2
-rw-r--r--src/stdlib/cli.c10
-rw-r--r--src/stdlib/intX.c.h4
-rw-r--r--src/stdlib/intX.h2
-rw-r--r--src/typecheck.c3
-rw-r--r--test/integers.tm21
18 files changed, 169 insertions, 68 deletions
diff --git a/CHANGES.md b/CHANGES.md
index b4910bb8..a60c854a 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,16 @@
# Version History
+## v2025-11-30
+
+### API changes
+
+- Added `base` parameter to various `Int.parse()` methods to allow explicitly
+ setting the numeric base from 1-36.
+
+### Bugfixes
+
+- Fixed various issues around parsing integers.
+
## v2025-11-29.2
### Bugfixes
diff --git a/Makefile b/Makefile
index 2bfde7a1..ad521916 100644
--- a/Makefile
+++ b/Makefile
@@ -226,7 +226,7 @@ check-utilities: check-c-compiler
@which debugedit 2>/dev/null >/dev/null \
|| printf '\033[33;1m%s\033[m\n' "I couldn't find 'debugedit' on your system! Try installing the package 'debugedit' with your package manager. (It's not required though)"
-install-files: build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) check-utilities
+install-files: $(INCLUDE_SYMLINK) build/bin/$(EXE_FILE) build/lib/$(LIB_FILE) build/lib/$(AR_FILE) $(MODULES_FILE) check-utilities
@if ! echo "$$PATH" | tr ':' '\n' | grep -qx "$(PREFIX)/bin"; then \
echo $$PATH; \
printf "\033[31;1mError: '$(PREFIX)/bin' is not in your \$$PATH variable!\033[m\n" >&2; \
diff --git a/api/api.md b/api/api.md
index f52691d3..6a7d218b 100644
--- a/api/api.md
+++ b/api/api.md
@@ -662,7 +662,7 @@ assert nums[] == [5, 6, 7, 8, 9, 10]
## Int.parse
```tomo
-Int.parse : func(text: Text, remainder: &Text? = none -> Int?)
+Int.parse : func(text: Text, base: Int? = none, remainder: &Text? = none -> Int?)
```
Converts a text representation of an integer into an integer.
@@ -670,6 +670,7 @@ Converts a text representation of an integer into an integer.
Argument | Type | Description | Default
---------|------|-------------|---------
text | `Text` | The text containing the integer. | -
+base | `Int?` | The numeric base to use when parsing the integer. If unspecified, the integer's base will be inferred from the text prefix. After any "+" or "-" sign, if the text begins with "0x", the base will be assumed to be 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the base will be assumed to be 10. | `none`
remainder | `&Text?` | If non-none, this argument will be set to the remainder of the text after the matching part. If none, parsing will only succeed if the entire text matches. | `none`
**Return:** The integer represented by the text. If the given text contains a value outside of the representable range or if the entire text can't be parsed as an integer, `none` will be returned.
@@ -690,6 +691,9 @@ assert Int.parse("asdf") == none
# Outside valid range:
assert Int8.parse("9999999") == none
+# Explicitly specifying base:
+assert Int.parse("10", base=16) == 16
+
```
## Int.prev_prime
diff --git a/api/integers.md b/api/integers.md
index 6af66b0d..ef3a6a60 100644
--- a/api/integers.md
+++ b/api/integers.md
@@ -255,7 +255,7 @@ assert nums[] == [5, 6, 7, 8, 9, 10]
## Int.parse
```tomo
-Int.parse : func(text: Text, remainder: &Text? = none -> Int?)
+Int.parse : func(text: Text, base: Int? = none, remainder: &Text? = none -> Int?)
```
Converts a text representation of an integer into an integer.
@@ -263,6 +263,7 @@ Converts a text representation of an integer into an integer.
Argument | Type | Description | Default
---------|------|-------------|---------
text | `Text` | The text containing the integer. | -
+base | `Int?` | The numeric base to use when parsing the integer. If unspecified, the integer's base will be inferred from the text prefix. After any "+" or "-" sign, if the text begins with "0x", the base will be assumed to be 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the base will be assumed to be 10. | `none`
remainder | `&Text?` | If non-none, this argument will be set to the remainder of the text after the matching part. If none, parsing will only succeed if the entire text matches. | `none`
**Return:** The integer represented by the text. If the given text contains a value outside of the representable range or if the entire text can't be parsed as an integer, `none` will be returned.
@@ -283,6 +284,9 @@ assert Int.parse("asdf") == none
# Outside valid range:
assert Int8.parse("9999999") == none
+# Explicitly specifying base:
+assert Int.parse("10", base=16) == 16
+
```
## Int.prev_prime
diff --git a/api/integers.yaml b/api/integers.yaml
index 70709b04..b3c6b579 100644
--- a/api/integers.yaml
+++ b/api/integers.yaml
@@ -280,6 +280,15 @@ Int.parse:
type: 'Text'
description: >
The text containing the integer.
+ base:
+ type: 'Int?'
+ default: 'none'
+ description: >
+ The numeric base to use when parsing the integer. If unspecified, the
+ integer's base will be inferred from the text prefix. After any "+" or
+ "-" sign, if the text begins with "0x", the base will be assumed to be
+ 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the
+ base will be assumed to be 10.
remainder:
type: '&Text?'
default: 'none'
@@ -300,6 +309,9 @@ Int.parse:
# Outside valid range:
assert Int8.parse("9999999") == none
+ # Explicitly specifying base:
+ assert Int.parse("10", base=16) == 16
+
Int.prev_prime:
short: get the previous prime
description: >
diff --git a/man/man3/tomo-Int.3 b/man/man3/tomo-Int.3
index 2476a27b..186c0aae 100644
--- a/man/man3/tomo-Int.3
+++ b/man/man3/tomo-Int.3
@@ -2,7 +2,7 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Int 3 2025-11-29 "Tomo man-pages"
+.TH Int 3 2025-11-30 "Tomo man-pages"
.SH NAME
Int \- a Tomo type
.SH LIBRARY
@@ -99,7 +99,7 @@ For more, see:
.TP
-.BI Int.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Int?)
+.BI Int.parse\ :\ func(text:\ Text,\ base:\ Int?\ =\ none,\ remainder:\ &Text?\ =\ none\ ->\ Int?)
Converts a text representation of an integer into an integer.
For more, see:
diff --git a/man/man3/tomo-Int.parse.3 b/man/man3/tomo-Int.parse.3
index df3888db..53713c78 100644
--- a/man/man3/tomo-Int.parse.3
+++ b/man/man3/tomo-Int.parse.3
@@ -2,14 +2,14 @@
.\" Copyright (c) 2025 Bruce Hill
.\" All rights reserved.
.\"
-.TH Int.parse 3 2025-11-29 "Tomo man-pages"
+.TH Int.parse 3 2025-11-30 "Tomo man-pages"
.SH NAME
Int.parse \- convert text to integer
.SH LIBRARY
Tomo Standard Library
.SH SYNOPSIS
.nf
-.BI Int.parse\ :\ func(text:\ Text,\ remainder:\ &Text?\ =\ none\ ->\ Int?)
+.BI Int.parse\ :\ func(text:\ Text,\ base:\ Int?\ =\ none,\ remainder:\ &Text?\ =\ none\ ->\ Int?)
.fi
.SH DESCRIPTION
Converts a text representation of an integer into an integer.
@@ -23,6 +23,7 @@ lb lb lbx lb
l l l l.
Name Type Description Default
text Text The text containing the integer. -
+base Int? The numeric base to use when parsing the integer. If unspecified, the integer's base will be inferred from the text prefix. After any "+" or "-" sign, if the text begins with "0x", the base will be assumed to be 16, "0o" will assume base 8, "0b" will assume base 2, otherwise the base will be assumed to be 10. none
remainder &Text? If non-none, this argument will be set to the remainder of the text after the matching part. If none, parsing will only succeed if the entire text matches. none
.TE
.SH RETURN
@@ -42,6 +43,9 @@ assert Int.parse("asdf") == none
# Outside valid range:
assert Int8.parse("9999999") == none
+
+# Explicitly specifying base:
+assert Int.parse("10", base=16) == 16
.EE
.SH SEE ALSO
.BR Tomo-Int (3)
diff --git a/src/compile/functions.c b/src/compile/functions.c
index cce93e3d..63d7d23d 100644
--- a/src/compile/functions.c
+++ b/src/compile/functions.c
@@ -6,6 +6,7 @@
#include "../stdlib/datatypes.h"
#include "../stdlib/integers.h"
#include "../stdlib/nums.h"
+#include "../stdlib/optionals.h"
#include "../stdlib/tables.h"
#include "../stdlib/text.h"
#include "../stdlib/util.h"
@@ -703,7 +704,7 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static
definition = Texts(definition, wrapper);
} else if (cache && cache->tag == Int) {
assert(args);
- OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NULL);
+ OptionalInt64_t cache_size = Int64$parse(Text$from_str(Match(cache, Int)->str), NONE_INT, NULL);
Text_t pop_code = EMPTY_TEXT;
if (cache->tag == Int && cache_size.has_value && cache_size.value > 0) {
// FIXME: this currently just deletes the first entry, but this
diff --git a/src/environment.c b/src/environment.c
index 43d22c3d..7e04fe79 100644
--- a/src/environment.c
+++ b/src/environment.c
@@ -93,7 +93,7 @@ env_t *global_env(bool source_mapping) {
MAKE_TYPE("Empty", EMPTY_TYPE, Text("Empty$$type"), Text("Empty$$info")),
MAKE_TYPE( //
"Bool", Type(BoolType), Text("Bool_t"), Text("Bool$info"),
- {"parse", "Bool$parse", "func(text:Text, remainder:&Text? = none -> Bool?)"}),
+ {"parse", "Bool$parse", "func(text:Text, remainder:&Text?=none -> Bool?)"}),
MAKE_TYPE( //
"Byte", Type(ByteType), Text("Byte_t"), Text("Byte$info"),
{"get_bit", "Byte$get_bit", "func(x:Byte, bit_index:Int -> Bool)"}, //
@@ -101,7 +101,7 @@ env_t *global_env(bool source_mapping) {
{"is_between", "Byte$is_between", "func(x:Byte, low:Byte, high:Byte -> Bool)"}, //
{"max", "Byte$max", "Byte"}, //
{"min", "Byte$min", "Byte"}, //
- {"parse", "Byte$parse", "func(text:Text, remainder:&Text? = none -> Byte?)"}, //
+ {"parse", "Byte$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Byte?)"}, //
{"to", "Byte$to", "func(first:Byte, last:Byte, step:Int8?=none -> func(->Byte?))"}),
MAKE_TYPE( //
"Int", Type(BigIntType), Text("Int_t"), Text("Int$info"), {"abs", "Int$abs", "func(x:Int -> Int)"}, //
@@ -126,7 +126,7 @@ env_t *global_env(bool source_mapping) {
{"next_prime", "Int$next_prime", "func(x:Int -> Int)"}, //
{"octal", "Int$octal", "func(i:Int, digits=0, prefix=yes -> Text)"}, //
{"onward", "Int$onward", "func(first:Int,step=1 -> func(->Int?))"}, //
- {"parse", "Int$parse", "func(text:Text, remainder:&Text? = none -> Int?)"}, //
+ {"parse", "Int$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int?)"}, //
{"plus", "Int$plus", "func(x,y:Int -> Int)"}, //
{"power", "Int$power", "func(base:Int,exponent:Int -> Int)"}, //
#if __GNU_MP_VERSION >= 6
@@ -145,7 +145,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int64$clamped", "func(x,low,high:Int64 -> Int64)"}, //
{"divided_by", "Int64$divided_by", "func(x,y:Int64 -> Int64)"}, //
{"gcd", "Int64$gcd", "func(x,y:Int64 -> Int64)"}, //
- {"parse", "Int64$parse", "func(text:Text, remainder:&Text? = none -> Int64?)"}, //
+ {"parse", "Int64$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int64?)"}, //
{"get_bit", "Int64$get_bit", "func(x:Int64, bit_index:Int -> Bool)"}, //
{"hex", "Int64$hex", "func(i:Int64, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
{"is_between", "Int64$is_between", "func(x:Int64,low:Int64,high:Int64 -> Bool)"}, //
@@ -167,7 +167,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int32$clamped", "func(x,low,high:Int32 -> Int32)"}, //
{"divided_by", "Int32$divided_by", "func(x,y:Int32 -> Int32)"}, //
{"gcd", "Int32$gcd", "func(x,y:Int32 -> Int32)"}, //
- {"parse", "Int32$parse", "func(text:Text, remainder:&Text? = none -> Int32?)"}, //
+ {"parse", "Int32$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int32?)"}, //
{"get_bit", "Int32$get_bit", "func(x:Int32, bit_index:Int -> Bool)"}, //
{"hex", "Int32$hex", "func(i:Int32, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
{"is_between", "Int32$is_between", "func(x:Int32,low:Int32,high:Int32 -> Bool)"}, //
@@ -189,7 +189,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int16$clamped", "func(x,low,high:Int16 -> Int16)"}, //
{"divided_by", "Int16$divided_by", "func(x,y:Int16 -> Int16)"}, //
{"gcd", "Int16$gcd", "func(x,y:Int16 -> Int16)"}, //
- {"parse", "Int16$parse", "func(text:Text, remainder:&Text? = none -> Int16?)"}, //
+ {"parse", "Int16$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int16?)"}, //
{"get_bit", "Int16$get_bit", "func(x:Int16, bit_index:Int -> Bool)"}, //
{"hex", "Int16$hex", "func(i:Int16, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
{"is_between", "Int16$is_between", "func(x:Int16,low:Int16,high:Int16 -> Bool)"}, //
@@ -211,7 +211,7 @@ env_t *global_env(bool source_mapping) {
{"clamped", "Int8$clamped", "func(x,low,high:Int8 -> Int8)"}, //
{"divided_by", "Int8$divided_by", "func(x,y:Int8 -> Int8)"}, //
{"gcd", "Int8$gcd", "func(x,y:Int8 -> Int8)"}, //
- {"parse", "Int8$parse", "func(text:Text, remainder:&Text? = none -> Int8?)"}, //
+ {"parse", "Int8$parse", "func(text:Text, base:Int?=none, remainder:&Text?=none -> Int8?)"}, //
{"get_bit", "Int8$get_bit", "func(x:Int8, bit_index:Int -> Bool)"}, //
{"hex", "Int8$hex", "func(i:Int8, digits=0, uppercase=yes, prefix=yes -> Text)"}, //
{"is_between", "Int8$is_between", "func(x:Int8,low:Int8,high:Int8 -> Bool)"}, //
@@ -245,7 +245,7 @@ env_t *global_env(bool source_mapping) {
C(SQRT1_2), {"INF", "(Num_t)(INFINITY)", "Num"}, //
{"TAU", "(Num_t)(2.*M_PI)", "Num"}, //
{"mix", "Num$mix", "func(amount,x,y:Num -> Num)"}, //
- {"parse", "Num$parse", "func(text:Text, remainder:&Text? = none -> Num?)"}, //
+ {"parse", "Num$parse", "func(text:Text, remainder:&Text?=none -> Num?)"}, //
{"abs", "fabs", "func(n:Num -> Num)"}, //
F_opt(acos), F_opt(acosh), F_opt(asin), F(asinh), F(atan), F_opt(atanh), F(cbrt), F(ceil), F_opt(cos),
F(cosh), F(erf), F(erfc), F(exp), F(exp2), F(expm1), F(floor), F(j0), F(j1), F_opt(log), F_opt(log10),
@@ -274,7 +274,7 @@ env_t *global_env(bool source_mapping) {
{"INF", "(Num32_t)(INFINITY)", "Num32"}, //
{"TAU", "(Num32_t)(2.f*M_PI)", "Num32"}, //
{"mix", "Num32$mix", "func(amount,x,y:Num32 -> Num32)"}, //
- {"parse", "Num32$parse", "func(text:Text, remainder:&Text? = none -> Num32?)"}, //
+ {"parse", "Num32$parse", "func(text:Text, remainder:&Text?=none -> Num32?)"}, //
{"abs", "fabsf", "func(n:Num32 -> Num32)"}, //
{"modulo", "Num32$mod", "func(x,y:Num32 -> Num32)"}, //
{"modulo1", "Num32$mod1", "func(x,y:Num32 -> Num32)"}, //
diff --git a/src/stdlib/bigint.c b/src/stdlib/bigint.c
index 46957c2d..2d145bd5 100644
--- a/src/stdlib/bigint.c
+++ b/src/stdlib/bigint.c
@@ -393,55 +393,98 @@ PUREFUNC Closure_t Int$onward(Int_t first, Int_t step) {
}
public
-Int_t Int$from_str(const char *str) {
- mpz_t i;
- int result;
- if (strncmp(str, "0x", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 16);
- } else if (strncmp(str, "0o", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 8);
- } else if (strncmp(str, "0b", 2) == 0) {
- result = mpz_init_set_str(i, str + 2, 2);
- } else {
- result = mpz_init_set_str(i, str, 10);
- }
- if (result != 0) return NONE_INT;
- return Int$from_mpz(i);
-}
+Int_t Int$from_str(const char *str) { return Int$parse(Text$from_str(str), NONE_INT, NULL); }
public
-OptionalInt_t Int$parse(Text_t text, Text_t *remainder) {
+OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
const char *str = Text$as_c_string(text);
- mpz_t i;
- int result;
- if (strncmp(str, "0x", 2) == 0) {
- str += 2;
- const char *end = str + strspn(str, "0123456789abcdefABCDEF");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 16);
+ bool negative = (*str == '-');
+ if (negative || *str == '+') str += 1;
+ const char *end = str;
+ int32_t base32;
+ if (base.small != 0) {
+ base32 = Int32$from_int(base, false);
+ switch (base32) {
+ case 16:
+ if (strncmp(str, "0x", 2) == 0) {
+ base16_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "0123456789abcdefABCDEF");
+ break;
+ case 10:
+ base10:
+ end = str + strspn(str, "0123456789");
+ break;
+ case 8:
+ if (strncmp(str, "0o", 2) == 0) {
+ base8_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "01234567");
+ break;
+ case 2:
+ if (strncmp(str, "0b", 2) == 0) {
+ base2_prefix:
+ str += 2;
+ }
+ end = str + strspn(str, "01");
+ break;
+ case 1: {
+ str += strspn(str, "0");
+ size_t n = strspn(str, "1");
+ end = str + n;
+ if (remainder) *remainder = Text$from_str(end);
+ else if (*end != '\0') return NONE_INT;
+ return Int$from_int64((int64_t)n);
+ }
+ default: {
+ if (base32 < 1 || base32 > 36) {
+ if (remainder) *remainder = text;
+ return NONE_INT;
+ }
+ for (; *end; end++) {
+ char c = *end;
+ int32_t digit;
+ if ('0' <= c && c <= '9') {
+ digit = (c - (int)'0');
+ } else if ('a' <= c && c <= 'z') {
+ digit = (c - (int)'a');
+ } else if ('A' <= c && c <= 'Z') {
+ digit = (c - (int)'A');
+ } else {
+ break;
+ }
+ if (digit >= base32) break;
+ }
+ }
+ }
+ } else if (strncmp(str, "0x", 2) == 0) {
+ base32 = 16;
+ goto base16_prefix;
} else if (strncmp(str, "0o", 2) == 0) {
- str += 2;
- const char *end = str + strspn(str, "01234567");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 8);
+ base32 = 8;
+ goto base8_prefix;
} else if (strncmp(str, "0b", 2) == 0) {
- str += 2;
- const char *end = str + strspn(str, "01");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 2);
+ base32 = 2;
+ goto base2_prefix;
} else {
- const char *end = str + strspn(str, "0123456789");
- if (remainder) *remainder = Text$from_str(end);
- else if (*end != '\0') return NONE_INT;
- result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), 10);
+ base32 = 10;
+ goto base10;
}
+
+ if (remainder) *remainder = Text$from_str(end);
+ else if (*end != '\0') return NONE_INT;
+
+ mpz_t i;
+ int result = mpz_init_set_str(i, String(string_slice(str, (size_t)(end - str))), base32);
if (result != 0) {
if (remainder) *remainder = text;
return NONE_INT;
}
+ if (negative) {
+ mpz_neg(i, i);
+ }
return Int$from_mpz(i);
}
diff --git a/src/stdlib/bigint.h b/src/stdlib/bigint.h
index a00bdf2f..9ce4c800 100644
--- a/src/stdlib/bigint.h
+++ b/src/stdlib/bigint.h
@@ -23,7 +23,7 @@ Text_t Int$octal(Int_t i, Int_t digits, bool prefix);
PUREFUNC Closure_t Int$to(Int_t first, Int_t last, OptionalInt_t step);
PUREFUNC Closure_t Int$onward(Int_t first, Int_t step);
OptionalInt_t Int$from_str(const char *str);
-OptionalInt_t Int$parse(Text_t text, Text_t *remainder);
+OptionalInt_t Int$parse(Text_t text, OptionalInt_t base, Text_t *remainder);
Int_t Int$abs(Int_t x);
Int_t Int$power(Int_t base, Int_t exponent);
Int_t Int$gcd(Int_t x, Int_t y);
diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c
index ab689ae4..4416d804 100644
--- a/src/stdlib/bytes.c
+++ b/src/stdlib/bytes.c
@@ -33,8 +33,8 @@ public
CONSTFUNC bool Byte$is_between(const Byte_t x, const Byte_t low, const Byte_t high) { return low <= x && x <= high; }
public
-OptionalByte_t Byte$parse(Text_t text, Text_t *remainder) {
- OptionalInt_t full_int = Int$parse(text, remainder);
+OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder) {
+ OptionalInt_t full_int = Int$parse(text, base, remainder);
if (full_int.small != 0L && Int$compare_value(full_int, I(0)) >= 0 && Int$compare_value(full_int, I(255)) <= 0) {
return (OptionalByte_t){.has_value = true, .value = Byte$from_int(full_int, true)};
} else {
diff --git a/src/stdlib/bytes.h b/src/stdlib/bytes.h
index 2f948177..6581f300 100644
--- a/src/stdlib/bytes.h
+++ b/src/stdlib/bytes.h
@@ -18,7 +18,7 @@ Byte_t Byte$from_int(Int_t i, bool truncate);
Byte_t Byte$from_int64(int64_t i, bool truncate);
Byte_t Byte$from_int32(int32_t i, bool truncate);
Byte_t Byte$from_int16(int16_t i, bool truncate);
-OptionalByte_t Byte$parse(Text_t text, Text_t *remainder);
+OptionalByte_t Byte$parse(Text_t text, OptionalInt_t base, Text_t *remainder);
Closure_t Byte$to(Byte_t first, Byte_t last, OptionalInt8_t step);
MACROLIKE Byte_t Byte$from_int8(int8_t i) { return (Byte_t)i; }
diff --git a/src/stdlib/cli.c b/src/stdlib/cli.c
index 04538796..18da5c5e 100644
--- a/src/stdlib/cli.c
+++ b/src/stdlib/cli.c
@@ -238,23 +238,23 @@ static List_t parse_arg_list(List_t args, const char *flag, void *dest, const Ty
if (parsed.small == 0) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int_t *)dest = parsed;
} else if (type == &Int64$info) {
- OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NULL);
+ OptionalInt64_t parsed = Int64$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int64_t *)dest = parsed.value;
} else if (type == &Int32$info) {
- OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NULL);
+ OptionalInt32_t parsed = Int32$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int32_t *)dest = parsed.value;
} else if (type == &Int16$info) {
- OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NULL);
+ OptionalInt16_t parsed = Int16$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int16_t *)dest = parsed.value;
} else if (type == &Int8$info) {
- OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NULL);
+ OptionalInt8_t parsed = Int8$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Int8_t *)dest = parsed.value;
} else if (type == &Byte$info) {
- OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NULL);
+ OptionalByte_t parsed = Byte$parse(Text$from_str(arg), NONE_INT, NULL);
if (!parsed.has_value) print_err("Could not parse argument for ", flag, ": ", arg);
*(Byte_t *)dest = parsed.value;
} else if (type == &Bool$info) {
diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h
index 0e665591..0910c7f1 100644
--- a/src/stdlib/intX.c.h
+++ b/src/stdlib/intX.c.h
@@ -188,8 +188,8 @@ Closure_t NAMESPACED(onward)(INT_T first, INT_T step) {
return (Closure_t){.fn = _next_int, .userdata = range};
}
public
-PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder) {
- OptionalInt_t full_int = Int$parse(text, remainder);
+PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder) {
+ OptionalInt_t full_int = Int$parse(text, base, remainder);
if (full_int.small == 0L) return (OPT_T){.has_value = false};
if (Int$compare_value(full_int, I(NAMESPACED(min))) < 0) {
return (OPT_T){.has_value = false};
diff --git a/src/stdlib/intX.h b/src/stdlib/intX.h
index 0f4632c2..1c8b4a05 100644
--- a/src/stdlib/intX.h
+++ b/src/stdlib/intX.h
@@ -46,7 +46,7 @@ List_t NAMESPACED(bits)(INTX_T x);
bool NAMESPACED(get_bit)(INTX_T x, Int_t bit_index);
Closure_t NAMESPACED(to)(INTX_T first, INTX_T last, OPT_T step);
Closure_t NAMESPACED(onward)(INTX_T first, INTX_T step);
-PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, Text_t *remainder);
+PUREFUNC OPT_T NAMESPACED(parse)(Text_t text, OptionalInt_t base, Text_t *remainder);
CONSTFUNC bool NAMESPACED(is_between)(const INTX_T x, const INTX_T low, const INTX_T high);
CONSTFUNC INTX_T NAMESPACED(clamped)(INTX_T x, INTX_T min, INTX_T max);
MACROLIKE CONSTFUNC INTX_T NAMESPACED(from_byte)(Byte_t b) { return (INTX_T)b; }
diff --git a/src/typecheck.c b/src/typecheck.c
index 37f4fcab..e432759b 100644
--- a/src/typecheck.c
+++ b/src/typecheck.c
@@ -14,6 +14,7 @@
#include "naming.h"
#include "parse/files.h"
#include "parse/types.h"
+#include "stdlib/optionals.h"
#include "stdlib/paths.h"
#include "stdlib/tables.h"
#include "stdlib/text.h"
@@ -1659,7 +1660,7 @@ PUREFUNC bool is_constant(env_t *env, ast_t *ast) {
case None: return true;
case Int: {
DeclareMatch(info, ast, Int);
- Int_t int_val = Int$parse(Text$from_str(info->str), NULL);
+ Int_t int_val = Int$parse(Text$from_str(info->str), NONE_INT, NULL);
if (int_val.small == 0) return false; // Failed to parse
return (Int$compare_value(int_val, I(BIGGEST_SMALL_INT)) <= 0);
}
diff --git a/test/integers.tm b/test/integers.tm
index 1b1f7569..67175f7a 100644
--- a/test/integers.tm
+++ b/test/integers.tm
@@ -105,3 +105,24 @@ func main()
assert Int64(6).get_bit(2) == yes
assert Int64(6).get_bit(3) == yes
assert Int64(6).get_bit(4) == no
+
+ assert Int.parse("123") == 123
+ assert Int.parse("0x10") == 16
+ assert Int.parse("0o10") == 8
+ assert Int.parse("0b10") == 2
+ assert Int.parse("abc") == none
+
+ assert Int.parse("-123") == -123
+ assert Int.parse("-0x10") == -16
+ assert Int.parse("-0o10") == -8
+ assert Int.parse("-0b10") == -2
+
+ for base in (2).to(36)
+ assert Int.parse("10", base=base) == base
+
+ assert Int.parse("111", base=1) == 3
+
+ assert Int.parse("z", base=36) == 35
+ assert Int.parse("Z", base=36) == 35
+ assert Int.parse("-z", base=36) == -35
+ assert Int.parse("-Z", base=36) == -35