1package interp
2
3import (
4	"errors"
5	"fmt"
6	"io"
7	"strconv"
8	"strings"
9
10	"github.com/wader/fq/internal/asciiwriter"
11	"github.com/wader/fq/internal/columnwriter"
12	"github.com/wader/fq/internal/hexpairwriter"
13	"github.com/wader/fq/internal/num"
14	"github.com/wader/fq/pkg/bitio"
15	"github.com/wader/fq/pkg/decode"
16	"github.com/wader/fq/pkg/scalar"
17)
18
19// 0   12      34    56
20// addr|hexdump|ascii|field
21const (
22	colAddr  = 0
23	colHex   = 2
24	colASCII = 4
25	colField = 6
26)
27
28func isCompound(v *decode.Value) bool {
29	switch v.V.(type) {
30	case *decode.Compound:
31		return true
32	default:
33		return false
34	}
35}
36
37func dumpEx(v *decode.Value, buf []byte, cw *columnwriter.Writer, depth int, rootV *decode.Value, rootDepth int, addrWidth int, opts Options) error {
38	deco := opts.Decorator
39	// no error check as we write into buffering column
40	// we check for err later for Flush()
41	cprint := func(c int, a ...interface{}) {
42		fmt.Fprint(cw.Columns[c], a...)
43	}
44	cfmt := func(c int, format string, a ...interface{}) {
45		fmt.Fprintf(cw.Columns[c], format, a...)
46	}
47
48	columns := func() {
49		cprint(1, deco.Column, "\n")
50		cprint(3, deco.Column, "\n")
51		cprint(5, deco.Column, "\n")
52	}
53
54	var hexHeader string
55	var asciiHeader string
56	if depth == 0 {
57		for i := 0; i < opts.LineBytes; i++ {
58			s := num.PadFormatInt(int64(i), opts.AddrBase, false, 2)
59			hexHeader += s
60			if i < opts.LineBytes-1 {
61				hexHeader += " "
62			}
63			asciiHeader += s[len(s)-1:]
64		}
65	}
66
67	isInArray := false
68	inArrayLen := 0
69	if v.Parent != nil {
70		if dc, ok := v.Parent.V.(*decode.Compound); ok {
71			isInArray = dc.IsArray
72			inArrayLen = len(dc.Children)
73		}
74	}
75
76	nameV := v
77	name := nameV.Name
78	if isInArray {
79		nameV = v.Parent
80		name = ""
81	}
82	if depth == 0 {
83		name = valuePathDecorated(nameV, deco)
84	} else {
85		name = deco.ObjectKey.Wrap(name)
86	}
87
88	rootIndent := strings.Repeat(" ", rootDepth)
89	indent := strings.Repeat("  ", depth)
90
91	if depth == 0 {
92		if !isCompound(v) {
93			columns()
94		}
95		cfmt(colHex, "%s", deco.DumpHeader.F(hexHeader))
96		cfmt(colASCII, "%s", deco.DumpHeader.F(asciiHeader))
97		if !isCompound(v) {
98			cw.Flush()
99		}
100	}
101
102	if opts.ArrayTruncate != 0 && depth != 0 && isInArray && v.Index >= opts.ArrayTruncate {
103		columns()
104		cfmt(colField, "%s%s%s:%s%s: ...",
105			indent,
106			deco.Index.F("["),
107			deco.Number.F(strconv.Itoa(v.Index)),
108			deco.Number.F(strconv.Itoa(inArrayLen)),
109			deco.Index.F("]"),
110		)
111		cw.Flush()
112		return decode.ErrWalkBreak
113	}
114
115	cfmt(colField, "%s%s", indent, name)
116	if isInArray {
117		cfmt(colField, "%s%s%s", deco.Index.F("["), deco.Number.F(strconv.Itoa(v.Index)), deco.Index.F("]"))
118	}
119
120	var valueErr error
121
122	// TODO: cleanup map[string]interface{} []interface{} or json format
123	// dump should use some internal interface instead?
124	switch vv := v.V.(type) {
125	case *decode.Compound:
126		if vv.IsArray {
127			cfmt(colField, "%s%s:%s%s", deco.Index.F("["), deco.Number.F("0"), deco.Number.F(strconv.Itoa(len(vv.Children))), deco.Index.F("]"))
128		} else {
129			cfmt(colField, "%s", deco.Object.F("{}"))
130		}
131		cprint(colField, ":")
132		if opts.Verbose && isInArray {
133			cfmt(colField, " %s", v.Name)
134		}
135		if vv.Description != "" {
136			cfmt(colField, " %s", deco.Value.F(vv.Description))
137		}
138		if vv.Format != nil {
139			cfmt(colField, " (%s)", deco.Value.F(vv.Format.Name))
140		}
141
142		valueErr = vv.Err
143	case *scalar.S:
144		// TODO: rethink scalar array/struct (json format)
145		switch av := vv.Actual.(type) {
146		case map[string]interface{}:
147			cfmt(colField, ": %s (%s)", deco.Object.F("{}"), deco.Value.F("json"))
148		case []interface{}:
149			cfmt(colField, ": %s%s:%s%s (%s)", deco.Index.F("["), deco.Number.F("0"), deco.Number.F(strconv.Itoa(len(av))), deco.Index.F("]"), deco.Value.F("json"))
150		default:
151			cprint(colField, ":")
152			if vv.Sym == nil {
153				cfmt(colField, " %s", deco.ValueColor(vv.Actual).F(previewValue(vv.Actual, vv.ActualDisplay)))
154			} else {
155				cfmt(colField, " %s", deco.ValueColor(vv.Actual).F(previewValue(vv.Sym, vv.SymDisplay)))
156				cfmt(colField, " (%s)", deco.ValueColor(vv.Actual).F(previewValue(vv.Actual, vv.ActualDisplay)))
157			}
158
159			if opts.Verbose && isInArray {
160				cfmt(colField, " %s", v.Name)
161			}
162
163			// TODO: similar to struct/array?
164			if vv.Description != "" {
165				cfmt(colField, fmt.Sprintf(" (%s)", deco.Value.F(vv.Description)))
166			}
167		}
168	default:
169		panic(fmt.Sprintf("unreachable vv %#+v", vv))
170	}
171
172	innerRange := v.InnerRange()
173
174	if opts.Verbose {
175		cfmt(colField, " %s (%s)",
176			num.BitRange(innerRange).StringByteBits(opts.AddrBase), num.Bits(innerRange.Len).StringByteBits(opts.SizeBase))
177	}
178
179	cprint(colField, "\n")
180
181	if valueErr != nil {
182		var printErrs func(depth int, err error)
183		printErrs = func(depth int, err error) {
184			indent := strings.Repeat("  ", depth)
185
186			var formatErr decode.FormatError
187			var decodeFormatsErr decode.FormatsError
188
189			switch {
190			case errors.As(err, &formatErr):
191				columns()
192				cfmt(colField, "%s  %s: %s: %s\n", indent, deco.Error.F("error"), formatErr.Format.Name, formatErr.Err.Error())
193
194				if opts.Verbose {
195					for _, f := range formatErr.Stacktrace.Frames() {
196						columns()
197						cfmt(colField, "%s    %s\n", indent, f.Function)
198						columns()
199						cfmt(colField, "%s      %s:%d\n", indent, f.File, f.Line)
200					}
201				}
202				switch {
203				case errors.Is(formatErr.Err, decode.FormatsError{}):
204					printErrs(depth+1, formatErr.Err)
205				}
206			case errors.As(err, &decodeFormatsErr):
207				cfmt(colField, "%s  %s\n", indent, err)
208				for _, e := range decodeFormatsErr.Errs {
209					printErrs(depth+1, e)
210				}
211			default:
212				columns()
213				cfmt(colField, "%s!%s\n", indent, deco.Error.F(err.Error()))
214			}
215		}
216
217		printErrs(depth, valueErr)
218	}
219
220	bufferLastBit := rootV.RootBitBuf.Len() - 1
221	startBit := innerRange.Start
222	stopBit := innerRange.Stop() - 1
223	sizeBits := innerRange.Len
224	lastDisplayBit := stopBit
225
226	if opts.DisplayBytes > 0 && sizeBits > int64(opts.DisplayBytes)*8 {
227		lastDisplayBit = startBit + (int64(opts.DisplayBytes)*8 - 1)
228		if lastDisplayBit%(int64(opts.LineBytes)*8) != 0 {
229			lastDisplayBit += (int64(opts.LineBytes) * 8) - lastDisplayBit%(int64(opts.LineBytes)*8) - 1
230		}
231
232		if lastDisplayBit > stopBit || stopBit-lastDisplayBit <= int64(opts.LineBytes)*8 {
233			lastDisplayBit = stopBit
234		}
235	}
236
237	bufferLastByte := bufferLastBit / 8
238	startByte := startBit / 8
239	stopByte := stopBit / 8
240	lastDisplayByte := lastDisplayBit / 8
241	displaySizeBytes := lastDisplayByte - startByte + 1
242	displaySizeBits := displaySizeBytes * 8
243	maxDisplaySizeBits := bufferLastBit - startByte*8 + 1
244	if sizeBits == 0 {
245		displaySizeBits = 0
246	}
247	if displaySizeBits > maxDisplaySizeBits {
248		displaySizeBits = maxDisplaySizeBits
249	}
250
251	startLine := startByte / int64(opts.LineBytes)
252	startLineByteOffset := startByte % int64(opts.LineBytes)
253	startLineByte := startLine * int64(opts.LineBytes)
254	lastDisplayLine := lastDisplayByte / int64(opts.LineBytes)
255
256	columns()
257
258	// has length and is not compound or a collapsed struct/array (max depth)
259	if innerRange.Len > 0 && (!isCompound(v) || (opts.Depth != 0 && opts.Depth == depth)) {
260		cfmt(colAddr, "%s%s\n",
261			rootIndent, deco.DumpAddr.F(num.PadFormatInt(startLineByte, opts.AddrBase, true, addrWidth)))
262
263		vBitBuf, err := rootV.RootBitBuf.BitBufRange(startByte*8, displaySizeBits)
264		if err != nil {
265			return err
266		}
267
268		addrLines := lastDisplayLine - startLine + 1
269		hexpairFn := func(b byte) string { return deco.ByteColor(b).Wrap(hexpairwriter.Pair(b)) }
270		asciiFn := func(b byte) string { return deco.ByteColor(b).Wrap(asciiwriter.SafeASCII(b)) }
271
272		if vBitBuf != nil {
273			if _, err := io.CopyBuffer(
274				hexpairwriter.New(cw.Columns[colHex], opts.LineBytes, int(startLineByteOffset), hexpairFn),
275				io.LimitReader(vBitBuf.Clone(), displaySizeBytes),
276				buf); err != nil {
277				return err
278			}
279			if _, err := io.CopyBuffer(
280				asciiwriter.New(cw.Columns[colASCII], opts.LineBytes, int(startLineByteOffset), asciiFn),
281				io.LimitReader(vBitBuf.Clone(), displaySizeBytes),
282				buf); err != nil {
283				return err
284			}
285		}
286
287		for i := int64(1); i < addrLines; i++ {
288			lineStartByte := startLineByte + i*int64(opts.LineBytes)
289			columns()
290			cfmt(colAddr, "%s%s\n", rootIndent, deco.DumpAddr.F(num.PadFormatInt(lineStartByte, opts.AddrBase, true, addrWidth)))
291		}
292		// TODO: correct? should rethink columnwriter api maybe?
293		lastLineStopByte := startLineByte + addrLines*int64(opts.LineBytes) - 1
294		if lastDisplayByte == bufferLastByte && lastDisplayByte != lastLineStopByte {
295			// extra "|" in as EOF markers
296			cfmt(colHex, "%s\n", deco.Column)
297			cfmt(colASCII, "%s\n", deco.Column)
298		}
299
300		if stopByte != lastDisplayByte {
301			isEnd := ""
302			if stopBit == bufferLastBit {
303				isEnd = " (end)"
304			}
305			columns()
306
307			cfmt(colAddr, "%s%s\n", rootIndent, deco.DumpAddr.F("*"))
308			cprint(colHex, "\n")
309			// TODO: truncate if display_bytes is small?
310			cfmt(colHex, "until %s%s (%s)",
311				num.Bits(stopBit).StringByteBits(opts.AddrBase),
312				isEnd,
313				num.PadFormatInt(bitio.BitsByteCount(sizeBits), opts.SizeBase, true, 0))
314			// TODO: dump last line?
315		}
316	}
317
318	if err := cw.Flush(); err != nil {
319		return err
320	}
321
322	return nil
323}
324
325func dump(v *decode.Value, w io.Writer, opts Options) error {
326	maxAddrIndentWidth := 0
327	makeWalkFn := func(fn decode.WalkFn) decode.WalkFn {
328		return func(v *decode.Value, rootV *decode.Value, depth int, rootDepth int) error {
329			if opts.Depth != 0 && depth > opts.Depth {
330				return decode.ErrWalkSkipChildren
331			}
332
333			return fn(v, rootV, depth, rootDepth)
334		}
335	}
336
337	_ = v.WalkPreOrder(makeWalkFn(func(v *decode.Value, rootV *decode.Value, depth int, rootDepth int) error {
338		maxAddrIndentWidth = num.MaxInt(
339			maxAddrIndentWidth,
340			rootDepth+num.DigitsInBase(bitio.BitsByteCount(v.InnerRange().Stop()), true, opts.AddrBase),
341		)
342		return nil
343	}))
344
345	cw := columnwriter.New(w, []int{maxAddrIndentWidth, 1, opts.LineBytes*3 - 1, 1, opts.LineBytes, 1, -1})
346	buf := make([]byte, 32*1024)
347
348	return v.WalkPreOrder(makeWalkFn(func(v *decode.Value, rootV *decode.Value, depth int, rootDepth int) error {
349		return dumpEx(v, buf, cw, depth, rootV, rootDepth, maxAddrIndentWidth-rootDepth, opts)
350	}))
351}
352
353func hexdump(w io.Writer, bv Buffer, opts Options) error {
354	bb, err := bv.toBuffer()
355	if err != nil {
356		return err
357	}
358	// TODO: hack
359	opts.Verbose = true
360	return dump(
361		&decode.Value{
362			// TODO: hack
363			V:          &scalar.S{Actual: bb},
364			Range:      bv.r,
365			RootBitBuf: bv.bb.Clone(),
366		},
367		w,
368		opts,
369	)
370}
371