1
1
mirror of https://github.com/wader/fq.git synced 2024-12-18 02:41:44 +03:00
fq/pkg/interp/dump.go
2021-10-21 00:48:30 +02:00

365 lines
9.7 KiB
Go

package interp
import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"github.com/wader/fq/internal/asciiwriter"
"github.com/wader/fq/internal/columnwriter"
"github.com/wader/fq/internal/hexpairwriter"
"github.com/wader/fq/internal/num"
"github.com/wader/fq/pkg/bitio"
"github.com/wader/fq/pkg/decode"
)
// 0 12 34 56
// addr|hexdump|ascii|field
const (
colAddr = 0
colHex = 2
colASCII = 4
colField = 6
)
func isCompound(v *decode.Value) bool {
switch v.V.(type) {
case decode.Struct, decode.Array:
return true
default:
return false
}
}
func dumpEx(v *decode.Value, buf []byte, cw *columnwriter.Writer, depth int, rootV *decode.Value, rootDepth int, addrWidth int, opts Options) error {
deco := opts.Decorator
// no error check as we write into buffering column
// we check for err later for Flush()
cprint := func(c int, a ...interface{}) {
fmt.Fprint(cw.Columns[c], a...)
}
cfmt := func(c int, format string, a ...interface{}) {
fmt.Fprintf(cw.Columns[c], format, a...)
}
columns := func() {
cprint(1, deco.Column, "\n")
cprint(3, deco.Column, "\n")
cprint(5, deco.Column, "\n")
}
var hexHeader string
var asciiHeader string
if depth == 0 {
for i := 0; i < opts.LineBytes; i++ {
s := num.PadFormatInt(int64(i), opts.AddrBase, false, 2)
hexHeader += s
if i < opts.LineBytes-1 {
hexHeader += " "
}
asciiHeader += s[len(s)-1:]
}
}
isInArray := false
inArrayLen := 0
if v.Parent != nil {
if da, ok := v.Parent.V.(decode.Array); ok {
isInArray = true
inArrayLen = len(da)
}
}
nameV := v
name := nameV.Name
if isInArray {
nameV = v.Parent
name = ""
}
if depth == 0 {
name = valuePathDecorated(nameV, deco)
} else {
name = deco.ObjectKey.Wrap(name)
}
rootIndent := strings.Repeat(" ", rootDepth)
indent := strings.Repeat(" ", depth)
if depth == 0 {
if !isCompound(v) {
columns()
}
cfmt(colHex, "%s", deco.DumpHeader.F(hexHeader))
cfmt(colASCII, "%s", deco.DumpHeader.F(asciiHeader))
if !isCompound(v) {
cw.Flush()
}
}
if opts.ArrayTruncate != 0 && depth != 0 && isInArray && v.Index >= opts.ArrayTruncate {
columns()
cfmt(colField, "%s%s%s:%s%s: ...",
indent,
deco.Index.F("["),
deco.Number.F(strconv.Itoa(v.Index)),
deco.Number.F(strconv.Itoa(inArrayLen-1)),
deco.Index.F("]"),
)
cw.Flush()
return decode.ErrWalkBreak
}
cfmt(colField, "%s%s", indent, name)
if isInArray {
cfmt(colField, "%s%s%s", deco.Index.F("["), deco.Number.F(strconv.Itoa(v.Index)), deco.Index.F("]"))
}
cprint(colField, ":")
if opts.Verbose && isInArray {
cfmt(colField, " %s", v.Name)
}
// TODO: cleanup map[string]interface{} []interface{} or json format
// dump should use some internal interface instead?
switch v.V.(type) {
case decode.Struct, map[string]interface{}:
cfmt(colField, " %s", deco.Object.F("{}"))
if v.Description != "" {
cfmt(colField, " %s", deco.Value.F(v.Description))
}
if v.Format != nil {
cfmt(colField, " (%s)", deco.Value.F(v.Format.Name))
}
case decode.Array, []interface{}:
var l int
switch vv := v.V.(type) {
case decode.Array:
l = len(vv)
case []interface{}:
l = len(vv)
default:
panic("unreachable")
}
cfmt(colField, " %s%s%s", deco.Index.F("["), deco.Number.F(strconv.Itoa(l)), deco.Index.F("]"))
if v.Description != "" {
cfmt(colField, " %s", deco.Value.F(v.Description))
}
if v.Format != nil {
cfmt(colField, " (%s)", deco.Value.F(v.Format.Name))
}
default:
if v.Symbol != "" {
cfmt(colField, " %s", deco.Value.F(v.Symbol))
cfmt(colField, " (%s)", deco.ValueColor(v).F(previewValue(v)))
} else {
cfmt(colField, " %s", deco.ValueColor(v).F(previewValue(v)))
}
// TODO: similar to struct/array?
if v.Description != "" {
cfmt(colField, fmt.Sprintf(" (%s)", deco.Value.F(v.Description)))
}
}
if opts.Verbose {
cfmt(colField, " %s (%s)",
num.BitRange(v.Range).StringByteBits(opts.AddrBase), num.Bits(v.Range.Len).StringByteBits(opts.SizeBase))
}
cprint(colField, "\n")
if v.Err != nil {
var printErrs func(depth int, err error)
printErrs = func(depth int, err error) {
indent := strings.Repeat(" ", depth)
var formatErr decode.FormatError
var decodeFormatsErr decode.FormatsError
switch {
case errors.As(err, &formatErr):
columns()
cfmt(colField, "%s %s: %s: %s\n", indent, deco.Error.F("error"), formatErr.Format.Name, formatErr.Err.Error())
if opts.Verbose {
for _, f := range formatErr.Stacktrace.Frames() {
columns()
cfmt(colField, "%s %s\n", indent, f.Function)
columns()
cfmt(colField, "%s %s:%d\n", indent, f.File, f.Line)
}
}
switch {
case errors.Is(formatErr.Err, decode.FormatsError{}):
printErrs(depth+1, formatErr.Err)
}
case errors.As(err, &decodeFormatsErr):
cfmt(colField, "%s %s\n", indent, err)
for _, e := range decodeFormatsErr.Errs {
printErrs(depth+1, e)
}
default:
columns()
cfmt(colField, "%s!%s\n", indent, deco.Error.F(v.Err.Error()))
}
}
printErrs(depth, v.Err)
}
bufferLastBit := rootV.RootBitBuf.Len() - 1
startBit := v.Range.Start
stopBit := v.Range.Stop() - 1
sizeBits := v.Range.Len
lastDisplayBit := stopBit
if opts.DisplayBytes > 0 && sizeBits > int64(opts.DisplayBytes)*8 {
lastDisplayBit = startBit + (int64(opts.DisplayBytes)*8 - 1)
if lastDisplayBit%(int64(opts.LineBytes)*8) != 0 {
lastDisplayBit += (int64(opts.LineBytes) * 8) - lastDisplayBit%(int64(opts.LineBytes)*8) - 1
}
if lastDisplayBit > stopBit || stopBit-lastDisplayBit <= int64(opts.LineBytes)*8 {
lastDisplayBit = stopBit
}
}
bufferLastByte := bufferLastBit / 8
startByte := startBit / 8
stopByte := stopBit / 8
lastDisplayByte := lastDisplayBit / 8
displaySizeBytes := lastDisplayByte - startByte + 1
displaySizeBits := displaySizeBytes * 8
maxDisplaySizeBits := bufferLastBit - startByte*8 + 1
if sizeBits == 0 {
displaySizeBits = 0
}
if displaySizeBits > maxDisplaySizeBits {
displaySizeBits = maxDisplaySizeBits
}
startLine := startByte / int64(opts.LineBytes)
startLineByteOffset := startByte % int64(opts.LineBytes)
startLineByte := startLine * int64(opts.LineBytes)
lastDisplayLine := lastDisplayByte / int64(opts.LineBytes)
columns()
// log.Printf("v: %#+v\n", v)
// log.Printf("isSimple: %#+v\n", isSimple)
// log.Printf("opts: %#+v\n", opts)
// log.Printf("depth: %#+v\n", depth)
// has length and is not compound or a collapsed struct/array (max depth)
if v.Range.Len > 0 && (!isCompound(v) || (opts.Depth != 0 && opts.Depth == depth)) {
cfmt(colAddr, "%s%s\n",
rootIndent, deco.DumpAddr.F(num.PadFormatInt(startLineByte, opts.AddrBase, true, addrWidth)))
vBitBuf, err := rootV.RootBitBuf.BitBufRange(startByte*8, displaySizeBits)
if err != nil {
return err
}
addrLines := lastDisplayLine - startLine + 1
hexpairFn := func(b byte) string { return deco.ByteColor(b).Wrap(hexpairwriter.Pair(b)) }
asciiFn := func(b byte) string { return deco.ByteColor(b).Wrap(asciiwriter.SafeASCII(b)) }
if vBitBuf != nil {
if _, err := io.CopyBuffer(
hexpairwriter.New(cw.Columns[colHex], opts.LineBytes, int(startLineByteOffset), hexpairFn),
io.LimitReader(vBitBuf.Copy(), displaySizeBytes),
buf); err != nil {
return err
}
if _, err := io.CopyBuffer(
asciiwriter.New(cw.Columns[colASCII], opts.LineBytes, int(startLineByteOffset), asciiFn),
io.LimitReader(vBitBuf.Copy(), displaySizeBytes),
buf); err != nil {
return err
}
}
for i := int64(1); i < addrLines; i++ {
lineStartByte := startLineByte + i*int64(opts.LineBytes)
columns()
cfmt(colAddr, "%s%s\n", rootIndent, deco.DumpAddr.F(num.PadFormatInt(lineStartByte, opts.AddrBase, true, addrWidth)))
}
// TODO: correct? should rethink columnwriter api maybe?
lastLineStopByte := startLineByte + addrLines*int64(opts.LineBytes) - 1
if lastDisplayByte == bufferLastByte && lastDisplayByte != lastLineStopByte {
// extra "|" in as EOF markers
cfmt(colHex, "%s\n", deco.Column)
cfmt(colASCII, "%s\n", deco.Column)
}
if stopByte != lastDisplayByte {
isEnd := ""
if stopBit == bufferLastBit {
isEnd = " (end)"
}
columns()
cfmt(colAddr, "%s%s\n", rootIndent, deco.DumpAddr.F("*"))
cprint(colHex, "\n")
// TODO: truncate if display_bytes is small?
cfmt(colHex, "until %s%s (%s)",
num.Bits(stopBit).StringByteBits(opts.AddrBase),
isEnd,
num.PadFormatInt(bitio.BitsByteCount(sizeBits), opts.SizeBase, true, 0))
// TODO: dump last line?
}
}
if err := cw.Flush(); err != nil {
return err
}
return nil
}
func dump(v *decode.Value, w io.Writer, opts Options) error {
maxAddrIndentWidth := 0
makeWalkFn := func(fn decode.WalkFn) decode.WalkFn {
return func(v *decode.Value, rootV *decode.Value, depth int, rootDepth int) error {
if opts.Depth != 0 && depth > opts.Depth {
return decode.ErrWalkSkipChildren
}
// skip first root level
if rootDepth > 0 {
rootDepth--
}
return fn(v, rootV, depth, rootDepth)
}
}
_ = v.WalkPreOrder(makeWalkFn(func(v *decode.Value, rootV *decode.Value, depth int, rootDepth int) error {
maxAddrIndentWidth = num.MaxInt(
maxAddrIndentWidth,
rootDepth+num.DigitsInBase(bitio.BitsByteCount(v.Range.Stop()), true, opts.AddrBase),
)
return nil
}))
cw := columnwriter.New(w, []int{maxAddrIndentWidth, 1, opts.LineBytes*3 - 1, 1, opts.LineBytes, 1, -1})
buf := make([]byte, 32*1024)
return v.WalkPreOrder(makeWalkFn(func(v *decode.Value, rootV *decode.Value, depth int, rootDepth int) error {
return dumpEx(v, buf, cw, depth, rootV, rootDepth, maxAddrIndentWidth-rootDepth, opts)
}))
}
func hexdump(w io.Writer, bv BufferRange, opts Options) error {
// TODO: hack
opts.Verbose = true
return dump(
&decode.Value{
Range: bv.r,
RootBitBuf: bv.bb.Copy(),
},
w,
opts,
)
}