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

+1
View File
@@ -0,0 +1 @@
I need to test this still; I can't recall if it's complete or not
+10
View File
@@ -0,0 +1,10 @@
package binarystr
const (
/*
bufSz is the size of the buffer/chunk for encoders/decords to use when unspecified by the method caller.
The below is 64 KiB, which should be relatively reasonable.
*/
bufSz int = 65536
)
+44
View File
@@ -0,0 +1,44 @@
/*
Package binarystr parses and renders string (and other) representations of binary data.
To distinguish this package from [encoding/binary], this package refers to this string encoding format
as "bit encoding" or "bitstrings".
For example, the original data:
[]byte("test")
// Or:
// []byte{0x74, 0x65, 0x73, 0x74}
can be encoded to a bitstring via e.g. [BytesToStr]:
01110100011001010111001101110100
And even formatted via [FmtBinStr]:
// For example, FmtBinStr("01110100011001010111001101110100", "", "", " "):
01110100 01100101 01110011 01110100
// Or FmtBinStr("01110100011001010111001101110100", "", " ", "\n"):
0111 0100
0110 0101
0111 0011
0111 0100
Or can be converted to a series of "binary booleans" via e.g. [BytesToBool]:
[]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,
}
*/
package binarystr
+28
View File
@@ -0,0 +1,28 @@
package binarystr
import (
`errors`
)
var (
ErrBadNumOct error = errors.New("bad number of bits for even octet split")
ErrNoChunk error = errors.New("empty/nil chunk")
)
type (
BadBinCharErr struct {
posByte uint64
posRune uint64
r rune
}
BadNumOctErr struct {
posByte uint64
posRune uint64
rem string
}
BadReadErr struct {
numBytes uint64
}
)
+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")
}
}
}
@@ -0,0 +1,26 @@
package binarystr
import "fmt"
// BytePos returns the byte position index immediately leading up to the error.
func (b *BadBinCharErr) BytePos() (pos uint64) { return b.posByte }
// Rune returns the offending rune that caused the error.
func (b *BadBinCharErr) Rune() (r rune) { return b.r }
// RunePos returns the rune (character) position index immediately leading up to the error.
func (b *BadBinCharErr) RunePos() (pos uint64) { return b.posRune }
// Error returns the error message for a BadBinCharError. It conforms to an [error] interface.
func (b *BadBinCharErr) Error() (err string) {
err = fmt.Sprintf(
"rune/character after position index #%d (%#v) (byte #%d) is not \"0\" or \"1\"",
b.posRune, b.r, b.posByte,
)
return
}
// String is an alias for the [BadBinCharErr.Error] method to conform explicitly to a [fmt.Stringer] interface.
func (b *BadBinCharErr) String() (s string) { return b.Error() }
+32
View File
@@ -0,0 +1,32 @@
package binarystr
import "fmt"
// BytePos returns the byte position index immediately leading up to the error.
func (b *BadNumOctErr) BytePos() (pos uint64) { return b.posByte }
// Error returns the error message for a BadNumOctError. It conforms to an [error] interface.
func (b *BadNumOctErr) Error() (err string) {
err = fmt.Sprintf(
"only able to read %d complete octets/bytes (%#d runes/characters); "+
"%d remaining characters are less than 8 bits",
b.posByte, b.posRune,
len(b.rem),
)
return
}
/*
Rem returns the remaining unparsed string of characters; this should ALWAYS be a length of 0 <= len(rem) < 8.
(It will only be an empty string if [Decoder.Read] encountered the end of its source reader before the buffer was filled,
but the available data cleanly broke into a last octet.)
*/
func (b *BadNumOctErr) Rem() (rem string) { return b.rem }
// RunePos returns the rune (character) position index immediately leading up to the error.
func (b *BadNumOctErr) RunePos() (pos uint64) { return b.posRune }
// String is an alias for the [BadNumOctErr.Error] method to conform explicitly to a [fmt.Stringer] interface.
func (b *BadNumOctErr) String() (s string) { return b.Error() }
+16
View File
@@ -0,0 +1,16 @@
package binarystr
import (
`fmt`
)
// Error conforms a [BadReadErr] to an [error].
func (b *BadReadErr) Error() (err string) {
err = fmt.Sprintf("only read %d bytes from internal read source before encountering a read error", b.numBytes)
return
}
// NumBytesRead returns the number of bytes read (BEFORE mutation/encoding/decoding) before the error was hit.
func (b *BadReadErr) NumBytesRead() (u64 uint64) { return b.numBytes }
+116
View File
@@ -0,0 +1,116 @@
package binarystr
import (
`errors`
`io`
`strings`
)
/*
Read conforms to an [io.Reader].
It returns plain data decoded from bitstring encoding in e's read source.
IMPORTANT: Read will return a [BadNumOctErr] if the amount of data in e's read source is not at least len(b) * 8!
ONE BYTE in b equals EIGHT BYTES from e's read source!
Likewise, n will ALWAYS be len(b), but the number of bytes actually read from e's internal reader
will ALWAYS be n*8!
In this case, b will still be populated with the available decoded data, and err ([BadNumOctErr])'s
[BadNumOctErr.Rem] method will contain the remaining bitstring (if there is any left over) after decoding
the octets that were returned (if any).
IMPORTANT: Read will return a [BadBinCharErr] if it encounters any character in e's read source that is not '0' or '1'!
THIS INCLUDES WHITESPACE due to the buffer size calculation (unlike [StrToBytes]); it is NOT skipped!
*/
func (e *Decoder) Read(b []byte) (n int, err error) {
var inN int
var s string
var endIdx int
var rem string
var buf []byte
defer func() {
if b != nil && buf != nil {
s = BytesToStr(buf)
copy(b, []byte(s))
}
}()
if len(b) == 0 {
return
}
buf = make([]byte, len(b)*8)
if inN, err = e.r.Read(buf); err != nil {
// This is caught/handled below.
if errors.Is(err, io.EOF) {
err = nil
} else {
return
}
}
if inN < len(buf) {
}
if inN != len(buf) {
err = &BadNumOctErr{
posByte: uint64(inN),
posRune: uint64(inN),
rem: rem, // We hit the end too "early" so there is no "remaining".
}
return
}
return
}
/*
ReadString returns a bitstring-encoded [string] from the entirety of the data
in the source reader of e.
IMPORTANT: If e's source reader is a non-terminating stream (e.g. a [math/rand.Rand]),
this function will NEVER RETURN (and will likely consume all memory).
YOU HAVE BEEN WARNED.
n is the number of bytes read from e's source reader.
*/
func (e *Decoder) ReadString() (s string, n uint64, err error) {
var inN int
var buf []byte
var rErr error
var sb *strings.Builder = new(strings.Builder)
defer func() {
if sb != nil {
s = sb.String()
}
}()
for {
buf = make([]byte, bufSz)
inN, rErr = e.r.Read(buf)
n += uint64(inN)
if inN != 0 {
if inN < bufSz {
buf = buf[:inN]
}
sb.WriteString(BytesToStr(buf))
} else {
if errors.Is(rErr, io.EOF) {
rErr = nil
}
break
}
if rErr != nil {
err = rErr
return
}
}
return
}
+105
View File
@@ -0,0 +1,105 @@
package binarystr
import (
`errors`
`io`
`strings`
)
/*
Read conforms to an [io.Reader].
It returns bitstring encoding of the data in e's read source.
(To conform to [io.Reader], this is returned as bytes - e.g. []byte(string()).)
IMPORTANT: Read will return [ErrBadNumOct] if len(b) % 8 != 0! EIGHT BYTES in b equal ONE BYTE
from e's read source! Keep this in mind and size b accordingly for your desired decoded data size!
Likewise, n will ALWAYS be len(b), but the number of bytes actually read from e's internal reader
will ALWAYS be n/8! (If for some bizarre case it isn't, err will be [BadReadErr].)
*/
func (e *Encoder) Read(b []byte) (n int, err error) {
var inN int
var s string
var buf []byte
defer func() {
if b != nil && buf != nil {
s = BytesToStr(buf)
copy(b, []byte(s))
}
}()
if len(b) == 0 {
return
}
if len(b)%8 != 0 {
err = ErrBadNumOct
return
}
buf = make([]byte, len(b)/8)
if inN, err = e.r.Read(buf); err != nil {
return
}
if inN != len(buf) {
if len(buf) > inN {
buf = buf[:inN]
}
err = &BadReadErr{
numBytes: uint64(inN),
}
return
}
return
}
/*
ReadString returns a bitstring-encoded [string] from the entirety of the data
in the source reader of e.
IMPORTANT: If e's source reader is a non-terminating stream (e.g. a [math/rand.Rand]),
this function will NEVER RETURN (and will likely consume all memory).
YOU HAVE BEEN WARNED.
n is the number of bytes read from e's source reader.
*/
func (e *Encoder) ReadString() (s string, n uint64, err error) {
var inN int
var buf []byte
var rErr error
var sb *strings.Builder = new(strings.Builder)
defer func() {
if sb != nil {
s = sb.String()
}
}()
for {
buf = make([]byte, bufSz)
inN, rErr = e.r.Read(buf)
n += uint64(inN)
if inN != 0 {
if inN < bufSz {
buf = buf[:inN]
}
sb.WriteString(BytesToStr(buf))
} else {
if errors.Is(rErr, io.EOF) {
rErr = nil
}
break
}
if rErr != nil {
err = rErr
return
}
}
return
}
+17
View File
@@ -0,0 +1,17 @@
package binarystr
import (
`io`
)
type (
Encoder struct {
r io.Reader
cur uint64
}
Decoder struct {
r io.Reader
cur uint64
}
)