ADDED:
* mapsx.(ListSortedKeys|ListSortedValues|SortedKeys|SortedValues)
This commit is contained in:
brent saner committed 2026-10-10 01:32:35 -04:00
1 parent 67a5c8a3fb
commit 7ec35990a9
16 files changed
+960 -15

No files matched your search

+408
View File
@@ -0,0 +1,408 @@
package binarystr
import (
`bytes`
`errors`
`io`
`strconv`
`strings`
`unicode`
)
/*
BoolToBytes provides the inverse of [BytesToBool].
Each boolean in bools indicates a 0 (false) or 1 (true).
e.g.:
[]bool{
false, true, true, true,
false, true, false, false,
false, true, true, false,
false, true, false, true,
false, true, true, true,
false, false, true, true,
false, true, true, true,
false, true, false, false,
}
to:
[]byte{
0x74, 0x65, 0x73, 0x74,
}
If bools is nil, then b, rem, and err will also be nil and this function will no-op.
If len(bools) % 8 != 0, then err will be [ErrBadNumOct].
The remaining booleans can be found in rem.
*/
func BoolToBytes(bools []bool) (b []byte, rem []bool, err error) {
var idx int
var loc []bool
var sb *strings.Builder = new(strings.Builder)
if bools == nil {
return
}
if len(bools) == 0 {
b = make([]byte, 0)
return
}
loc = make([]bool, len(bools))
copy(loc, bools)
for idx = 0; idx < len(loc); idx += 8 {
if len(loc) < 8 {
break
}
writeBoolChunk(loc[idx:idx+8], sb)
loc = loc[idx+8:]
}
if b, err = StrToBytes(sb.String()); err != nil {
return
}
if len(loc) > 1 {
rem = append(rem, loc...)
err = ErrBadNumOct
}
return
}
/*
BytesToStr converts bytes to a bitstring.
e.g.:
[]byte{
0x74, 0x65, 0x73, 0x74,
}
to:
"01110100011001010111001101110100"
For the inverse, see [StrToBytes].
To format the bitstring, see [FmtBinStr].
*/
func BytesToStr(b []byte) (binStr string) {
var cb byte
var sb *strings.Builder
if b == nil {
return
}
sb = new(strings.Builder)
for _, cb = range b {
// This will never actually return an error.
_, _ = sb.WriteString(
strconv.FormatUint(
uint64(uint8(cb)),
2,
),
)
}
binStr = sb.String()
return
}
/*
BytesToBool returns a slice of "binary booleans".
For each bit of data in b, a 0 is false and a 1 is true (as one would expect).
For the inverse of this, see [BoolToBytes].
*/
func BytesToBool(b []byte) (bools []bool) {
var err error
var r rune
var rSz int
var idx int
var s *strings.Reader
if len(b) == 0 {
return
}
// s will always be a valid bitstring.
s = strings.NewReader(BytesToStr(b))
bools = make([]bool, s.Len())
for idx = 0; idx < s.Len(); idx++ {
r, rSz, err = s.ReadRune()
if rSz == 0 {
break
}
if err != nil {
// It'll only be an io.EOF if anything at all.
err = nil
break
}
switch r {
case '0':
bools[idx] = false
case '1':
bools[idx] = true
}
}
return
}
/*
FmtBinStr formats bitstring s to more comfortable representations.
bitSep is a string to separate each bit boundary.
nybSep is a string to separate each "nibble"/"nybble" (4-bit "word") boundary.
bytSep is a string to separate each byte boundary.
Note that if you only explicitly use whitespace separators, then the output
can still be parsed back to native []byte{} data via [StrToBytes].
Note that NO checking is done against the validity of s being a bitstring;
the only content-checking done in s is if a given rune is a whitespace character or not
(whitespace present in s is skipped over, which lets you run FmtBinStr to reformat a bitstring).
*/
func FmtBinStr(s, bitSep, nybSep, bytSep string) (out string) {
var idx int
var lastIdx int
var sb *strings.Builder
if strings.TrimSpace(s) == "" {
return
}
sb = new(strings.Builder)
lastIdx = len(s) - 1
for idx = 0; idx < len(s); idx++ {
_ = sb.WriteByte(s[idx])
if idx != lastIdx {
if (idx+1)%8 == 0 {
if bytSep != "" {
_, _ = sb.WriteString(bytSep)
}
continue
} else if (idx+1)%4 == 0 {
if nybSep != "" {
_, _ = sb.WriteString(nybSep)
}
continue
}
}
if bitSep != "" {
_, _ = sb.WriteString(bitSep)
}
}
out = sb.String()
return
}
/*
NewDecoder returns a [Decoder] from [io.Reader] r.
Data from r is expected to be BITSTRING data (strings);
doing read operations from a [Decoder] returns PLAIN data.
*/
func NewDecoder(r io.Reader) (e *Decoder) {
e = &Decoder{
r: r,
}
return
}
/*
NewEncoder returns an [Encoder] from [io.Reader] r.
Data from r is expected to be PLAIN data;
doing read operations from an [Encoder] returns BITSTRING data (strings).
*/
func NewEncoder(r io.Reader) (e *Encoder) {
e = &Encoder{
r: r,
}
return
}
/*
StrToBytes converts a bitstring to bytes.
Whitespace is ignored.
e.g.:
"01110100011001010111001101110100"
to:
[]byte("test")
or:
[]byte{0x74, 0x65, 0x73, 0x74}
For the inverse, see [BytesToStr].
If the number of characters in binStr is not modulo 8 (`len() % 8 != 0`),
then err will be of type [BadNumOctErr].
If there are any characters encountered in binStr that are not "0" or "1"
(other than whitespace), then err will be of type [BadBinCharErr].
*/
func StrToBytes(binStr string) (b []byte, err error) {
var r rune
var cb byte
var rSz int
var pos uint64
var rErr error
var posR uint64
var chunk []rune
var buf *strings.Reader
var bytBuf *bytes.Buffer = new(bytes.Buffer)
defer func() {
b = bytBuf.Bytes()
}()
if strings.TrimSpace(binStr) == "" {
return
}
buf = strings.NewReader(binStr)
chunk = make([]rune, 0, 8)
for {
r, rSz, rErr = buf.ReadRune()
if rSz == 0 {
break
}
pos += uint64(rSz)
if rSz != 0 {
posR++
}
if len(chunk) == 8 {
if cb, err = chunkToByte(chunk); err != nil {
return
}
if err = bytBuf.WriteByte(cb); err != nil {
return
}
chunk = make([]rune, 0, 8)
}
if unicode.IsSpace(r) {
continue
}
if rSz != 0 {
switch r {
case '0', '1':
chunk = append(chunk, r)
default:
err = &BadBinCharErr{
posByte: pos,
posRune: posR,
r: r,
}
return
}
}
if rErr != nil {
if errors.Is(rErr, io.EOF) {
break
} else {
err = rErr
return
}
}
if rSz == 0 {
break
}
}
if len(chunk) != 0 {
if len(chunk) == 8 {
if cb, err = chunkToByte(chunk); err != nil {
return
}
if err = bytBuf.WriteByte(cb); err != nil {
return
}
} else {
err = &BadNumOctErr{
posByte: pos,
posRune: posR,
rem: string(chunk),
}
return
}
}
return
}
func chunkToByte(chunk []rune) (b byte, err error) {
var u8 uint64
if len(chunk) == 0 {
err = ErrNoChunk
return
}
if u8, err = strconv.ParseUint(string(chunk), 2, 8); err != nil {
return
}
b = byte(uint8(u8))
return
}
func writeBoolChunk(chunk []bool, sb io.StringWriter) {
var b bool
if len(chunk) == 0 {
return
}
for _, b = range chunk {
switch b {
case false:
sb.WriteString("0")
case true:
sb.WriteString("1")
}
}
}