Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $ ./jqjq '.+. | map(.+105) | implode' <<< '[1,8]'
$ ./jqjq "eval($(jq -Rs . jqjq.jq)+.)" <<< '"eval(\"def f: 1,8; [f,f] | map(.+105) | implode\")"'
"jqjq"

# jqjq have a REPL
# jqjq has a REPL
$ ./jqjq --repl
> 1,2,3 | .*2
2
Expand Down Expand Up @@ -263,7 +263,7 @@ jqjq has the common lex, parse, eval design.

Lexer gets a string and chews off parts from left to right producing an array of tokens `[{<name>: ...}, ...]`. Each chew is done by testing regex:s in a priority order to make sure to match longer prefixes first, ex: `+=` is matched before `+`. For a match a lambda is evaluated, usually `{<token-name>: .}`, but in some cases like for quoted strings it is a bit more complicated.

The lexer also has a stack to keep track of balance of seen `(`, `)` and `\(` to properly know how to chop of a string with interpolation into tokens. e.g. is `)` a right paratheses or contuination of a string as in `"abc \(123) def"`?
The lexer also has a stack to keep track of balance of seen `(`, `)` and `\(` to properly know how to chop of a string with interpolation into tokens. e.g. is `)` a right parenthesis or continuation of a string as in `"abc \(123) def"`?

You can use `./jqjq --lex '...'` to lex and see the tokens.

Expand All @@ -290,8 +290,8 @@ When evaluating the AST eval function get the current AST node, path and environ
- "," operator in jq (and gojq) is left associate but for the way jqjq parses it creates the correct parse tree when it's right associate. Don't know why.
- Suffix with multiple `[]` outputs values in wrong order.
- String literal using interpolation that has more than one generator outputs in wrong order. Ex: `"\(1,2) \(3,4)"`.
- Non-associate operators like `==` should fail, ex: `1 == 2 == 3`.
- Object are parsed differently compared to gojq. gojq has a list of pipe queries, jqjq will only have one that might be pipe op.
- Non-associative operators like `==` should fail, ex: `1 == 2 == 3`.
- Objects are parsed differently compared to gojq. gojq has a list of pipe queries, jqjq will only have one that might be pipe op.
- Less "passthrough" piggyback on jq features:
- `reduce/foreach` via recursive function? similar to `if` or `{}`-literal?
- `try/catch` via some backtrack return value? change `[path, value]` to include an error somehow?
Expand Down
117 changes: 62 additions & 55 deletions jqjq.jq
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ def parse:
// error("parse error: \(.)")
);

def _tojson($opts):
def _tojson_stream($opts):
# see jq jv_print.c:jv_dump_term for the reference color printing logic
def _c_null: 0;
def _c_false: 1;
Expand All @@ -1159,12 +1159,14 @@ def _tojson($opts):
def _c_field: 7;
def _color($id):
if $opts.colors != null then
"\u001b[\($opts.colors[$id])m" + . + "\u001b[0m"
$opts.colors[$id], ., "\u001b[0m"
else . end;
def _f($opts; $indent):
def _r($prefix):
( ($opts.indent // 0) as $indent
| (if $indent > 0 then ["\n", " "] else ["", ""] end) as [$newline, $space]
| ($indent * " ") as $indent
| def _f($prefix):
( type as $t
| if $t == "null" then tojson | _color(_c_null)
| if $t == "null" then "null" | _color(_c_null)
elif $t == "string" then tojson | _color(_c_string)
elif $t == "number" then tojson | _color(_c_number)
elif $t == "boolean" then
Expand All @@ -1174,53 +1176,54 @@ def _tojson($opts):
elif $t == "array" then
if length == 0 then "[]" | _color(_c_array)
else
[ ("[" | _color(_c_array)), $opts.newline
, ( [ .[]
| $prefix, $indent
, _r($prefix+$indent)
, ("," | _color(_c_array)), $opts.newline
]
| .[0:-2]
( ($prefix + $indent) as $elem_prefix
| ("[" | _color(_c_array))
, $elem_prefix, (.[0] | _f($elem_prefix))
, ( .[1:][]
| ("," | _color(_c_array))
, $elem_prefix, _f($elem_prefix)
)
, $opts.newline
, $prefix, ("]" | _color(_c_array))
]
)
end
elif $t == "object" then
if length == 0 then "{}" | _color(_c_object)
else
[ ("{" | _color(_c_object)), $opts.newline
, ( [ to_entries[]
| $prefix, $indent
, (.key | tojson | _color(_c_field))
, (":" | _color(_c_object)), $opts.space
, (.value | _r($prefix+$indent))
, ("," | _color(_c_object)), $opts.newline
]
| .[0:-2]
( ($prefix + $indent) as $elem_prefix
| to_entries as $entries
| ("{" | _color(_c_object))
, ( $entries[0]
| $elem_prefix
, (.key | tojson | _color(_c_field))
, (":" | _color(_c_object)), $space
, (.value | _f($elem_prefix))
)
, ( $entries[1:][]
| ("," | _color(_c_object))
, $elem_prefix
, (.key | tojson | _color(_c_field))
, (":" | _color(_c_object)), $space
, (.value | _f($elem_prefix))
)
, $opts.newline
, $prefix, ("}" | _color(_c_object))
]
)
end
else _internal_error("unknown type \($t)")
end
);
_r("");
( ( { indent: 0
, newline: ""
, space: ""
} + $opts
| if .indent > 0 then
( .newline = "\n"
| .space = " "
)
_f($newline + $indent)
);
def _tojson: [_tojson_stream({})] | join("");

def dump($opts):
( if $opts.raw_output and type == "string" then
if $opts.raw_output0 and contains("\u0000") then
error("Cannot dump a string containing NUL with --raw-output0 option")
end
) as $o
| _f($o; $o.indent * " ")
| if type == "array" then flatten | join("") end
else _tojson_stream($opts)
end
, if $opts.stream_sep != null then $opts.stream_sep else empty end
);
def _tojson: _tojson({});

def undefined_func_error:
error("undefined function \(.name)");
Expand Down Expand Up @@ -2301,7 +2304,7 @@ def isempty(f): [limit(1; f)] == [];

# Assuming the input array is sorted, bsearch/1 returns
# the index of the target if the target is in the input array; and otherwise
# (-1 - ix), where ix is the insertion point that would leave the array sorted.
# (-1 - ix), where ix is the insertion point that would leave the array sorted.
# If the input is not sorted, bsearch will terminate but with irrelevant results.
def bsearch($target):
if length == 0 then -1
Expand Down Expand Up @@ -2535,37 +2538,48 @@ def jqjq($args; $env):
(($opts.color_output | not) and ($env.NO_COLOR | . != null and . != "")) then
null
end
| if . != null then map("\u001b[\(.)m") end
);

def _parse_opts($opts; $env):
( $opts
+ { colors: _parse_colors($opts; $env)
, indent: (if $opts.compact_output then 0 else 2 end)
, stream_sep: (
( if $opts.raw_no_lf then "" else "\n" end
| if $opts.raw_output0 then . + "\u0000" end
)
)
}
);

def _repl:
def _repl($opts):
def _repeat_break(f):
try repeat(f)
catch
if . == "break" then empty
else error
end;
( _parse_opts({}; $env) as $opts
| _builtins_env as $builtins_env
( _parse_opts($opts; $env) as $opts
| ( if $opts.no_builtins then {}
else _builtins_env
end
) as $builtins_env
| _repeat_break(
( "> "
, ( try input
catch error("break")
| . as $expr
| null
| try
( eval($expr; {"$ENV": $env}; $builtins_env)
| _tojson($opts)
, "\n"
(
( eval($expr; {"$ENV": $env}; $builtins_env)
| dump($opts)
)
, if $opts.raw_no_lf then "\n" else empty end
)
catch
("error: \(.)\n")
"error: \(.)\n"
)
)
)
Expand Down Expand Up @@ -2737,14 +2751,7 @@ def jqjq($args; $env):
) as $builtins_env
| _inputs
| eval($opts.filter; {"$ENV": $env}; $builtins_env)
| if $opts.raw_output and type == "string" then
if $opts.raw_output0 and contains("\u0000") then
error("Cannot dump a string containing NUL with --raw-output0 option")
end
else _tojson($opts)
end
| if $opts.raw_no_lf | not then ., "\n" end
| if $opts.raw_output0 then ., "\u0000" end
| dump($opts)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_filter/1 looks quite neat now!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just like how it was before color support! Just with dump($opts) instead of tojson haha

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha hehe, re-neated, good!

);

def _parse_args:
Expand Down Expand Up @@ -2815,7 +2822,7 @@ def jqjq($args; $env):
| if $opts.help then _help
elif $opts.lex then $opts.filter | lex, "\n"
elif $opts.parse then $opts.filter | lex | parse, "\n"
elif $opts.repl then _repl
elif $opts.repl then _repl($opts)
elif $opts.run_tests then input | _run_tests
else
_filter($opts)
Expand Down