Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

options: add "-o base=2" #703

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
options: add "-o base=2"
Add experimental support for base 2 (bits).

For example:
$ fq -o base=2 '.frames[0].header' example.mp3

Also, add various comments to make sense of the dump.go code.
  • Loading branch information
mikez committed Jun 14, 2023
commit 314e2b8788bd34f6f97342359cf1d4368e60b576
57 changes: 57 additions & 0 deletions internal/binwriter/binwriter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package binwriter

import (
"io"

"github.com/wader/fq/pkg/bitio"
)

type Writer struct {
w io.Writer
width int
startLineOffset int
offset int
fn func(b byte) string
}

func New(w io.Writer, width int, startLineOffset int, fn func(b byte) string) *Writer {
return &Writer{
w: w,
width: width,
startLineOffset: startLineOffset,
offset: 0,
fn: fn,
}
}

func (w *Writer) WriteBits(p []byte, nBits int64) (n int64, err error) {
for w.offset < w.startLineOffset {
b := []byte(" ")
if w.offset%w.width == w.width-1 {
b = []byte(" \n")
}
if _, err := w.w.Write(b); err != nil {
return 0, err
}
w.offset++
}

for i := int64(0); i < nBits; i++ {
var v byte
if bitio.Read64(p, i, 1) == 1 {
v = 1
}
if _, err := w.w.Write([]byte(w.fn(v))); err != nil {
return 0, err
}

w.offset++
if w.offset%w.width == 0 {
if _, err := w.w.Write([]byte("\n")); err != nil {
return 0, err
}
}
}

return nBits, nil
}
111 changes: 87 additions & 24 deletions pkg/interp/dump.go
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import (

"github.com/wader/fq/internal/ansi"
"github.com/wader/fq/internal/asciiwriter"
"github.com/wader/fq/internal/binwriter"
"github.com/wader/fq/internal/bitioex"
"github.com/wader/fq/internal/columnwriter"
"github.com/wader/fq/internal/hexpairwriter"
@@ -72,6 +73,7 @@ func dumpEx(v *decode.Value, ctx *dumpCtx, depth int, rootV *decode.Value, rootD
cprint := func(c int, a ...any) {
fmt.Fprint(cw.Columns[c], a...)
}
// cfmt: column i fmt.fprintf
cfmt := func(c int, format string, a ...any) {
fmt.Fprintf(cw.Columns[c], format, a...)
}
@@ -118,7 +120,9 @@ func dumpEx(v *decode.Value, ctx *dumpCtx, depth int, rootV *decode.Value, rootD

// show address bar on root, nested root and format change
if depth == 0 || v.IsRoot || v.Format != nil {
// write header: 00 01 02 03 04
cfmt(colHex, "%s", deco.DumpHeader.F(ctx.hexHeader))
// write header: 012345
cfmt(colASCII, "%s", deco.DumpHeader.F(ctx.asciiHeader))

if willDisplayData {
@@ -188,6 +192,10 @@ func dumpEx(v *decode.Value, ctx *dumpCtx, depth int, rootV *decode.Value, rootD

cprint(colField, "\n")

// --------------------------------------------------
// Error handling
// --------------------------------------------------

if valueErr != nil {
var printErrs func(depth int, err error)
printErrs = func(depth int, err error) {
@@ -223,24 +231,33 @@ func dumpEx(v *decode.Value, ctx *dumpCtx, depth int, rootV *decode.Value, rootD
printErrs(depth, valueErr)
}

// --------------------------------------------------
// For a given field, compute various helper variables
// --------------------------------------------------

rootBitLen, err := bitioex.Len(rootV.RootReader)
if err != nil {
return err
}

bufferLastBit := rootBitLen - 1
startBit := innerRange.Start
stopBit := innerRange.Stop() - 1
sizeBits := innerRange.Len
lastDisplayBit := stopBit
startBit := innerRange.Start // field's start bit index (for entire file)
stopBit := innerRange.Stop() - 1 // field's end bit index (for entire file); inclusive
sizeBits := innerRange.Len // field's bit length (1, 8, 16, 32, ...)

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
// determine lastDisplayBit:
// sometimes the field's bit length overflows the max width of a line;
// cut off the overflow in such cases.
lastDisplayBit := stopBit
displayBits := int64(opts.DisplayBytes) * 8
lineBits := int64(opts.LineBytes) * 8
if opts.DisplayBytes > 0 && sizeBits > displayBits {
lastDisplayBit = startBit + (displayBits - 1)
if lastDisplayBit%lineBits != 0 {
lastDisplayBit += lineBits - lastDisplayBit%lineBits - 1
}

if lastDisplayBit > stopBit || stopBit-lastDisplayBit <= int64(opts.LineBytes)*8 {
if lastDisplayBit > stopBit || stopBit-lastDisplayBit <= lineBits {
lastDisplayBit = stopBit
}
}
@@ -258,38 +275,71 @@ func dumpEx(v *decode.Value, ctx *dumpCtx, depth int, rootV *decode.Value, rootD
if displaySizeBits > maxDisplaySizeBits {
displaySizeBits = maxDisplaySizeBits
}
if displaySizeBits > stopBit-startBit {
displaySizeBits = stopBit - startBit + 1 // TODO: -1 hmm
}

startLine := startByte / int64(opts.LineBytes)
startLineByteOffset := startByte % int64(opts.LineBytes)

startLineBitOffset := startBit % int64(opts.LineBytes*8)

startLineByte := startLine * int64(opts.LineBytes)
lastDisplayLine := lastDisplayByte / int64(opts.LineBytes)

// --------------------------------------------------
// Output Data
// --------------------------------------------------

// has length and is not compound or a collapsed struct/array (max depth)
if willDisplayData {
// write address: 0x00012 (example)
cfmt(colAddr, "%s%s\n",
rootIndent, deco.DumpAddr.F(mathex.PadFormatInt(startLineByte, opts.Addrbase, true, addrWidth)))

vBR, err := bitioex.Range(rootV.RootReader, startByte*8, displaySizeBits)
vBR1, err := bitioex.Range(rootV.RootReader, startByte*8, displaySizeBits)
if err != nil {
return err
}

vBR2, err := bitioex.Range(rootV.RootReader, startBit, displaySizeBits)
if err != nil {
return err
}

addrLines := lastDisplayLine - startLine + 1
hexpairFn := func(b byte) string { return deco.ByteColor(b).Wrap(hexpairwriter.Pair(b)) }
binFn := func(b byte) string { return deco.ByteColor(b).Wrap(string("01"[int(b)])) }
asciiFn := func(b byte) string { return deco.ByteColor(b).Wrap(asciiwriter.SafeASCII(b)) }

hexBR, err := bitio.CloneReadSeeker(vBR)
if err != nil {
return err
}
if _, err := bitioex.CopyBitsBuffer(
hexpairwriter.New(cw.Columns[colHex], opts.LineBytes, int(startLineByteOffset), hexpairFn),
hexBR,
buf); err != nil {
return err
switch opts.Base {
case 16:
// write hex: 89 50 4e 47 0d 0a 1a 0a ...
hexBR, err := bitio.CloneReadSeeker(vBR1)
if err != nil {
return err
}
if _, err := bitioex.CopyBitsBuffer(
hexpairwriter.New(cw.Columns[colHex], opts.LineBytes, int(startLineByteOffset), hexpairFn),
hexBR,
buf); err != nil {
return err
}
case 2:
// write bits: 100010010101000...
hexBR, err := bitio.CloneReadSeeker(vBR2)
if err != nil {
return err
}
if _, err := bitio.CopyBuffer(
binwriter.New(cw.Columns[colHex], opts.LineBytes*8, int(startLineBitOffset), binFn),
hexBR,
buf); err != nil {
return err
}
}

asciiBR, err := bitio.CloneReadSeeker(vBR)
// write ascii: .PNG.........IHDR...
asciiBR, err := bitio.CloneReadSeeker(vBR1)
if err != nil {
return err
}
@@ -364,7 +414,13 @@ func dump(v *decode.Value, w io.Writer, opts *Options) error {
}

addrColumnWidth := maxAddrIndentWidth
hexColumnWidth := opts.LineBytes*3 - 1
var hexColumnWidth int
switch opts.Base {
case 16:
hexColumnWidth = opts.LineBytes*3 - 1
case 2:
hexColumnWidth = opts.LineBytes * 8
}
asciiColumnWidth := opts.LineBytes
treeColumnWidth := -1
// TODO: set with and truncate/wrap properly
@@ -387,11 +443,18 @@ func dump(v *decode.Value, w io.Writer, opts *Options) error {

var hexHeader string
var asciiHeader string
var spaceLength int
switch opts.Base {
case 16:
spaceLength = 1
case 2:
spaceLength = 8 - 2 // TODO: adapt for wider screens
}
for i := 0; i < opts.LineBytes; i++ {
s := mathex.PadFormatInt(int64(i), opts.Addrbase, false, 2)
hexHeader += s
if i < opts.LineBytes-1 {
hexHeader += " "
if spaceLength > 1 || i < opts.LineBytes-1 {
hexHeader += strings.Repeat(" ", spaceLength)
}
asciiHeader += s[len(s)-1:]
}
1 change: 1 addition & 0 deletions pkg/interp/interp.go
Original file line number Diff line number Diff line change
@@ -1044,6 +1044,7 @@ type Options struct {
BitsFormat string
LineBytes int
DisplayBytes int
Base int
Addrbase int
Sizebase int
SkipGaps bool
24 changes: 22 additions & 2 deletions pkg/interp/options.jq
Original file line number Diff line number Diff line change
@@ -78,6 +78,7 @@ def _opt_options:
argdecode: "array_string_pair",
argjson: "array_string_pair",
array_truncate: "number",
base: "number",
bits_format: "string",
byte_colors: "csv_ranges_array",
color: "boolean",
@@ -528,12 +529,31 @@ def options($opts):
+ [$opts]
)
| add
| ( if .width != 0 then [_intdiv(_intdiv(.width; 8); 2) * 2, 4] | max
else 16
| ( if .width != 0 then
if .base == 2 then
# set input data bits to ~44.4% of width
.5 * (8 / (8 + 1)) * .width | floor
else
# set input data hex to ~37.5% of width
.5 * (3 / (3 + 1)) * .width | floor
end
else
16 * 3
end
) as $input_data_width
| ( if .base == 2 then
# 100010010101000
# show at least 1 byte
[_intdiv($input_data_width; 8), 1] | max
else
# 89 50 4e 47 0d 0a 1a 0a
# show an even amount of bytes; and at least 4
[_intdiv(_intdiv($input_data_width; 3); 2) * 2, 4] | max
end
) as $display_bytes
# default if not set
| .display_bytes |= (. // $display_bytes)
| .line_bytes |= (. // $display_bytes)
| .base |= (. // 16)
);
def options: options({});