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