1
1
mirror of https://github.com/wader/fq.git synced 2024-11-22 07:16:49 +03:00

repl,interp: Refactor repl and slurp

Now repl, slurp and help implemented using same query rewrite.
Include filename context in error if possible.
Add spew function that does opposite of slurp.
Start of help infra, not done or documented yet.
Show error pointer on parse error.
Rename internal eval to _eval and make eval be wrapper that
does rewrite and has various eror handling etc.
Nicer repl, slupr and help errors.
This commit is contained in:
Mattias Wadman 2022-02-20 22:25:46 +01:00
parent 47b3c64bf9
commit 0a043f9096
26 changed files with 805 additions and 311 deletions

4
.gitattributes vendored
View File

@ -1,4 +1,4 @@
# for windows: fqtest uses \n and asserts output so should be exact # for windows: fqtest, jq and json uses \n and uses it in output so must be exact
*.fqtest eol=lf *.fqtest eol=lf
# for windows: there are fqtest using json that need to be exact
*.json eol=lf *.json eol=lf
*.jq eol=lf

View File

@ -36,7 +36,6 @@
#### Language #### Language
- Nicer variables somehow? `... | var($VAR)`? make slurp and rewrite `$var` to `$var[]`?
- Cleanup/Make binary buffers make sense. - Cleanup/Make binary buffers make sense.
- gojq uses golang `int` for slice indexes, might be issue for non-64bit cpus - gojq uses golang `int` for slice indexes, might be issue for non-64bit cpus

View File

@ -417,7 +417,9 @@ you currently have to do `fq -d raw 'mp3({force: true})' file`.
- `ddv`/`ddv($opts)` verbosely display value and don't truncate arrays or binaries - `ddv`/`ddv($opts)` verbosely display value and don't truncate arrays or binaries
- `p`/`preview` show preview of field tree - `p`/`preview` show preview of field tree
- `hd`/`hexdump` hexdump value - `hd`/`hexdump` hexdump value
- `repl` nested REPL, must be last in a pipeline. `1 | repl`, can "slurp" outputs `1, 2, 3 | repl`. - `repl`/`repl($opts)` nested REPL, must be last in a pipeline. `1 | repl`, can "slurp" outputs. Ex: `1, 2, 3 | repl`, `[1,2,3] | repl({compact: true})`.
- `slurp($name)` slurp outputs and save them to `$name`, must be last in pipeline. Will be available as global array `$name`. Ex `1,2,3 | slurp("a")`, `$a[]` same as `spew("a")`.
- `spew`/`spew($name)` output previously slurped values. `spew` outputs all slurps as an object, `spew($name)` outouts one slurp. Ex: `spew("a")`.
- `paste` read string from stdin until ^D. Useful for pasting text. - `paste` read string from stdin until ^D. Useful for pasting text.
- Ex: `paste | frompem | asn1_ber | repl` read from stdin then decode and start a new sub-REPL with result. - Ex: `paste | frompem | asn1_ber | repl` read from stdin then decode and start a new sub-REPL with result.

View File

@ -2,6 +2,8 @@
def _can_display: empty; def _can_display: empty;
def _decode($format; $opts): empty; def _decode($format; $opts): empty;
def _display($opts): empty; def _display($opts): empty;
def _eval($expr; $filename): empty;
def _eval($expr): empty;
def _extkeys: empty; def _extkeys: empty;
def _exttype: empty; def _exttype: empty;
def _global_state: empty; def _global_state: empty;
@ -26,7 +28,5 @@ def _tobits($bits; $is_range; $pad): empty;
def _tovalue: empty; def _tovalue: empty;
def _tovalue($opts): empty; def _tovalue($opts): empty;
def base64: empty; def base64: empty;
def eval($expr; $filename): empty;
def eval($expr): empty;
def open: empty; def open: empty;
def scope: empty; def scope: empty;

View File

@ -2,6 +2,7 @@ package interp
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
@ -173,7 +174,6 @@ func (of *openFile) ToBinary() (Binary, error) {
return newBinaryFromBitReader(of.br, 8, 0) return newBinaryFromBitReader(of.br, 8, 0)
} }
// def open: #:: string| => binary
// opens a file for reading from filesystem // opens a file for reading from filesystem
// TODO: when to close? when br loses all refs? need to use finalizer somehow? // TODO: when to close? when br loses all refs? need to use finalizer somehow?
func (i *Interp) _open(c interface{}, a []interface{}) gojq.Iter { func (i *Interp) _open(c interface{}, a []interface{}) gojq.Iter {
@ -196,6 +196,11 @@ func (i *Interp) _open(c interface{}, a []interface{}) gojq.Iter {
} }
f, err = i.os.FS().Open(path) f, err = i.os.FS().Open(path)
if err != nil { if err != nil {
// path context added in jq error code
var pe *fs.PathError
if errors.As(err, &pe) {
return gojq.NewIter(pe.Err)
}
return gojq.NewIter(err) return gojq.NewIter(err)
} }
} }

98
pkg/interp/eval.jq Normal file
View File

@ -0,0 +1,98 @@
include "internal";
include "query";
def _eval_error($what; $error):
error({
what: $what,
error: $error,
column: 0,
line: 1,
filename: ""
});
def _eval_error_function_not_defined($name; $args):
_eval_error(
"compile";
"function not defined: \($name)/\($args | length)"
);
def _eval_query_rewrite($opts):
_query_fromtostring(
( . as $orig_query
| _query_pipe_last as $last
| ( $last
| if _query_is_func then [_query_func_name, _query_func_args]
else ["", []]
end
) as [$last_func_name, $last_func_args]
| $opts.slurps[$last_func_name] as $slurp
| if $slurp then
_query_transform_pipe_last(_query_ident)
end
| if $opts.catch_query then
_query_try(.; $opts.catch_query)
end
| _query_pipe(
$opts.input_query // _query_ident;
.
)
| if $slurp then
_query_func(
$slurp;
[ # pass original, rewritten and args queries as query ast trees
( { slurp: _query_string($last_func_name)
, slurp_args:
( $last_func_args
| if . then
( map(_query_toquery)
| _query_commas
| _query_array
)
else (null | _query_array)
end
)
, orig: ($orig_query | _query_toquery)
, rewrite: _query_toquery
}
| _query_object
)
]
)
end
)
);
# TODO: better way? what about nested eval errors?
def _eval_is_compile_error:
type == "object" and .error != null and .what != null;
def _eval_compile_error_tostring:
[ (.filename // "expr")
, if .line != 1 or .column != 0 then "\(.line):\(.column)"
else empty
end
, " \(.error)"
] | join(":");
def eval($expr; $opts; on_error; on_compile_error):
( . as $c
| ($opts.filename // "expr") as $filename
| try
_eval(
$expr | _eval_query_rewrite($opts);
$filename
)
catch
if _eval_is_compile_error then
# rewrite parse error will not have filename
( .filename = $filename
| {error: ., input: $c}
| on_compile_error
)
else
( {error: ., input: $c}
| on_error
)
end
);
def eval($expr): eval($expr; {}; .error | error; .error | error);

View File

@ -2,6 +2,9 @@ include "internal";
include "options"; include "options";
include "binary"; include "binary";
def _display_default_opts:
options({depth: 1});
def display($opts): def display($opts):
( options($opts) as $opts ( options($opts) as $opts
| if _can_display then _display($opts) | if _can_display then _display($opts)
@ -18,6 +21,7 @@ def display($opts):
); );
def display: display({}); def display: display({});
def hexdump($opts): _hexdump(options({display_bytes: 0} + $opts)); def hexdump($opts): _hexdump(options({display_bytes: 0} + $opts));
def hexdump: hexdump({display_bytes: 0}); def hexdump: hexdump({display_bytes: 0});
def hd($opts): hexdump($opts); def hd($opts): hexdump($opts);
@ -55,7 +59,7 @@ def path_to_expr:
# TODO: don't use eval? should support '.a.b[1]."c.c"' and escapes? # TODO: don't use eval? should support '.a.b[1]."c.c"' and escapes?
def expr_to_path: def expr_to_path:
( if type != "string" then error("require string argument") end ( if type != "string" then error("require string argument") end
| eval("null | path(\(.))") | _eval("null | path(\(.))")
); );
def trim: capture("^\\s*(?<str>.*?)\\s*$"; "").str; def trim: capture("^\\s*(?<str>.*?)\\s*$"; "").str;

130
pkg/interp/help.jq Normal file
View File

@ -0,0 +1,130 @@
include "internal";
include "query";
include "eval";
include "repl";
# TODO: variants, values, keywords?
# TODO: store some other way?
def _help_functions:
{ length: {
summary: "Length of string, array, object, etc",
doc:
"- For string number of unicode codepoints
- For array number of elements in array
- For object number of key-value pairs
- For null zero
- For number the number itself
- For boolean is an error
",
examples:
[ [[1,2,3], "length"]
, ["abc", "length"]
, [{a: 1, b: 2}, "length"]
, [null, "length"]
, [123, "length"]
, [true, "length"]
]
},
"..": {
summary: "Recursive descent of .",
doc:
"Recursively descend . and output each value.
Same as recurse without argument.
",
examples:
[ ["a", ".."]
, [[1,2,3], ".."]
, [{a: 1, b: {c: 3}}, ".."]
]
},
empty: {
summary: "Output nothing",
doc:
"Output no value, not even null, and cause backtrack.
",
examples:
[ ["empty"]
, ["[1,empty,2]"]
]
}
};
def help($_): error("help must be alone or last in pipeline. ex: help(length) or ... | help");
def help: help(null);
# TODO: refactor
def _help_slurp($query):
def _name:
if _query_is_func then _query_func_name
else _query_tostring
end;
if $query.orig | _query_is_func then
( ($query.orig | _query_func_args) as $args
| ($args | length) as $argc
| if $args == null then
# help
( "Type expression to evaluate"
, "\\t Completion"
, "Up/Down History"
, "^C Interrupt execution"
, "... | repl Start a new REPL"
, "^D Exit REPL"
) | println
elif $argc == 1 then
# help(...)
( ($args[0] | _name) as $name
| _help_functions[$name] as $hf
| if $hf then
# help(name)
( "\($name): \($hf.summary)"
, $hf.doc
, "Examples:"
, ( $hf.examples[]
| . as $e
| if length == 1 then
( "> \($e[0])"
, (null | try _eval($e[0]) | tojson catch "error: \(.)")
)
else
( "> \($e[0] | tojson) | \($e[1])"
, ($e[0] | try _eval($e[1]) | tojson catch "error: \(.)")
)
end
)
) | println
else
# help(unknown)
# TODO: check builtin
( ( . # TODO: extract
| builtins
| map(split("/") | {key: .[0], value: true})
| from_entries
) as $builtins
| ( . # TODO: extract
| scope
| map({key: ., value: true})
| from_entries
) as $scope
| if $builtins | has($name) then
"\($name) is builtin function"
elif $scope | has($name) then
"\($name) is a function or variable"
else
"don't know what \($name) is "
end
| println
)
end
)
else
_eval_error("compile"; "help must be last in pipeline. ex: help(length) or ... | help")
end
)
else
# ... | help
# TODO: check builtin
( _repl_slurp_eval($query.rewrite) as $outputs
| "value help"
, $outputs
)
end;

View File

@ -9,12 +9,15 @@ def println: ., "\n" | print;
def printerr: tostring | _stderr; def printerr: tostring | _stderr;
def printerrln: ., "\n" | printerr; def printerrln: ., "\n" | printerr;
# jq compat def _debug($name):
def debug: ( (([$name, .] | tojson) | printerrln)
( ((["DEBUG", .] | tojson) | printerrln)
, . , .
); );
# jq compat
def debug: _debug("DEBUG");
def debug(f): . as $c | f | debug | $c; def debug(f): . as $c | f | debug | $c;
# jq compat, output to compact json to stderr and let input thru # jq compat, output to compact json to stderr and let input thru
def stderr: def stderr:
( (tojson | printerr) ( (tojson | printerr)
@ -45,9 +48,6 @@ def _include_paths(f): _global_var("include_paths"; f);
def _options_stack: _global_var("options_stack"); def _options_stack: _global_var("options_stack");
def _options_stack(f): _global_var("options_stack"; f); def _options_stack(f): _global_var("options_stack"; f);
def _options_cache: _global_var("options_cache");
def _options_cache(f): _global_var("options_cache"; f);
def _cli_last_expr_error: _global_var("cli_last_expr_error"); def _cli_last_expr_error: _global_var("cli_last_expr_error");
def _cli_last_expr_error(f): _global_var("cli_last_expr_error"; f); def _cli_last_expr_error(f): _global_var("cli_last_expr_error"; f);
@ -69,10 +69,10 @@ def _input_io_errors(f): _global_var("input_io_errors"; f);
def _input_decode_errors: _global_var("input_decode_errors"); def _input_decode_errors: _global_var("input_decode_errors");
def _input_decode_errors(f): _global_var("input_decode_errors"; f); def _input_decode_errors(f): _global_var("input_decode_errors"; f);
def _variables: _global_var("variables"); def _slurps: _global_var("slurps");
def _variables(f): _global_var("variables"; f); def _slurps(f): _global_var("slurps"; f);
# eval f and finally eval fin even if empty or error. # call f and finally eval fin even if empty or error.
# _finally(1; debug) # _finally(1; debug)
# _finally(null; debug) # _finally(null; debug)
# _finally(error("a"); debug) # _finally(error("a"); debug)
@ -135,24 +135,10 @@ def _recurse_break(f):
else error else error
end; end;
# TODO: better way? what about nested eval errors?
def _eval_is_compile_error: type == "object" and .error != null and .what != null;
def _eval_compile_error_tostring:
[ (.filename | if . == "" then "expr" end)
, if .line != 1 or .column != 0 then "\(.line):\(.column)" else empty end
, " \(.error)"
] | join(":");
def _eval($expr; $filename; f; on_error; on_compile_error):
try
eval($expr; $filename) | f
catch
if _eval_is_compile_error then on_compile_error
else on_error
end;
def _is_scalar: def _is_scalar:
type |. != "array" and . != "object"; type |. != "array" and . != "object";
def _is_context_canceled_error: . == "context canceled"; def _is_context_canceled_error: . == "context canceled";
def _error_str: "error: \(.)"; def _error_str($contexts): (["error"] + $contexts + [.]) | join(": ");
def _error_str: _error_str([]);

View File

@ -36,6 +36,7 @@ import (
//go:embed interp.jq //go:embed interp.jq
//go:embed internal.jq //go:embed internal.jq
//go:embed eval.jq
//go:embed options.jq //go:embed options.jq
//go:embed binary.jq //go:embed binary.jq
//go:embed decode.jq //go:embed decode.jq
@ -45,6 +46,7 @@ import (
//go:embed args.jq //go:embed args.jq
//go:embed query.jq //go:embed query.jq
//go:embed repl.jq //go:embed repl.jq
//go:embed help.jq
//go:embed formats.jq //go:embed formats.jq
var builtinFS embed.FS var builtinFS embed.FS
@ -56,7 +58,7 @@ func init() {
functionRegisterFns = append(functionRegisterFns, func(i *Interp) []Function { functionRegisterFns = append(functionRegisterFns, func(i *Interp) []Function {
return []Function{ return []Function{
{"_readline", 0, 1, nil, i._readline}, {"_readline", 0, 1, nil, i._readline},
{"eval", 1, 2, nil, i.eval}, {"_eval", 1, 2, nil, i._eval},
{"_stdin", 0, 1, nil, i.makeStdioFn("stdin", i.os.Stdin())}, {"_stdin", 0, 1, nil, i.makeStdioFn("stdin", i.os.Stdin())},
{"_stdout", 0, 0, nil, i.makeStdioFn("stdout", i.os.Stdout())}, {"_stdout", 0, 0, nil, i.makeStdioFn("stdout", i.os.Stdout())},
{"_stderr", 0, 0, nil, i.makeStdioFn("stderr", i.os.Stderr())}, {"_stderr", 0, 0, nil, i.makeStdioFn("stderr", i.os.Stderr())},
@ -507,7 +509,7 @@ func (i *Interp) _readline(c interface{}, a []interface{}) gojq.Iter {
return gojq.NewIter(expr) return gojq.NewIter(expr)
} }
func (i *Interp) eval(c interface{}, a []interface{}) gojq.Iter { func (i *Interp) _eval(c interface{}, a []interface{}) gojq.Iter {
var err error var err error
expr, err := toString(a[0]) expr, err := toString(a[0])
if err != nil { if err != nil {
@ -742,7 +744,7 @@ func (i *Interp) Eval(ctx context.Context, c interface{}, expr string, opts Eval
var variableNames []string var variableNames []string
var variableValues []interface{} var variableValues []interface{}
for k, v := range i.variables() { for k, v := range i.slurps() {
variableNames = append(variableNames, "$"+k) variableNames = append(variableNames, "$"+k)
variableValues = append(variableValues, v) variableValues = append(variableValues, v)
} }
@ -794,7 +796,7 @@ func (i *Interp) Eval(ctx context.Context, c interface{}, expr string, opts Eval
} }
ni.evalInstance.includeSeen[filename] = struct{}{} ni.evalInstance.includeSeen[filename] = struct{}{}
// return cached version if file has already been compiled // return cached version if file has already been parsed
if q, ok := ni.includeCache[filename]; ok { if q, ok := ni.includeCache[filename]; ok {
return q, nil return q, nil
} }
@ -1074,9 +1076,9 @@ func (i *Interp) includePaths() []string {
return paths return paths
} }
func (i *Interp) variables() map[string]interface{} { func (i *Interp) slurps() map[string]interface{} {
variablesAny, _ := i.lookupState("variables").(map[string]interface{}) slurpsAny, _ := i.lookupState("slurps").(map[string]interface{})
return variablesAny return slurpsAny
} }
func (i *Interp) Options(v interface{}) Options { func (i *Interp) Options(v interface{}) Options {

View File

@ -6,8 +6,11 @@ include "match";
include "funcs"; include "funcs";
include "grep"; include "grep";
include "args"; include "args";
include "eval";
include "query";
include "repl"; include "repl";
# generated decode functions per format and format helpers include "help";
# generate torepr, format decode helpers and include format specific functions
include "formats"; include "formats";
# optional user init # optional user init
include "@config/init?"; include "@config/init?";
@ -44,20 +47,20 @@ def input:
( . as $err ( . as $err
| _input_io_errors(. += {($name): $err}) as $_ | _input_io_errors(. += {($name): $err}) as $_
| $err | $err
| (_error_str | printerrln) | (_error_str([$name]) | printerrln)
, _input($opts; f) , _input($opts; f)
) )
| try f | try f
catch catch
( . as $err ( . as $err
| _input_decode_errors(. += {($name): $err}) as $_ | _input_decode_errors(. += {($name): $err}) as $_
| [ "\($name): \($opts.decode_format)" | [ $opts.decode_format
, if $err | type == "string" then ": \($err)" , if $err | type == "string" then ": \($err)"
# TODO: if not string assume decode itself failed for now # TODO: if not string assume decode itself failed for now
else ": failed to decode (try -d FORMAT)" else ": failed to decode (try -d FORMAT)"
end end
] | join("") ] | join("")
| (_error_str | printerrln) | (_error_str([$name]) | printerrln)
, _input($opts; f) , _input($opts; f)
) )
); );
@ -116,30 +119,46 @@ def inputs: _repeat_break(input);
def input_filename: _input_filename; def input_filename: _input_filename;
def var: _variables; # user expr error, report and continue
def var($k; f): def _cli_eval_on_expr_error:
( . as $c ( if type == "object" then
| if ($k | _is_ident | not) then error("invalid variable name: \($k)") end if .error | _eval_is_compile_error then .error | _eval_compile_error_tostring
| _variables(.[$k] |= f) elif .error then .error
| empty end
); else tostring
def var($k): . as $c | var($k; $c); end
| . as $err
def _cli_expr_on_error:
( . as $err
| _cli_last_expr_error($err) as $_ | _cli_last_expr_error($err) as $_
| (_error_str | printerrln) | (_error_str([input_filename // empty]) | printerrln)
); );
def _cli_expr_on_compile_error: # other expr error, should not happen, report and halt
( _eval_compile_error_tostring def _cli_eval_on_error:
halt_error(_exit_code_expr_error);
# could not compile expr, report and halt
def _cli_eval_on_compile_error:
( .error
| _eval_compile_error_tostring
| halt_error(_exit_code_compile_error) | halt_error(_exit_code_compile_error)
); );
# _cli_expr_eval halts on compile errors def _cli_repl_error($_):
def _cli_expr_eval($expr; $filename; f): _eval_error("compile"; "repl can only be used from interactive repl");
_eval($expr; $filename; f; _cli_expr_on_error; _cli_expr_on_compile_error); def _cli_slurp_error(_):
def _cli_expr_eval($expr; $filename): _eval_error("compile"; "slurp can only be used from interactive repl");
_eval($expr; $filename; .; _cli_expr_on_error; _cli_expr_on_compile_error); # _cli_eval halts on compile errors
def _cli_eval($expr; $opts):
eval(
$expr;
$opts + {
slurps: {
help: "_help_slurp",
repl: "_cli_repl_error",
slurp: "_cli_slurp_error"
},
catch_query: _query_func("_cli_eval_on_expr_error")
};
_cli_eval_on_error;
_cli_eval_on_compile_error
);
def _main: def _main:
@ -166,20 +185,22 @@ def _main:
, args_help_text(_opt_cli_opts) , args_help_text(_opt_cli_opts)
); );
def _formats_list: def _formats_list:
[ ( formats ( [ formats
| to_entries[] | to_entries[]
| [(.key+" "), .value.description] | [(.key+" "), .value.description]
) ]
]
| table( | table(
.; .;
map( map(
( . as $rc ( . as $rc
| .string # right pad format name to align description
| if $rc.column != 1 then rpad(" "; $rc.maxwidth) end | if .column == 0 then .string | rpad(" "; $rc.maxwidth)
else $rc.string
end
) )
) | join("") ) | join("")
); )
);
def _map_decode_file: def _map_decode_file:
map( map(
( . as $a ( . as $a
@ -228,12 +249,10 @@ def _main:
, null | halt_error(_exit_code_args_error) , null | halt_error(_exit_code_args_error)
) )
else else
# use _finally as display etc prints and outputs empty ( # store some global state
_finally(
# store some globals
( _include_paths($opts.include_path) as $_ ( _include_paths($opts.include_path) as $_
| _input_filenames($opts.filenames) as $_ | _input_filenames($opts.filenames) as $_
| _variables( | _slurps(
( $opts.arg + ( $opts.arg +
$opts.argjson + $opts.argjson +
$opts.raw_file + $opts.raw_file +
@ -242,45 +261,51 @@ def _main:
| from_entries | from_entries
) )
) )
# for inputs a, b, c: ) as $_
# repl: [a,b,c] | repl | { filename: $opts.expr_eval_path
# repl slurp: [[a, b, c]] | repl } as $eval_opts
# cli a, b, c | expr # use _finally as display etc prints and outputs empty
# cli slurp [a ,b c] | expr | _finally(
| ( def _inputs: if $opts.repl then
( if $opts.null_input then null # TODO: share input_query but first have to figure out how to handle
# note that jq --slurp --raw-input (string_input) is special, will concat # context/interrupts better as open will happen in a sub repl which
# all files into one string instead of iterating lines # context will be cancelled.
elif $opts.string_input then inputs ( def _inputs:
elif $opts.slurp then [inputs] if $opts.null_input then null
else inputs elif $opts.string_input then inputs
end elif $opts.slurp then [inputs]
); else inputs
if $opts.repl then end;
( [_inputs] [_inputs]
| map(_cli_expr_eval($opts.expr; $opts.expr_eval_path)) | map(_cli_eval($opts.expr; $eval_opts))
| _repl({}) | _repl({})
)
else
( _inputs
# iterate all inputs
| _cli_last_expr_error(null) as $_
| _cli_expr_eval($opts.expr; $opts.expr_eval_path; _repl_display)
)
end
) )
) else
; # finally ( _cli_last_expr_error(null) as $_
( if _input_io_errors then | _display_default_opts as $default_opts
null | halt_error(_exit_code_input_io_error) | _cli_eval(
end $opts.expr;
| if _input_decode_errors then ( $eval_opts
null | halt_error(_exit_code_input_decode_error) | .input_query =
end ( if $opts.null_input then _query_null
| if _cli_last_expr_error then # note that jq --slurp --raw-input (string_input) is special, will concat
null | halt_error(_exit_code_expr_error) # all files into one string instead of iterating lines
end elif $opts.string_input then _query_func("inputs")
elif $opts.slurp then _query_func("inputs") | _query_array
else _query_func("inputs")
end
)
)
)
| display($default_opts)
)
end;
# finally
( if _input_io_errors then null | halt_error(_exit_code_input_io_error) end
| if _input_decode_errors then null | halt_error(_exit_code_input_decode_error) end
| if _cli_last_expr_error then null | halt_error(_exit_code_expr_error) end
) )
) )
)
end end
); );

View File

@ -86,9 +86,10 @@ def _opt_eval($rest):
# if -f was used, all rest non-args are filenames # if -f was used, all rest non-args are filenames
# otherwise first is expr rest is filesnames # otherwise first is expr rest is filesnames
( .expr_file ( .expr_file
| . as $expr_file
| if . then | if . then
try (open | tobytes | tostring) try (open | tobytes | tostring)
catch halt_error(_exit_code_args_error) catch ("\($expr_file): \(.)" | halt_error(_exit_code_args_error))
else $rest[0] // null else $rest[0] // null
end end
) )
@ -123,8 +124,10 @@ def _opt_eval($rest):
( .raw_file ( .raw_file
| if . then | if . then
( map(.[1] |= ( map(.[1] |=
try (open | tobytes | tostring) ( . as $f
catch halt_error(_exit_code_args_error) | try (open | tobytes | tostring)
catch ("\($f): \(.)" | halt_error(_exit_code_args_error))
)
) )
) )
end end

View File

@ -1,45 +1,123 @@
# a() -> b() # null
def _query_null:
{term: {type: "TermTypeNull"}};
# string
def _query_string($str):
{ term: {
type: "TermTypeString",
str: {
str: $str
}
}
};
# .
def _query_ident:
{term: {type: "TermTypeIdentity"}};
def _query_is_ident:
.term.type == "TermTypeIdentity";
# a($args...) -> b($args...)
def _query_func_rename(name): def _query_func_rename(name):
.term.func.name = name; .term.func.name = name;
# $name($args)
def _query_func($name; $args):
{ term: {
type: "TermTypeFunc",
func: {
args: $args,
name: $name
}
}
};
def _query_func($name):
_query_func($name; null);
# . | r def _query_func_name:
def _query_pipe(r): .term.func.name;
def _query_func_args:
.term.func.args;
def _query_is_func:
.term.type == "TermTypeFunc";
def _query_is_func($name):
_query_is_func and _query_func_name == $name;
def _query_empty:
_query_func("empty");
# l | r
def _query_pipe(l; r):
{ op: "|", { op: "|",
left: ., left: l,
right: r right: r
}; };
# . -> [.]
def _query_array:
( . as $q
| { term: {
type: "TermTypeArray",
array: {}
}
}
| if $q then .term.array.query = $q end
);
# {} -> {}
def _query_object:
{ term: {
object: {
key_vals:
( to_entries
| map(
{
key: .key,
val: {
queries: [.value]
}
}
)
)
},
type: "TermTypeObject"
}
};
# l,r
def _query_comma(l; r):
{ left: l,
op: ",",
right: r
};
# [1,2,3] -> 1,2,3
# output each query in array
def _query_commas:
if length == 0 then _query_empty
else
reduce .[1:][] as $q (
.[0];
_query_comma(.; $q)
)
end;
# . -> .[] # . -> .[]
def _query_iter: def _query_iter:
.term.suffix_list = [{iter: true}]; .term.suffix_list = [{iter: true}];
def _query_ident: # try b catch c
{term: {type: "TermTypeIdentity"}}; def _query_try(b; c):
def _query_try(f):
{ term: { { term: {
type: "TermTypeTry",
try: { try: {
body: f, body: b,
}, catch: c
type: "TermTypeTry" }
} }
}; };
def _query_try(b):
def _query_func($name; $args): _query_try(b; null);
{ term: {
func: {
args: $args,
name: $name
},
type: "TermTypeFunc"
}
};
def _query_func($name):
_query_func($name; null);
def _query_is_func(name):
.term.func.name == name;
# last query in pipeline # last query in pipeline
def _query_pipe_last: def _query_pipe_last:
@ -162,14 +240,9 @@ def _query_completion(f):
| if . then | if . then
( .query |= ( .query |=
( _query_func("map"; [ ( _query_func("map"; [
_query_pipe( _query_pipe(.; _query_try(_query_func($c | f)))
_query_try(
_query_func($c | f)
)
)
]) ])
| _query_pipe( | _query_pipe(.; _query_func("add")
_query_func("add")
) )
| .meta = $meta | .meta = $meta
| .imports = $imports | .imports = $imports
@ -185,32 +258,21 @@ def _query_completion(f):
catch {type: "error", name: "", error: .} catch {type: "error", name: "", error: .}
); );
# <filter...> | <slurp_func> -> # query ast to ast of quey itself, used by query rewrite/slurp
# map(<filter...> | .) | (<slurp_func> | f) def _query_toquery:
def _query_slurp_wrap(f): ( tojson
# save and move directives to new root query | _query_fromstring
( . as {$meta, $imports} );
# query rewrite helper, takes care of from/to and directives
def _query_fromtostring(f):
( _query_fromstring
# save and move directives to possible new root query
| . as {$meta, $imports}
| del(.meta) | del(.meta)
| del(.imports) | del(.imports)
| _query_pipe_last as $lq | f
| _query_transform_pipe_last(_query_ident) as $pipe
| _query_func("map"; [$pipe])
| _query_pipe($lq | f)
| .meta = $meta | .meta = $meta
| .imports = $imports | .imports = $imports
);
# filter -> .[] | filter
def _query_iter_wrap:
( . as $q
| _query_ident
| _query_iter
| _query_pipe($q)
);
# query rewrite helper, takes care of from/to
def _query_fromto(f):
( _query_fromstring
| f
| _query_tostring | _query_tostring
); );

View File

@ -21,25 +21,4 @@ include "query";
.[0] | _query_fromstring | _query_pipe_last | _query_tostring .[0] | _query_fromstring | _query_pipe_last | _query_tostring
) )
) )
,
([
["", "map(.) | ."],
[".", "map(.) | ."],
["a", "map(.) | a"],
["1, 2", "map(.) | 1, 2"],
["1 | 2", "map(1 | .) | 2"],
["1 | 2 | 3", "map(1 | 2 | .) | 3"],
["(1 | 2) | 3", "map((1 | 2) | .) | 3"],
["1 | (2 | 3)", "map(1 | .) | (2 | 3)"],
["1 as $_ | 2", "map(1 as $_ | .) | 2"],
["def f: 1; 1", "map(.) | def f: 1; 1"],
["def f: 1; 1 | 2", "map(def f: 1; 1 | .) | 2"],
["module {a:1};\ninclude \"a\";\n1", "module { a: 1 };\ninclude \"a\";\nmap(.) | 1"],
empty
][] | assert(
"\(.) | _query_slurp_wrap";
.[1];
.[0] | _query_fromstring | _query_slurp_wrap(.) | _query_tostring
)
)
) )

View File

@ -1,10 +1,11 @@
include "internal"; include "internal";
include "options"; include "options";
include "eval";
include "query"; include "query";
include "decode"; include "decode";
include "funcs"; include "funcs";
# TODO: currently only make sense to allow keywords start start a term or directive # TODO: currently only make sense to allow keywords starting a term or directive
def _complete_keywords: def _complete_keywords:
[ [
"and", "and",
@ -46,7 +47,7 @@ def _complete($line; $cursor_pos):
def _is_separator: . as $c | " .;[]()|=" | contains($c); def _is_separator: . as $c | " .;[]()|=" | contains($c);
def _is_internal: startswith("_") or startswith("$_"); def _is_internal: startswith("_") or startswith("$_");
def _query_index_or_key($q): def _query_index_or_key($q):
( ([.[] | eval($q) | type]) as $n ( ([.[] | _eval($q) | type]) as $n
| if ($n | all(. == "object")) then "." | if ($n | all(. == "object")) then "."
elif ($n | all(. == "array")) then "[]" elif ($n | all(. == "array")) then "[]"
else null else null
@ -75,7 +76,7 @@ def _complete($line; $cursor_pos):
) )
else else
( $c ( $c
| eval($query) | _eval($query)
| ($prefix | _is_internal) as $prefix_is_internal | ($prefix | _is_internal) as $prefix_is_internal
| map( | map(
select( select(
@ -156,33 +157,56 @@ def _prompt:
, _values , _values
] | join(" ") + "> "; ] | join(" ") + "> ";
# _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 # user expr error
def _repl_display_opts: options({depth: 1}); def _repl_on_expr_error:
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 ( if _eval_is_compile_error then _eval_compile_error_tostring
# was interrupted by user, just ignore else tostring
elif _is_context_canceled_error then empty
end end
| (_error_str | println) | _error_str
| println
); );
def _repl_on_compile_error: _repl_on_error; # other expr error, should not happen
def _repl_eval($expr): def _repl_on_error:
( _repl_display_opts as $opts halt_error(_exit_code_expr_error);
| _eval( # compile error
$expr; def _repl_on_compile_error:
""; ( if .error | _eval_is_compile_error then
_repl_display($opts); ( # TODO: move, redo as: def _symbols: if unicode then {...} else {...} end?
_repl_on_error; def _arrow_up: if options.unicode then "⬆" else "^" end;
_repl_on_compile_error if .error.column != 0 then
) ( ((.input | _prompt | length) + .error.column-1) as $pos
| " " * $pos + "\(_arrow_up) \(.error.error)"
)
else
( .error
| _eval_compile_error_tostring
| _error_str
)
end
)
# was interrupted by user, just ignore
elif .error | _is_context_canceled_error then empty
else .error | _error_str
end
| println
); );
def _repl_eval($expr; on_error; on_compile_error; $slurps):
eval(
$expr;
{ slurps: $slurps,
input_query: (_query_ident | _query_iter), # .[]
catch_query: _query_func("_repl_on_expr_error")
};
on_error;
on_compile_error
);
def _repl_eval($expr; on_error; on_compile_error):
_repl_eval($expr; on_error; on_compile_error; null);
# run read-eval-print-loop # run read-eval-print-loop
# input is array of inputs to iterate # input is array of inputs to iterate
def _repl($opts): #:: a|(Opts) => @ def _repl($opts):
def _read_expr: def _read_expr:
_repeat_break( _repeat_break(
# both _prompt and _complete want input arrays # both _prompt and _complete want input arrays
@ -198,18 +222,17 @@ def _repl($opts): #:: a|(Opts) => @
); );
def _repl_loop: def _repl_loop:
try try
_repl_eval( ( _display_default_opts as $default_opts
( _read_expr | _repl_eval(
| _query_fromto( _read_expr;
if _query_pipe_last | _query_is_func("repl") then _repl_on_error;
# "... | repl" -> "map(... | .) | _repl_slurp" _repl_on_compile_error;
_query_slurp_wrap(_query_func_rename("_repl_slurp")) { repl: "_repl_slurp",
else help: "_help_slurp",
# "..." to -> ".[] | ..." slurp: "_slurp"
_query_iter_wrap }
end
)
) )
| display($default_opts)
) )
catch catch
if . == "interrupt" then empty if . == "interrupt" then empty
@ -217,7 +240,9 @@ def _repl($opts): #:: a|(Opts) => @
elif _eval_is_compile_error then _repl_on_error elif _eval_is_compile_error then _repl_on_error
else error else error
end; end;
if _is_completing | not then if $opts | type != "object" then
error("options must be an object")
elif _is_completing | not then
( _options_stack(. + [$opts]) as $_ ( _options_stack(. + [$opts]) as $_
| _finally( | _finally(
_repeat_break(_repl_loop); _repeat_break(_repl_loop);
@ -227,22 +252,65 @@ def _repl($opts): #:: a|(Opts) => @
else empty else empty
end; end;
def _repl_slurp($opts): _repl($opts); def _repl_slurp_eval($query):
def _repl_slurp: _repl({}); try
[ eval(
$query | _query_tostring;
{};
_repl_on_expr_error;
error
)
]
catch
error(.error);
# TODO: introspect and show doc, reflection somehow? def _repl_slurp($query):
def help: if ($query.slurp_args | length) > 1 then
( "Type expression to evaluate" _eval_error("compile"; "repl requires none or one options argument. ex: ... | repl or ... | repl({compact: true})")
, "\\t Completion" else
, "Up/Down History" # only allow one output for args, multiple would be confusing i think (would start multiples repl:s)
, "^C Interrupt execution" ( ( if ($query.slurp_args | length) > 0 then
, "... | repl Start a new REPL" first(_repl_slurp_eval($query.slurp_args[0])[])
, "^D Exit REPL" else {}
) | println; end
) as $opts
| if $opts | type != "object" then
_eval_error("compile"; "options must be an object")
end
| _repl_slurp_eval($query.rewrite)
| _repl($opts)
)
end;
# just gives error, call appearing last will be renamed to _repl_slurp # just gives error, call appearing last will be renamed to _repl_slurp
def repl($_): def repl($_): error("repl must be last in pipeline. ex: ... | repl");
if options.repl then error("repl must be last")
else error("repl can only be used from interactive repl")
end;
def repl: repl(null); def repl: repl(null);
def _slurp($query):
if ($query.slurp_args | length != 1) then
_eval_error("compile"; "slurp requires one string argument. ex: ... | slurp(\"name\")")
else
# TODO: allow only one output?
( _repl_slurp_eval($query.slurp_args[0])[] as $name
| if ($name | _is_ident | not) then
_eval_error("compile"; "invalid slurp name \"\($name)\", must be a valid identifier. ex: ... | slurp(\"name\")")
else
( _repl_slurp_eval($query.rewrite) as $v
| _slurps(.[$name] |= $v)
| empty
)
end
)
end;
def slurp($_): error("slurp must be last in pipeline. ex: ... | slurp(\"name\")");
def slurp: slurp(null);
def spew($name):
( _slurps[$name]
| if . then .[]
else error("no such slurp: \($name)")
end
);
def spew:
_slurps;

View File

@ -28,11 +28,11 @@ null> ^D
$ fq -n --raw-file filea /nonexisting $ fq -n --raw-file filea /nonexisting
exitcode: 2 exitcode: 2
stderr: stderr:
error: open testdata/nonexisting: no such file or directory error: /nonexisting: no such file or directory
$ fq -n --decode-file filea /nonexisting $ fq -n --decode-file filea /nonexisting
exitcode: 2 exitcode: 2
stderr: stderr:
error: --decode-file filea: open testdata/nonexisting: no such file or directory error: --decode-file filea: no such file or directory
$ fq -n -d mp4 --decode-file filea /test.mp3 '$filea' $ fq -n -d mp4 --decode-file filea /test.mp3 '$filea'
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: /test.mp3 (mp4) |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: /test.mp3 (mp4)
| | | error: mp4: error at position 0x8: no styp, ftyp, free or moov box found | | | error: mp4: error at position 0x8: no styp, ftyp, free or moov box found

View File

@ -9,11 +9,11 @@ hex
hexdump hexdump
null> _is_ide\t null> _is_ide\t
_is_ident _is_ident
null> {aa: 123} | var("test") null> {aa: 123} | slurp("test")
null> $\t null> $\t
$ENV $ENV
$test $test
null> $test.a\t null> $test[].a\t
aa aa
null> {bb: 123} as $aa | $aa.b\t null> {bb: 123} as $aa | $aa.b\t
bb bb

View File

@ -23,4 +23,4 @@ error: arg:1:2: unexpected token <EOF>
$ fq . non-existing $ fq . non-existing
exitcode: 2 exitcode: 2
stderr: stderr:
error: open testdata/non-existing: no such file or directory error: non-existing: no such file or directory

View File

@ -16,3 +16,7 @@ $ fq -nf /err.jq
exitcode: 3 exitcode: 3
stderr: stderr:
error: /err.jq:1:6: unexpected token ")" error: /err.jq:1:6: unexpected token ")"
$ fq -n -f missing
exitcode: 2
stderr:
error: missing: no such file or directory

43
pkg/interp/testdata/help.fqtest vendored Normal file
View File

@ -0,0 +1,43 @@
$ fq -ni
null> help
Type expression to evaluate
\t Completion
Up/Down History
^C Interrupt execution
... | repl Start a new REPL
^D Exit REPL
null> help | abc
error: expr: function not defined: abc/0
null> help | 1
error: help must be alone or last in pipeline. ex: help(length) or ... | help
null> abc | help
error: expr: function not defined: abc/0
null> "a"+1 | help
error: cannot add: string ("a") and number (1)
"value help"
[]
null> help(length)
length: Length of string, array, object, etc
- For string number of unicode codepoints
- For array number of elements in array
- For object number of key-value pairs
- For null zero
- For number the number itself
- For boolean is an error
Examples:
> [1,2,3] | length
3
> "abc" | length
3
> {"a":1,"b":2} | length
2
> null | length
0
> 123 | length
123
> true | length
error: length cannot be applied to: boolean (true)
null> help(1;2)
error: expr: help must be last in pipeline. ex: help(length) or ... | help
null> ^D

View File

@ -18,7 +18,7 @@ $ fq -d raw '(.,input,input,input) | try todescription catch .' /a /b /c
"/c" "/c"
exitcode: 5 exitcode: 5
stderr: stderr:
error: break error: /c: break
$ fq -d raw -n '(.,inputs) | try todescription catch .' /a /b /c $ fq -d raw -n '(.,inputs) | try todescription catch .' /a /b /c
"expected decode value but got: null (null)" "expected decode value but got: null (null)"
"/a" "/a"
@ -40,7 +40,7 @@ $ fq -d raw -n '(input,input,input,input) | todescription' /a /b /c
"/c" "/c"
exitcode: 5 exitcode: 5
stderr: stderr:
error: break error: /c: break
$ fq -d raw input_filename $ fq -d raw input_filename
"<stdin>" "<stdin>"
stdin: stdin:
@ -54,7 +54,7 @@ $ fq -d raw input_filename /a /non-existing /c
"/c" "/c"
exitcode: 2 exitcode: 2
stderr: stderr:
error: open testdata/non-existing: no such file or directory error: /non-existing: no such file or directory
$ fq -d raw '(' /a /b /c $ fq -d raw '(' /a /b /c
exitcode: 3 exitcode: 3
stderr: stderr:
@ -66,9 +66,9 @@ error: arg: function not defined: bla/0
$ fq -d raw '1+"a"' /a /b /c $ fq -d raw '1+"a"' /a /b /c
exitcode: 5 exitcode: 5
stderr: stderr:
error: cannot add: number (1) and string ("a") error: /a: cannot add: number (1) and string ("a")
error: cannot add: number (1) and string ("a") error: /b: cannot add: number (1) and string ("a")
error: cannot add: number (1) and string ("a") error: /c: cannot add: number (1) and string ("a")
$ fq -s -d raw '[.[] | todescription]' /a /b /c $ fq -s -d raw '[.[] | todescription]' /a /b /c
[ [
"/a", "/a",

View File

@ -79,7 +79,7 @@ $ fq -n -o expr=123
$ fq -o expr_file=test.jq -n options.expr_file $ fq -o expr_file=test.jq -n options.expr_file
exitcode: 2 exitcode: 2
stderr: stderr:
error: open testdata/test.jq: no such file or directory error: test.jq: no such file or directory
$ fq -o 'filenames=["/test.mp3"]' format $ fq -o 'filenames=["/test.mp3"]' format
"mp3" "mp3"
$ fq -o 'force=true' -n options.force $ fq -o 'force=true' -n options.force

View File

@ -5,11 +5,20 @@ null
null> 1+1 null> 1+1
2 2
null> ( null> (
error: expr:1:2: unexpected token <EOF> ^ unexpected token <EOF>
null> )
^ unexpected token ")"
null> abc null> abc
error: expr: function not defined: abc/0 error: expr: function not defined: abc/0
null> 1+"a" null> 1+"a"
error: cannot add: number (1) and string ("a") error: cannot add: number (1) and string ("a")
null> abc | repl
error: expr: function not defined: abc/0
null> "a"+1 | repl
error: cannot add: string ("a") and number (1)
> empty> ^D
null> repl | 1
error: repl must be last in pipeline. ex: ... | repl
null> 1 | repl null> 1 | repl
> number> .+1 > number> .+1
2 2
@ -24,6 +33,11 @@ null> 1,2,3 | repl
2 2
3 3
> number, ...[0:3][]> ^D > number, ...[0:3][]> ^D
null> 1,error("err"),3 | repl
error: err
> number> .
1
> number> ^D
null> (1 | raw | .unknown0), 1 | repl null> (1 | raw | .unknown0), 1 | repl
> .unknown0 string, ...[0:2][]> ^D > .unknown0 string, ...[0:2][]> ^D
null> def f: 1; f,f | repl null> def f: 1; f,f | repl
@ -45,6 +59,16 @@ null> [1] | repl
1 1
] ]
> [number]> ^D > [number]> ^D
null> 1,2,error("err"),3 | repl
error: err
> number, ...[0:2][]> .
1
2
> number, ...[0:2][]> ^D
null> repl(123)
error: expr: options must be an object
null> repl(123; 123)
error: expr: repl requires none or one options argument. ex: ... | repl or ... | repl({compact: true})
null> [] | repl null> [] | repl
> []> ^D > []> ^D
null> ^D null> ^D
@ -56,6 +80,12 @@ number, ...[0:3][]> .*2
2 2
4 4
6 6
number, ...[0:3][]> .*2 | repl
> number, ...[0:3][]> .
2
4
6
> number, ...[0:3][]> ^D
number, ...[0:3][]> ^D number, ...[0:3][]> ^D
$ fq -i '[1,2,3]' $ fq -i '[1,2,3]'
[number, ...][0:3]> repl({compact: true}) [number, ...][0:3]> repl({compact: true})
@ -74,6 +104,6 @@ json!> ^D
$ fq -i -n '"[]" | json' $ fq -i -n '"[]" | json'
json> ^D json> ^D
$ fq -n repl $ fq -n repl
exitcode: 5 exitcode: 3
stderr: stderr:
error: repl can only be used from interactive repl error: arg: repl can only be used from interactive repl

106
pkg/interp/testdata/slurp.fqtest vendored Normal file
View File

@ -0,0 +1,106 @@
$ fq -ni
null> abc | slurp
error: expr: slurp requires one string argument. ex: ... | slurp("name")
null> slurp | 1
error: slurp must be last in pipeline. ex: ... | slurp("name")
null> abc | slurp("a")
error: expr: function not defined: abc/0
null> "a"+1 | slurp("a")
error: cannot add: string ("a") and number (1)
null> 123 | slurp("a")
null> 123, "bb" | slurp("bb")
null> 123, 456, error("err"), "bb" | slurp("err")
error: err
null> spew
{
"a": [
123
],
"bb": [
123,
"bb"
],
"err": [
123,
456
]
}
null> spew("bb")
123
"bb"
null> $a
[
123
]
null> "aa" | slurp("a")
null> spew
{
"a": [
"aa"
],
"bb": [
123,
"bb"
],
"err": [
123,
456
]
}
null> $a
[
"aa"
]
null> . | repl
> null> $bb
[
123,
"bb"
]
> null> ^D
null> 123 | slurp("a b")
error: expr: invalid slurp name "a b", must be a valid identifier. ex: ... | slurp("name")
null> 123 | slurp(null)
error: expr: invalid slurp name "null", must be a valid identifier. ex: ... | slurp("name")
null> ^D
$ fq -i
null> 1,2,3 | repl
> number, ...[0:3][]> ., .*2 | slurp("b")
> number, ...[0:3][]> if . == 2 then error("err") end | slurp("c")
error: err
> number, ...[0:3][]> ^D
null> spew
{
"b": [
1,
2,
2,
4,
3,
6
],
"c": [
1,
3
]
}
null> ^D
$ fq -d mp3 -i . /test.mp3
mp3> .frames[0] | slurp("f")
mp3> $f[]
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.frames[0]{}: (mp3_frame)
0x20| ff fb 40| ..@| header{}:
0x30|c0 |. |
0x30| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| ...............| side_info{}:
0x40|00 00 |.. |
0x40| 49 6e 66 6f 00 00 00 0f 00 00 00 02 00 00| Info..........| xing{}: (xing)
0x50|02 57 00 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6|.W..............|
* |until 0xdd.7 (156) | |
0xd0| 00 00| ..| padding: raw bits
0xe0|00 00 00 |... |
| | | crc_calculated: "827a" (raw bits)
mp3> ^D
$ fq -n slurp
exitcode: 3
stderr:
error: arg: slurp can only be used from interactive repl

View File

@ -57,9 +57,9 @@ c
$ fq -R . missing $ fq -R . missing
exitcode: 2 exitcode: 2
stderr: stderr:
error: open testdata/missing: no such file or directory error: missing: no such file or directory
$ fq -Rs . missing $ fq -Rs . missing
"" ""
exitcode: 2 exitcode: 2
stderr: stderr:
error: open testdata/missing: no such file or directory error: missing: no such file or directory

View File

@ -1,52 +0,0 @@
$ fq -ni
null> 123 | var("a")
null> "bb" | var("bb")
null> var
{
"a": 123,
"bb": "bb"
}
null> $a
123
null> "aa" | var("a")
null> var
{
"a": "aa",
"bb": "bb"
}
null> $a
"aa"
null> var("a"; empty)
null> $a
error: expr: variable not defined: $a
null> var
{
"bb": "bb"
}
null> . | repl
> null> $bb
"bb"
> null> ^D
null> var("bb"; empty)
null> var
{}
null> 123 | var("a b")
error: invalid variable name: a b
null> 123 | var(null)
error: invalid variable name: null
null> ^D
$ fq -d mp3 -i . /test.mp3
mp3> .frames[0] | var("f")
mp3> $f
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.frames[0]{}: (mp3_frame)
0x20| ff fb 40| ..@| header{}:
0x30|c0 |. |
0x30| 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00| ...............| side_info{}:
0x40|00 00 |.. |
0x40| 49 6e 66 6f 00 00 00 0f 00 00 00 02 00 00| Info..........| xing{}: (xing)
0x50|02 57 00 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6 a6|.W..............|
* |until 0xdd.7 (156) | |
0xd0| 00 00| ..| padding: raw bits
0xe0|00 00 00 |... |
| | | crc_calculated: "827a" (raw bits)
mp3> ^D