mirror of
https://github.com/wader/fq.git
synced 2024-12-18 02:41:44 +03:00
365 lines
9.7 KiB
Go
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,
|
|
)
|
|
}
|