1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
include "internal";
include "options";
include "eval";
include "query";
include "decode";
include "interp";
include "funcs";
include "ansi";
# TODO: currently only make sense to allow keywords starting a term or directive
def _complete_keywords:
[ "and"
#"as"
#"break"
#"catch"
, "def"
#"elif"
#"else"
#"end"
, "false"
, "foreach"
, "if"
, "import"
, "include"
, "label"
, "module"
, "null"
, "or"
, "reduce"
#"then"
, "true"
, "try"
];
def _complete_scope:
[ (scope | map(split("/")[0]) | unique)
, _complete_keywords
] | add;
def _complete_keys:
# uses try as []? will not catch errors
[try keys[] catch empty, try _extkeys[] catch empty];
# TODO: "def zzz: null; abc" scope won't see zzz after completion rewrite as def will be inside map(def zzz: null; abc | ...)
# TODO: handle variables via ast walk?
# TODO: refactor this
# TODO: completionMode
# TODO: return escaped identifier, not sure current readline implementation supports
# modifying "previous" characters if quoting is needed
# completions that needs to change previous input, ex: .a\t -> ."a \" b" etc
def _complete($line; $cursor_pos):
# TODO: reverse this? word or non-ident char?
def _is_separator: . as $c | " .;[]()|=" | contains($c);
def _is_internal: startswith("_") or startswith("$_");
def _query_index_or_key($q):
( ([.[] | _eval($q; {}) | type]) as $n
| if ($n | all(. == "object")) then "."
elif ($n | all(. == "array")) then "[]"
else null
end
);
# only complete if at end or there is a whitespace for now
if ($line[$cursor_pos] | . == null or _is_separator) then
( . as $c
| $line[0:$cursor_pos]
| . as $line_query
# expr -> map(partial-expr | . | f?) | add
# TODO: move map/add logic to here?
| _query_completion(
if .type | . == "func" or . == "var" then "_complete_scope"
elif .type == "index" then "_complete_keys"
else error("unreachable")
end
) as {$type, $query, $prefix}
| { prefix: $prefix
, names: (
if $type == "none" then
( $c
| _query_index_or_key($line_query)
| if . then [.] else [] end
)
else
( $c
| _eval($query; {})
| ($prefix | _is_internal) as $prefix_is_internal
| map(
select(
strings and
# TODO: var type really needed? just func?
(_is_ident or $type == "var") and
((_is_internal | not) or $prefix_is_internal) and
startswith($prefix)
)
)
| unique
| sort
| if length == 1 and .[0] == $prefix then
( $c
| _query_index_or_key($line_query)
| if . then [$prefix+.] else [$prefix] end
)
end
)
end
)
}
)
else
{prefix: "", names: []}
end;
def _complete($line): _complete($line; $line | length);
# empty input []
# >* empty>
# single input [v]
# >* VALUE_PATH VALUE_PREVIEW>
# multiple inputs [v,...]
# >* VALUE_PATH VALUE_PREVIEW, ...[#]>
# single/multi inputs where first input is array [[v,...], ...]
# >* [VALUE_PATH VALUE_PREVIEW, ...][#], ...[#]>
def _prompt($opts):
def _repl_level:
(_options_stack | length | if . > 2 then ((.-2) * ">") else empty end);
def _value_path:
(._path? // []) | if . == [] then empty else _path_to_expr($opts) end;
def _value_preview($depth):
if $depth == 0 and format == null and _is_array then
[ "["
, if length == 0 then empty
else
( (.[0] | _value_preview(1))
, if length > 1 then ", ..." else empty end
)
end
, "]"
, if length > 1 then
( ("[" | _ansi_if($opts; "array"))
, ("0" | _ansi_if($opts; "number"))
, ":"
, (length | tostring | _ansi_if($opts; "number"))
, ("]" | _ansi_if($opts; "array"))
)
else empty
end
] | join("")
else
( . as $c
| format
| if . != null then
( .
+ if $c._error then "!" else "" end
)
else
( $c
| if _is_decode_value then type
else (_exttype // type)
end
)
end
) | _ansi_if($opts; "prompt_value")
end;
def _value:
[ _value_path
, _value_preview(0)
] | join(" ");
def _values:
if length == 0 then "empty"
else
[ (.[0] | _value)
, if length > 1 then
( ", ..."
, ("[" | _ansi_if($opts; "array"))
, ("0" | _ansi_if($opts; "number"))
, ":"
, (length | tostring | _ansi_if($opts; "number"))
, ("]" | _ansi_if($opts; "array"))
, "[]"
)
else empty
end
] | join("")
end;
[ (_repl_level | _ansi_if($opts; "prompt_repl_level")) , _values
] | join(" ") + "> ";
def _prompt: _prompt(null);
# user expr error
def _repl_on_expr_error:
( if _eval_is_compile_error then _eval_compile_error_tostring
else tostring
end
| _error_str
| println
);
# other expr error, interrupted or something unexpected happened
def _repl_on_error:
# was interrupted by user, just ignore
if .error | _is_context_canceled_error then empty
else halt_error(_exit_code_expr_error)
end;
# compile error
def _repl_on_compile_error:
( if .error | _eval_is_compile_error then
( # TODO: move, redo as: def _symbols: if unicode then {...} else {...} end?
def _arrow_up: if options.unicode then "⬆" else "^" end;
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
)
else .error | _error_str
end
| println
);
def _repl_display:
display(_display_default_opts);
def _repl_eval($expr; on_error; on_compile_error):
eval(
$expr;
{ slurps:
{ repl: "_repl_slurp"
, help: "_help_slurp"
, slurp: "_slurp"
}
# input to repl is always array of values to iterate
, input_query: (_query_ident | _query_iter) # .[]
# each input should be evaluated separately like cli file args, so catch and just print errors
, catch_query: _query_func("_repl_on_expr_error")
# run display in sub eval so it can be interrupted
, output_query: _query_func("_repl_display")
};
on_error;
on_compile_error
);
# run read-eval-print-loop
# input is array of inputs to iterate
def _repl($opts):
def _read_expr:
_repeat_break(
# both _prompt and _complete want input arrays
( _readline(
{ prompt: _prompt(options($opts))
, complete: "_complete"
, timeout: options.completion_timeout
}
)
| if trim == "" then empty
else (., error("break"))
end
)
);
def _repl_loop:
try
_repl_eval(
_read_expr;
_repl_on_error;
_repl_on_compile_error
)
catch
if . == "interrupt" then empty
elif . == "eof" then error("break")
elif _eval_is_compile_error then _repl_on_error
else error
end;
if $opts | type != "object" then
error("options must be an object")
elif _is_completing | not then
( _options_stack(. + [$opts]) as $_
| _finally(
_repeat_break(_repl_loop);
_options_stack(.[:-1])
)
)
else empty
end;
def _repl_slurp_eval($query):
try
[ eval(
$query | _query_tostring;
{};
_repl_on_expr_error;
error
)
]
catch
error(.error);
def _repl_slurp($query):
if ($query.slurp_args | length) > 1 then
_eval_error("compile"; "repl requires none or one options argument. ex: ... | repl or ... | repl({compact: true})")
else
# only allow one output for args, multiple would be confusing i think (would start multiples repl:s)
( ( if ($query.slurp_args | length) > 0 then
first(_repl_slurp_eval($query.slurp_args[0])[])
else {}
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
def repl($_): error("repl must be last in pipeline. ex: ... | repl");
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;
|