v1.17.6
ADDED: * mapsx.(ListSortedKeys|ListSortedValues|SortedKeys|SortedValues)
This commit is contained in:
16 files changed
+960
-15
No files matched your search
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user