1
1
mirror of https://github.com/wader/fq.git synced 2024-11-30 09:58:13 +03:00
fq/pkg/scalar/scalar.go
Mattias Wadman cb3dc80252 decode,tar: Add scalar description and Try* helpers
Rework time helpers to use new functions
Fix panic in tar decoder where sym value might be missing

Still not very happy about the API but it's getting better.
2022-10-04 17:18:51 +02:00

295 lines
6.9 KiB
Go

package scalar
// TODO: d.Pos check err
// TODO: fn for actual?
// TODO: dsl import .? own scalar package?
// TODO: better IOError op names
import (
"bytes"
"fmt"
"strconv"
"strings"
"time"
"github.com/wader/fq/internal/bitioex"
"github.com/wader/fq/pkg/bitio"
"golang.org/x/exp/constraints"
)
//go:generate sh -c "cat scalar_gen.go.tmpl | go run ../../dev/tmpl.go ../decode/types.json | gofmt > scalar_gen.go"
type DisplayFormat int
const (
NumberDecimal DisplayFormat = iota
NumberBinary
NumberOctal
NumberHex
)
func (df DisplayFormat) FormatBase() int {
switch df {
case NumberDecimal:
return 10
case NumberBinary:
return 2
case NumberOctal:
return 8
case NumberHex:
return 16
default:
return 0
}
}
type S struct {
Actual any // nil, int, int64, uint64, float64, string, bool, []byte, *bit.Int, bitio.BitReaderAtSeeker,
ActualDisplay DisplayFormat
Sym any
SymDisplay DisplayFormat
Description string
Unknown bool
}
func (s S) Value() any {
if s.Sym != nil {
return s.Sym
}
return s.Actual
}
type Mapper interface {
MapScalar(S) (S, error)
}
type Fn func(S) (S, error)
func (fn Fn) MapScalar(s S) (S, error) {
return fn(s)
}
var ActualBin = Fn(func(s S) (S, error) { s.ActualDisplay = NumberBinary; return s, nil })
var ActualOct = Fn(func(s S) (S, error) { s.ActualDisplay = NumberOctal; return s, nil })
var ActualDec = Fn(func(s S) (S, error) { s.ActualDisplay = NumberDecimal; return s, nil })
var ActualHex = Fn(func(s S) (S, error) { s.ActualDisplay = NumberHex; return s, nil })
var SymBin = Fn(func(s S) (S, error) { s.SymDisplay = NumberBinary; return s, nil })
var SymOct = Fn(func(s S) (S, error) { s.SymDisplay = NumberOctal; return s, nil })
var SymDec = Fn(func(s S) (S, error) { s.SymDisplay = NumberDecimal; return s, nil })
var SymHex = Fn(func(s S) (S, error) { s.SymDisplay = NumberHex; return s, nil })
func Actual(v any) Mapper {
return Fn(func(s S) (S, error) { s.Actual = v; return s, nil })
}
func Sym(v any) Mapper {
return Fn(func(s S) (S, error) { s.Sym = v; return s, nil })
}
func Description(v string) Mapper {
return Fn(func(s S) (S, error) { s.Description = v; return s, nil })
}
func ActualUAdd(n int) ActualUFn {
// TODO: use math.Add/Sub?
return ActualUFn(func(a uint64) uint64 { return uint64(int64(a) + int64(n)) })
}
func ActualSAdd(n int) ActualSFn {
return ActualSFn(func(a int64) int64 { return a + int64(n) })
}
func ActualTrim(cutset string) ActualStrFn {
return ActualStrFn(func(a string) string { return strings.Trim(a, cutset) })
}
var ActualTrimSpace = ActualStrFn(strings.TrimSpace)
func strMapToSym(fn func(s string) (any, error), try bool) Mapper {
return Fn(func(s S) (S, error) {
ts := strings.TrimSpace(s.ActualStr())
n, err := fn(ts)
if err != nil {
if try {
return s, nil
}
return s, err
}
s.Sym = n
return s, nil
})
}
func TrySymUParseUint(base int) Mapper {
return strMapToSym(func(s string) (any, error) { return strconv.ParseUint(s, base, 64) }, true)
}
func TrySymSParseInt(base int) Mapper {
return strMapToSym(func(s string) (any, error) { return strconv.ParseInt(s, base, 64) }, true)
}
func TrySymFParseFloat(base int) Mapper {
return strMapToSym(func(s string) (any, error) { return strconv.ParseFloat(s, base) }, true)
}
func SymUParseUint(base int) Mapper {
return strMapToSym(func(s string) (any, error) { return strconv.ParseUint(s, base, 64) }, false)
}
func SymSParseInt(base int) Mapper {
return strMapToSym(func(s string) (any, error) { return strconv.ParseInt(s, base, 64) }, false)
}
func SymFParseFloat(base int) Mapper {
return strMapToSym(func(s string) (any, error) { return strconv.ParseFloat(s, base) }, false)
}
type URangeEntry struct {
Range [2]uint64
S S
}
// URangeToScalar maps uint64 ranges to a scalar, first in range is chosen
type URangeToScalar []URangeEntry
func (rs URangeToScalar) MapScalar(s S) (S, error) {
n := s.ActualU()
for _, re := range rs {
if n >= re.Range[0] && n <= re.Range[1] {
ns := re.S
ns.Actual = s.Actual
s = ns
break
}
}
return s, nil
}
// SRangeToScalar maps ranges to a scalar, first in range is chosen
type SRangeEntry struct {
Range [2]int64
S S
}
// SRangeToScalar maps sint64 ranges to a scalar, first in range is chosen
type SRangeToScalar []SRangeEntry
func (rs SRangeToScalar) MapScalar(s S) (S, error) {
n := s.ActualS()
for _, re := range rs {
if n >= re.Range[0] && n <= re.Range[1] {
ns := re.S
ns.Actual = s.Actual
s = ns
break
}
}
return s, nil
}
func RawSym(s S, nBytes int, fn func(b []byte) string) (S, error) {
br := s.ActualBitBuf()
brLen, err := bitioex.Len(br)
if err != nil {
return S{}, err
}
if nBytes < 0 {
nBytes = int(brLen) / 8
if brLen%8 != 0 {
nBytes++
}
}
if brLen < int64(nBytes)*8 {
return s, nil
}
// TODO: shared somehow?
b := make([]byte, nBytes)
if _, err := br.ReadBitsAt(b, int64(nBytes)*8, 0); err != nil {
return s, err
}
s.Sym = fn(b[0:nBytes])
return s, nil
}
var RawUUID = Fn(func(s S) (S, error) {
return RawSym(s, -1, func(b []byte) string {
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
})
})
var RawHex = Fn(func(s S) (S, error) {
return RawSym(s, -1, func(b []byte) string { return fmt.Sprintf("%x", b) })
})
type BytesToScalar []struct {
Bytes []byte
Scalar S
}
func (m BytesToScalar) MapScalar(s S) (S, error) {
rc, err := bitio.CloneReader(s.ActualBitBuf())
if err != nil {
return s, err
}
bb := &bytes.Buffer{}
if _, err := bitioex.CopyBits(bb, rc); err != nil {
return s, err
}
for _, bs := range m {
if bytes.Equal(bb.Bytes(), bs.Bytes) {
ns := bs.Scalar
ns.Actual = s.Actual
s = ns
break
}
}
return s, nil
}
// TODO: nicer api, use generic, many generic Try function somehow?
func mapFn[T any](tryVFn func(S) (T, bool), vFn func(S) T, fn func(s S, v T) S) Mapper {
return Fn(func(s S) (S, error) {
var v T
var ok bool
if tryVFn != nil {
v, ok = tryVFn(s)
if !ok {
return s, nil
}
} else {
v = vFn(s)
}
return fn(s, v), nil
})
}
func TryMapFn[T any](epoch time.Time, format string, vFn func(S) (T, bool), fn func(s S, v T) S) Mapper {
return mapFn(vFn, nil, fn)
}
func MapFn[T any](epoch time.Time, format string, vFn func(S) T, fn func(s S, v T) S) Mapper {
return mapFn(nil, vFn, fn)
}
var unixTimeEpochDate = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)
func DescriptionFn[T any](vFn func(S) (T, bool), fn func(v T) string) Mapper {
return mapFn(vFn, nil, func(s S, v T) S {
s.Description = fn(v)
return s
})
}
// TODO: scalar types constraints
func DescriptionTimeFn[T constraints.Integer | constraints.Float](vFn func(S) (T, bool), epoch time.Time, format string) Mapper {
return DescriptionFn(vFn, func(v T) string {
return epoch.Add(time.Duration(v) * time.Second).Format(format)
})
}
func DescriptionUnixTimeFn[T constraints.Integer | constraints.Float](vFn func(S) (T, bool), format string) Mapper {
return DescriptionTimeFn(vFn, unixTimeEpochDate, format)
}