Skip to content

Commit

Permalink
interp: Eval options in jq instead of calling jq from go
Browse files Browse the repository at this point in the history
Simpler and causes less weird performance issues
  • Loading branch information
wader committed Nov 1, 2021
1 parent 13fae09 commit 96cc128
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 61 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"Errorer",
"errorln",
"esds",
"eval",
"Exif",
"Exiter",
"FALLID",
Expand Down
1 change: 1 addition & 0 deletions doc/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- `format/0` overlap with jq builtin `format/1`. What to rename it to? `decode_format`?
- repl expression returning a value that produced lots of output can't be interrupted. This is becaus ctrl-C currently only interrupts the evaluation of the expression, outputted value is printed (`display`) by parent.
- Rework cli/repl user interrupt (context cancel via ctrl-c), see comment in Interp.Main
- Optimize `Interp.Options` calls, now called per display. Cache per eval? needs to handle nested evals.

### TODO and ideas

Expand Down
10 changes: 5 additions & 5 deletions pkg/interp/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (i *Interp) makeFunctions() []Function {

{[]string{"_tobitsrange"}, 0, 2, i._toBitsRange, nil},

{[]string{"tovalue"}, 0, 1, i.toValue, nil},
{[]string{"_tovalue"}, 1, 1, i._toValue, nil},

{[]string{"hex"}, 0, 0, makeStringBitBufTransformFn(
func(r io.Reader) (io.Reader, error) { return hex.NewDecoder(r), nil },
Expand Down Expand Up @@ -664,7 +664,7 @@ func (i *Interp) format(c interface{}, a []interface{}) interface{} {
}

func (i *Interp) _display(c interface{}, a []interface{}) gojq.Iter {
opts := i.OptionsEx(a...)
opts := i.Options(a[0])

switch v := c.(type) {
case Display:
Expand Down Expand Up @@ -734,9 +734,9 @@ func (i *Interp) _toBitsRange(c interface{}, a []interface{}) interface{} {
return bv
}

func (i *Interp) toValue(c interface{}, a []interface{}) interface{} {
func (i *Interp) _toValue(c interface{}, a []interface{}) interface{} {
v, _ := toValue(
func() Options { return i.OptionsEx(append([]interface{}{}, a...)...) },
func() Options { return i.Options(a[0]) },
c,
)
return v
Expand Down Expand Up @@ -921,7 +921,7 @@ func (i *Interp) _bitsMatch(c interface{}, a []interface{}) gojq.Iter {
}

func (i *Interp) _hexdump(c interface{}, a []interface{}) gojq.Iter {
opts := i.OptionsEx(a...)
opts := i.Options(a[0])
bv, err := toBufferView(c)
if err != nil {
return gojq.NewIter(err)
Expand Down
20 changes: 12 additions & 8 deletions pkg/interp/funcs.jq
Original file line number Diff line number Diff line change
Expand Up @@ -54,22 +54,26 @@ def decode($name; $opts):
def decode($name): decode($name; {});
def decode: decode(options.decode_format; {});

def tovalue($opts): _tovalue(options($opts));
def tovalue: _tovalue({});

def display($opts): _display($opts);
def display: _display({});
def d($opts): _display($opts);
def d: _display({});
def full($opts): _display({array_truncate: 0} + $opts);
def display($opts): _display(options($opts));
def display: display({});
def d($opts): display($opts);
def d: display({});

def full($opts): display({array_truncate: 0} + $opts);
# TODO: rename, gets mixed up with f args often
def full: full({});
def f($opts): full($opts);
def f: full;
def verbose($opts): _display({verbose: true, array_truncate: 0} + $opts);
def verbose($opts): display({verbose: true, array_truncate: 0} + $opts);
def verbose: verbose({});
def v($opts): verbose($opts);
def v: verbose;
def hexdump($opts): _hexdump({display_bytes: 0} + $opts);
def hexdump: _hexdump({display_bytes: 0});

def hexdump($opts): _hexdump(options({display_bytes: 0} + $opts));
def hexdump: hexdump({display_bytes: 0});
def hd($opts): hexdump($opts);
def hd: hexdump;

Expand Down
13 changes: 11 additions & 2 deletions pkg/interp/grep.jq
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# TODO: remove once symbolic value is done properly
def _grep_tovalue:
( if _is_decode_value then
( ._symbol as $s
| if $s != "" then $s end
)
end
);

def _grep($v; filter_cond; string_cond; other_cond):
if $v | type == "string" then
( ..
Expand All @@ -10,14 +19,14 @@ def _grep($v; filter_cond; string_cond; other_cond):
end;

def _value_grep_string_cond($v; $flags):
( _tovalue
( _grep_tovalue
| if type == "string" then test($v; $flags)
else false
end
)? // false;

def _value_grep_other_cond($v; $flags):
( _tovalue
( _grep_tovalue
| . == $v
)? // false;

Expand Down
13 changes: 3 additions & 10 deletions pkg/interp/internal.jq
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def _input_decode_errors(f): _global_var("input_decode_errors"; f);
def _variables: _global_var("variables");
def _variables(f): _global_var("variables"; f);

# eval f and finally eval fin even on empty or error
# eval f and finally eval fin even on empty or error.
# note that if f outputs more than one value fin will be called
# for each value.
def _finally(f; fin):
( try f // (fin | empty)
catch (fin as $_ | error)
Expand Down Expand Up @@ -100,15 +102,6 @@ def _eval($expr; $filename; f; on_error; on_compile_error):
else on_error
end;

# TODO: remove one symbolic value is done properly?
def _tovalue:
( if _is_decode_value then
( ._symbol as $s
| if $s != "" then $s end
)
end
);

def _is_scalar:
type |. != "array" and . != "object";

Expand Down
32 changes: 4 additions & 28 deletions pkg/interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -871,31 +871,11 @@ func (i *Interp) variables() map[string]interface{} {
return variablesAny
}

func (i *Interp) OptionsEx(fnOptsV ...interface{}) Options {
opts := func() Options {
vs, err := i.EvalFuncValues(i.evalContext.ctx, nil, "options", []interface{}{fnOptsV}, DiscardCtxWriter{Ctx: i.evalContext.ctx})
if err != nil {
return Options{}
}
if len(vs) < 1 {
return Options{}
}
v := vs[0]
if _, ok := v.(error); ok {
return Options{}
}
m, ok := v.(map[string]interface{})
if !ok {
return Options{}
}
var opts Options
_ = mapstructure.Decode(m, &opts)
opts.Depth = num.MaxInt(0, opts.Depth)

return opts
}()

func (i *Interp) Options(v interface{}) Options {
var opts Options
_ = mapstructure.Decode(v, &opts)
opts.ArrayTruncate = num.MaxInt(0, opts.ArrayTruncate)
opts.Depth = num.MaxInt(0, opts.Depth)
opts.AddrBase = num.ClampInt(2, 36, opts.AddrBase)
opts.SizeBase = num.ClampInt(2, 36, opts.SizeBase)
opts.LineBytes = num.MaxInt(0, opts.LineBytes)
Expand All @@ -906,10 +886,6 @@ func (i *Interp) OptionsEx(fnOptsV ...interface{}) Options {
return opts
}

func (i *Interp) Options() Options {
return i.OptionsEx(nil)
}

func (i *Interp) NewColorJSON(opts Options) (*colorjson.Encoder, error) {
indent := 2
if opts.Compact {
Expand Down
6 changes: 2 additions & 4 deletions pkg/interp/options.jq
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,6 @@ def _to_options:
| with_entries(select(.value != null))
);

# . will have additional array of options taking priority
# NOTE: is called from go *interp.Interp Options()
def options($opts):
[_build_default_dynamic_options] + _options_stack + $opts | add;
def options: options([{}]);
[_build_default_dynamic_options] + _options_stack + [$opts] | add;
def options: options({});
19 changes: 16 additions & 3 deletions pkg/interp/repl.jq
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,29 @@ def _prompt:
, _values
] | join(" ") + "> ";

def _repl_display: _display({depth: 1});
# _repl_display takes a opts arg to make it possible for repl_eval to
# just call options/0 once per eval even if it was multiple outputs
def _repl_display_opts: options({depth: 1});
def _repl_display($opts): _display($opts);
def _repl_display: _display(_repl_display_opts);
def _repl_on_error:
( if _eval_is_compile_error then _eval_compile_error_tostring
# was interrupte by user, just ignore
# was interrupted by user, just ignore
elif _is_context_canceled_error then empty
end
| (_error_str | println)
);
def _repl_on_compile_error: _repl_on_error;
def _repl_eval($expr): _eval($expr; "repl"; _repl_display; _repl_on_error; _repl_on_compile_error);
def _repl_eval($expr):
( _repl_display_opts as $opts
| _eval(
$expr;
"repl";
_repl_display($opts);
_repl_on_error;
_repl_on_compile_error
)
);

# run read-eval-print-loop
def _repl($opts): #:: a|(Opts) => @
Expand Down
1 change: 0 additions & 1 deletion pkg/interp/testdata/args.fqtest
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ vpx_ccr VPX Codec Configuration Record
wav WAV file
webp WebP image
xing Xing header
zip ZIP archive
$ fq -X
exitcode: 2
stderr:
Expand Down

0 comments on commit 96cc128

Please sign in to comment.