Skip to content

Commit 7235672

Browse files
committed
ref: base64 architecture to submodules and apply stdlib standards
This commit addresses the architectural and stylistic feedback for the Base64 implementation: * Architecture: Consolidated interfaces into a single stdlib_base64 parent module and moved the high-performance logic into encode and decode submodules to prevent compilation cascades. * Standards: Replaced the logical error flag with type(state_type) from stdlib_error for consistent library-wide error handling. * Optimization: Marked the core _into subroutines as pure to guarantee no side effects and allow for aggressive compiler optimizations. * Dependency: Imported base64_alphabet directly from stdlib_ascii to reduce redundancy. * Documentation: Added explanatory comments for the branchless DT and IS_VAL lookup tables. * Formatting: Expanded semicolon-separated multi-statement lines into single lines to improve debuggability and prevent CI truncation errors. Signed-off-by: RatanKokal <ratanskokal@gmail.com>
1 parent 2e7fad9 commit 7235672

5 files changed

Lines changed: 320 additions & 174 deletions

File tree

src/core/stdlib_ascii.fypp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ module stdlib_ascii
6565
character(len=*), public, parameter :: letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" !! A .. Za .. z
6666
character(len=*), public, parameter :: uppercase = letters(1:26) !! A .. Z
6767
character(len=*), public, parameter :: lowercase = letters(27:) !! a .. z
68+
character(len=*), public, parameter :: base64_alphabet = &
69+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" !! RFC 4648 Base64 alphabet
6870
character(len=*), public, parameter :: whitespace = " "//TAB//VT//CR//LF//FF !! ASCII _whitespace
6971

7072

src/strings/stdlib_base64.fypp

Lines changed: 158 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
! SPDX-Identifier: MIT
2+
#:include "common.fypp"
23

34
!> Base64 encoding and decoding algorithms
45
!>
@@ -10,13 +11,167 @@
1011
!> may be subject to change in future releases of `stdlib`.
1112

1213
module stdlib_base64
13-
use stdlib_base64_encode, only: base64_encode, base64_encode_into
14-
use stdlib_base64_decode, only: base64_decode, base64_decode_into
15-
14+
use stdlib_kinds, only: sp, dp, xdp, qp, int8, int16, int32, int64, lk
15+
use, intrinsic :: iso_c_binding, only: c_size_t, c_bool
16+
use stdlib_ascii, only: base64_alphabet
17+
use stdlib_error, only: state_type
18+
1619
implicit none
1720
private
1821

1922
public :: base64_encode, base64_encode_into
2023
public :: base64_decode, base64_decode_into
2124

25+
! Branchless RFC 4648 decode map: byte -> 6-bit value, invalid -> -1.
26+
! The main loop OR-reduces values and checks once at the end.
27+
integer(int8), parameter :: DT(0:255) = int( [ &
28+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
29+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
30+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, &
31+
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, &
32+
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, &
33+
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, &
34+
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, &
35+
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, &
36+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
37+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
38+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
39+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
40+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
41+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
42+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, &
43+
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 &
44+
], int8)
45+
46+
! Branchless stream-compaction mask for despace step: keep(1) / drop(0).
47+
integer(int32), parameter :: IS_VAL(0:255) = int([ &
48+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &
49+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &
50+
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
51+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
52+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
53+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
54+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
55+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
56+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
57+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
58+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
59+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
60+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
61+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
62+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, &
63+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 &
64+
], int32)
65+
66+
!> Encodes intrinsic arrays into Base64 text.
67+
!>
68+
!> This is the ergonomic API for encoding. It allocates and returns the
69+
!> output string automatically.
70+
!>
71+
!> Use this form when convenience matters more than avoiding allocations.
72+
!>
73+
!> Example:
74+
!>```fortran
75+
!> character(len=:), allocatable :: encoded
76+
!> integer(int8) :: bytes(3) = [77_int8, 97_int8, 110_int8]
77+
!> encoded = base64_encode(bytes) ! "TWFu"
78+
!>```
79+
interface base64_encode
80+
#:for k1, t1, _, _ in REAL_KINDS_TYPES
81+
module procedure :: base64_encode_real_${k1}$
82+
#:endfor
83+
#:for k1, t1, _, _ in INT_KINDS_TYPES
84+
module procedure :: base64_encode_int_${k1}$
85+
#:endfor
86+
#:for k1, t1, _ in CMPLX_KINDS_TYPES
87+
module procedure :: base64_encode_cmplx_${k1}$
88+
#:endfor
89+
#:for k1, t1 in LOG_KINDS_TYPES
90+
module procedure :: base64_encode_logical_${k1}$
91+
#:endfor
92+
end interface base64_encode
93+
94+
interface
95+
#:for k1, t1, _, _ in REAL_KINDS_TYPES
96+
module function base64_encode_real_${k1}$(data) result(str)
97+
${t1}$, intent(in), target, contiguous :: data(..)
98+
character(len=:), allocatable :: str
99+
end function base64_encode_real_${k1}$
100+
#:endfor
101+
102+
#:for k1, t1, _, _ in INT_KINDS_TYPES
103+
module function base64_encode_int_${k1}$(data) result(str)
104+
${t1}$, intent(in), target, contiguous :: data(..)
105+
character(len=:), allocatable :: str
106+
end function base64_encode_int_${k1}$
107+
#:endfor
108+
109+
#:for k1, t1, _ in CMPLX_KINDS_TYPES
110+
module function base64_encode_cmplx_${k1}$(data) result(str)
111+
${t1}$, intent(in), target, contiguous :: data(..)
112+
character(len=:), allocatable :: str
113+
end function base64_encode_cmplx_${k1}$
114+
#:endfor
115+
116+
#:for k1, t1 in LOG_KINDS_TYPES
117+
module function base64_encode_logical_${k1}$(data) result(str)
118+
${t1}$, intent(in), target, contiguous :: data(..)
119+
character(len=:), allocatable :: str
120+
end function base64_encode_logical_${k1}$
121+
#:endfor
122+
123+
module function base64_encode_bytes(bytes) result(str)
124+
integer(int8), intent(in), target, contiguous :: bytes(:)
125+
character(len=:), allocatable, target :: str
126+
end function base64_encode_bytes
127+
128+
!> Encodes bytes into a caller-provided output buffer.
129+
!>
130+
!> This is the preallocated API for throughput-sensitive workflows.
131+
!> It does not allocate and reports status through `err_state`.
132+
!>
133+
!> On success, `err_state%ok()` is `.true.` and `encoded_len` is the
134+
!> number of meaningful characters written into `str`.
135+
pure module subroutine base64_encode_into(bytes, str, encoded_len, err_state)
136+
integer(int8), intent(in), target, contiguous :: bytes(:)
137+
character(len=*), intent(out), target :: str
138+
integer, intent(out) :: encoded_len
139+
type(state_type), intent(out) :: err_state
140+
end subroutine base64_encode_into
141+
142+
!> Decodes Base64 text into a caller-provided output buffer.
143+
!>
144+
!> This is the preallocated API for throughput-sensitive workflows.
145+
!> It does not allocate and reports status through `err_state`.
146+
!>
147+
!> The optional `skip_despace` input can be used when the input is
148+
!> already whitespace-free.
149+
pure module subroutine base64_decode_into(str, res, decoded_len, err_state, skip_despace)
150+
character(len=*), intent(in) :: str
151+
character(len=*), intent(out) :: res
152+
integer, intent(out) :: decoded_len
153+
type(state_type), intent(out) :: err_state
154+
logical, intent(in), optional :: skip_despace
155+
end subroutine base64_decode_into
156+
157+
!> Decodes Base64 text and returns an allocated byte-string.
158+
!>
159+
!> This is the ergonomic API for decoding. It allocates and returns
160+
!> the result automatically.
161+
!>
162+
!> On error, an empty result is returned. If `err_state` is present,
163+
!> details are stored there.
164+
!>
165+
!> Example:
166+
!>```fortran
167+
!> character(len=:), allocatable :: decoded
168+
!> decoded = base64_decode("TWFu") ! "Man"
169+
!>```
170+
module function base64_decode(str, err_state) result(res)
171+
character(len=*), intent(in) :: str
172+
type(state_type), intent(out), optional :: err_state
173+
character(len=:), allocatable :: res
174+
end function base64_decode
175+
end interface
176+
22177
end module stdlib_base64

0 commit comments

Comments
 (0)