Skip to content

Commit 8aa3f53

Browse files
committed
FEAT: enbase/debase with base 36 (alphanumeric)
1 parent 0df0d44 commit 8aa3f53

File tree

5 files changed

+199
-3
lines changed

5 files changed

+199
-3
lines changed

make/rebol3.nest

+4
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,9 @@ include-base85-encoding: [
613613
; adds support for enbase/debase with base 85 (ASCII85)
614614
config: INCLUDE_BASE85
615615
]
616+
include-base36-encoding: [
617+
config: INCLUDE_BASE36
618+
]
616619

617620
;- native additional compressions:
618621
include-lzma-compression: [
@@ -745,6 +748,7 @@ include-rebol-bulk: [
745748
:include-image-natives
746749
:include-lzma-compression
747750
:include-crush-compression
751+
:include-base36-encoding
748752
:include-base85-encoding
749753
:include-view
750754
:include-midi

src/boot/natives.reb

+2-2
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ construct: native [
393393
debase: native [
394394
{Decodes binary-coded string to binary value.}
395395
value [binary! any-string!] {The string to decode}
396-
base [integer!] {Binary base to use: 85, 64, 16, or 2}
396+
base [integer!] {Binary base to use: 85, 64, 36, 16, or 2}
397397
/url {Base 64 Decoding with URL and Filename Safe Alphabet}
398398
/part {Limit the length of the input}
399399
limit [integer!]
@@ -402,7 +402,7 @@ debase: native [
402402
enbase: native [
403403
{Encodes a string into a binary-coded string.}
404404
value [binary! string!] {If string, will be UTF8 encoded}
405-
base [integer!] {Binary base to use: 85, 64, 16, or 2}
405+
base [integer!] {Binary base to use: 85, 64, 36, 16, or 2}
406406
/url {Base 64 Encoding with URL and Filename Safe Alphabet}
407407
/part {Limit the length of the input}
408408
limit [integer!]

src/core/f-enbase.c

+138
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,37 @@
441441
#endif
442442

443443

444+
#ifdef INCLUDE_BASE36
445+
#define BASE36_LENGTH 13
446+
/***********************************************************************
447+
**
448+
*/ static const REBYTE Enbase36[36] =
449+
/*
450+
** Base-36 binary encoder table.
451+
**
452+
***********************************************************************/
453+
{
454+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
455+
};
456+
457+
static REBU64 base36_powers[BASE36_LENGTH] = {
458+
1ULL,
459+
36ULL,
460+
1296ULL,
461+
46656ULL,
462+
1679616ULL,
463+
60466176ULL,
464+
2176782336ULL,
465+
78364164096ULL,
466+
2821109907456ULL,
467+
101559956668416ULL,
468+
3656158440062976ULL,
469+
131621703842267136ULL,
470+
4738381338321616896ULL
471+
};
472+
#endif
473+
474+
444475
/***********************************************************************
445476
**
446477
*/ static REBSER *Decode_Base2(const REBYTE **src, REBCNT len, REBYTE delim)
@@ -727,6 +758,61 @@
727758
}
728759
#endif
729760

761+
#ifdef INCLUDE_BASE36
762+
/***********************************************************************
763+
**
764+
*/ static REBSER* Decode_Base36(const REBYTE** src, REBCNT len, REBYTE delim)
765+
/*
766+
***********************************************************************/
767+
{
768+
REBYTE* bp;
769+
const REBYTE* cp;
770+
REBSER* ser;
771+
REBCNT ser_size;
772+
REBCNT chunk;
773+
REBINT pad = 0;
774+
REBU64 c = 0;
775+
REBINT i, d = 0;
776+
777+
cp = *src;
778+
779+
if (len > BASE36_LENGTH) goto err;
780+
else if (len == 0) {
781+
ser = Make_Binary(1);
782+
ser->tail = 0;
783+
return ser;
784+
}
785+
786+
for (i = 0; i < len; i++) {
787+
if (cp[i] >= '0' && cp[i] <= '9')
788+
d = cp[i] - '0';
789+
else if (cp[i] >= 'A' && cp[i] <= 'Z')
790+
d = 10 + cp[i] - 'A';
791+
else if (cp[i] >= 'a' && cp[i] <= 'z')
792+
d = 10 + cp[i] - 'a';
793+
else goto err;
794+
795+
c += d * base36_powers[len - i - 1];
796+
if (c < 0) goto err;
797+
}
798+
// Allocate buffer large enough to hold result:
799+
ser = Make_Binary(8);
800+
ser_size = SERIES_AVAIL(ser);
801+
bp = STR_HEAD(ser);
802+
803+
for (i = 7; i >= 0; i--) {
804+
bp[i] = (REBYTE)(c & 0xFF);
805+
c >>= 8;
806+
}
807+
ser->tail = 8;
808+
return ser;
809+
810+
err:
811+
*src = cp;
812+
return 0;
813+
}
814+
#endif
815+
730816

731817
/***********************************************************************
732818
**
@@ -753,6 +839,13 @@
753839
ser = Decode_Base85 (&src, len, delim);
754840
#else
755841
Trap0(RE_FEATURE_NA);
842+
#endif
843+
break;
844+
case 36:
845+
#ifdef INCLUDE_BASE36
846+
ser = Decode_Base36(&src, len, delim);
847+
#else
848+
Trap0(RE_FEATURE_NA);
756849
#endif
757850
break;
758851
}
@@ -954,4 +1047,49 @@
9541047

9551048
return series;
9561049
}
1050+
#endif
1051+
1052+
1053+
#ifdef INCLUDE_BASE36
1054+
/***********************************************************************
1055+
**
1056+
*/ REBSER* Encode_Base36(REBVAL* value, REBSER* series, REBCNT len, REBFLG brk)
1057+
/*
1058+
** Base36 encode a given series. Must be BYTES, not UNICODE.
1059+
**
1060+
***********************************************************************/
1061+
{
1062+
REBYTE* bp;
1063+
REBYTE* src;
1064+
REBOOL discard = TRUE;
1065+
REBU64 d, m = 0;
1066+
REBCNT n, p = 0;
1067+
REBINT i;
1068+
1069+
if (len > VAL_LEN(value)) len = VAL_LEN(value);
1070+
if (len > sizeof(REBI64)) {
1071+
Trap1(RE_OUT_OF_RANGE, value);
1072+
}
1073+
else if (len == 0) {
1074+
series = Prep_String(series, &bp, 1);
1075+
SERIES_TAIL(series) = 0;
1076+
return series;
1077+
}
1078+
src = VAL_BIN_DATA(value);
1079+
1080+
for (bp = src, n = len; n; n--, bp++)
1081+
m = (m << 8) | *bp;
1082+
1083+
series = Prep_String(series, &bp, BASE36_LENGTH + 1);
1084+
1085+
for (i = BASE36_LENGTH - 1; i >= 0; i--) {
1086+
d = m / base36_powers[i];
1087+
m = m - base36_powers[i] * d;
1088+
discard = discard && (d == 0 && i > 0);
1089+
if (!discard)
1090+
bp[p++] = Enbase36[d];
1091+
}
1092+
SERIES_TAIL(series) = p;
1093+
return series;
1094+
}
9571095
#endif

src/core/n-strings.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,13 @@ static struct digest {
544544
#else
545545
Trap0(RE_FEATURE_NA);
546546
#endif
547-
547+
break;
548+
case 36:
549+
#ifdef INCLUDE_BASE36
550+
ser = Encode_Base36(arg, 0, limit, FALSE);
551+
#else
552+
Trap0(RE_FEATURE_NA);
553+
#endif
548554
break;
549555
default:
550556
Trap_Arg(D_ARG(2));

src/tests/units/enbase-test.r3

+48
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,52 @@ if any [
239239
===end-group===
240240
]
241241

242+
if any [
243+
not error? err: try [enbase #{} 36]
244+
err/id <> 'feature-na
245+
][
246+
base36-int-tests: [
247+
0 "0"
248+
1 "1"
249+
36 "10"
250+
64 "1S"
251+
1024 "SG"
252+
19930503 "BV6H3"
253+
1843067821 "UHBC8D"
254+
3951668550778163018 "U0TPLAQIV70Q"
255+
]
256+
257+
===start-group=== "de/enbase-36"
258+
--test-- "enbase debase 36"
259+
--assert "" = enbase debase "" 36 36
260+
--assert "0" = enbase debase "0" 36 36
261+
--assert "1" = enbase debase "1" 36 36
262+
--assert "0" = enbase debase "000" 36 36
263+
--assert "1" = enbase debase "001" 36 36
264+
265+
--test-- "enbase int 36"
266+
foreach [inp out] base36-int-tests [
267+
--assert out = enbase to binary! inp 36
268+
]
269+
270+
--test-- "debase int 36"
271+
foreach [out inp] base36-int-tests [
272+
--assert out = to integer! debase inp 36
273+
]
274+
275+
--test-- "enbase 36"
276+
--assert "0" = enbase #{00} 36
277+
--assert "0" = enbase #{00000000} 36
278+
--assert "0" = enbase #{0000000000000000} 36
279+
--assert "3W5E11264SGSF" = enbase #{FFFFFFFFFFFFFFFF} 36
280+
281+
--test-- "debase 36 errors"
282+
--assert all [error? e: try [debase "00000000000000" 36] e/id = 'invalid-data]
283+
--assert all [error? e: try [debase "0.0" 36] e/id = 'invalid-data]
284+
--test-- "enbase 36 errors"
285+
--assert all [error? e: try [enbase #{000000000000000000} 36] e/id = 'out-of-range]
286+
287+
===end-group===
288+
]
289+
242290
~~~end-file~~~

0 commit comments

Comments
 (0)