p1: /v2/scrape, /v1/fetch, auth, CLI, contract tests
Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
/gojq
|
||||
/goxz
|
||||
/CREDITS
|
||||
/y.output
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
*.md
|
||||
*.y
|
||||
**/*.jq
|
||||
**/*.json
|
||||
**/*.yaml
|
||||
**/*_test.go
|
||||
.github
|
||||
_gojq
|
||||
_tools
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
**/testdata/** binary
|
||||
/builtin.go eol=lf linguist-generated=true
|
||||
/parser.go eol=lf linguist-generated=true
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/gojq
|
||||
/goxz
|
||||
/CREDITS
|
||||
/y.output
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
# Changelog
|
||||
## [v0.12.19](https://github.com/itchyny/gojq/compare/v0.12.18..v0.12.19) (2026-04-01)
|
||||
* fix `gsub` and `sub` when the replacement emits multiple values
|
||||
* fix `fmax`, `fmin`, `modf` functions against NaN and infinities
|
||||
* fix `join/1` to use `add/0` implementation and handle null separator
|
||||
* fix `del` and `delpaths` on null to emit null
|
||||
* fix arithmetic operations on the minimum integer
|
||||
* fix `significand` function against subnormal numbers
|
||||
* fix handling of `--` in cli flag parsing for jq compatibility
|
||||
* fix `flatten/1` to emit error when the depth is NaN
|
||||
* fix array slice update to validate index types
|
||||
* fix string repetition boundary check to match jq behavior
|
||||
* implement `splits/2` using `match/2` for better jq compatibility
|
||||
* implement `to_entries` and `from_entries` in jq for simplicity
|
||||
* improve performance of regexp functions by caching compiled regexps
|
||||
|
||||
## [v0.12.18](https://github.com/itchyny/gojq/compare/v0.12.17..v0.12.18) (2025-12-02)
|
||||
* implement `trimstr/1`, `toboolean/0` function
|
||||
* fix `last/1` to be included in `builtins/0`
|
||||
* fix `--indent 0` to preserve newlines
|
||||
* fix string repetition to emit error when the result is too large
|
||||
* increase the array index limit to 536870912 (`2^29`)
|
||||
* stop numeric normalization for concurrent execution (see 1ace748d08df)
|
||||
* support binding expressions with binary operators (`1 + 2 as $x | -$x`)
|
||||
* improve `gojq.NewIter` to be a generic function
|
||||
* improve logic for getting file contents on JSON parse error
|
||||
* improve JSON parsing to preserve the precision of floating-point numbers
|
||||
* improve YAML parsing performance and preserve the precision of large integers
|
||||
* improve performance and reduce memory allocation of long-running queries
|
||||
|
||||
## [v0.12.17](https://github.com/itchyny/gojq/compare/v0.12.16..v0.12.17) (2024-12-01)
|
||||
* implement `add/1`, `skip/2` functions
|
||||
* implement `--library-path` option as the alias of `-L` option
|
||||
* fix `reduce` syntax to emit results for each initial value
|
||||
* fix `last/1` to yield no values when the argument yields no values
|
||||
* fix `limit/2` to emit an error on negative count
|
||||
* fix `@uri` and `@urid` formats not to convert space between plus sign
|
||||
* fix resolving search paths of import statements in the query
|
||||
* improve time functions to accept fewer element arrays
|
||||
|
||||
## [v0.12.16](https://github.com/itchyny/gojq/compare/v0.12.15..v0.12.16) (2024-06-01)
|
||||
* fix offset of query parsing error on multi-byte characters
|
||||
* fix tests of `exp10` and `atan2` failing on some platforms
|
||||
* fix `debug/1` to be available only when `debug/0` is defined
|
||||
* improve parser to allow binary operators as object values
|
||||
* improve compiler to emit error if query is missing
|
||||
|
||||
## [v0.12.15](https://github.com/itchyny/gojq/compare/v0.12.14..v0.12.15) (2024-04-01)
|
||||
* implement `ltrim`, `rtrim`, and `trim` functions
|
||||
* implement `gojq.ParseError` for getting the offset and token of query parsing error
|
||||
* implement `gojq.HaltError` for detecting halt errors and stopping outer iteration
|
||||
* fix object construction with duplicate keys (`{x:0,y:1} | {a:.x,a:.y}`)
|
||||
* fix `halt` and `halt_error` functions to stop the command execution immediately
|
||||
* fix variable scope of binding syntax (`"a" as $v | def f: $v; "b" as $v | f`)
|
||||
* fix pre-defined variables to be available in initial modules (`$ARGS` in `~/.jq`)
|
||||
* fix `ltrimstr` and `rtrimstr` functions to emit error on non-string input
|
||||
* fix `nearbyint` and `rint` functions to round ties to even
|
||||
* improve parser to allow `reduce`, `foreach`, `if`, `try`-`catch` syntax as object values
|
||||
* remove `pow10` in favor of `exp10`, define `scalbn` and `scalbln` by `ldexp`
|
||||
|
||||
## [v0.12.14](https://github.com/itchyny/gojq/compare/v0.12.13..v0.12.14) (2023-12-01)
|
||||
* implement `abs`, `pick`, and `debug/1` functions
|
||||
* implement `--raw-output0` option, and remove `--nul-output` (`-0`) option
|
||||
* fix string multiplication by zero to emit an empty string
|
||||
* fix zero divided by zero to emit an error, not `nan`
|
||||
* fix modulo operator to emit `nan` if either side is `nan`
|
||||
* fix `implode` function to emit replacement characters on invalid code points
|
||||
* fix `stderr` function to output strings in raw format
|
||||
* fix `error` function to throw an error even for `null`
|
||||
* fix `walk` function on multiple outputs arguments
|
||||
* fix `--from-file` option to work with `--args` and `--jsonargs` options
|
||||
* fix the default module search path `../lib` relative to the executable
|
||||
* improve query parser to support comment continuation with backslash
|
||||
* improve `modulemeta` function to include defined function names in the module
|
||||
* improve search path of `import` and `include` directives to support `$ORIGIN` expansion
|
||||
* remove deprecated `leaf_paths` function
|
||||
|
||||
## [v0.12.13](https://github.com/itchyny/gojq/compare/v0.12.12..v0.12.13) (2023-06-01)
|
||||
* implement `@urid` format string to decode URI values
|
||||
* fix functions returning arrays not to emit nil slices (`flatten`, `group_by`,
|
||||
`unique`, `unique_by`, `nth`, `indices`, `path`, and `modulemeta.deps`)
|
||||
|
||||
## [v0.12.12](https://github.com/itchyny/gojq/compare/v0.12.11..v0.12.12) (2023-03-01)
|
||||
* fix assignment operator (`=`) with overlapping paths and multiple values (`[[]] | .. = ..`)
|
||||
* fix crash on multiplying large numbers to an empty string (`9223372036854775807 * ""`)
|
||||
* improve zsh completion file
|
||||
|
||||
## [v0.12.11](https://github.com/itchyny/gojq/compare/v0.12.10..v0.12.11) (2022-12-24)
|
||||
* fix crash on assignment operator (`=`) with multiple values (`. = (0,0)`)
|
||||
* fix `isnormal` and `normals` functions against subnormal numbers
|
||||
|
||||
## [v0.12.10](https://github.com/itchyny/gojq/compare/v0.12.9..v0.12.10) (2022-12-01)
|
||||
* fix `break` in `try`-`catch` query (`label $x | try break $x catch .`)
|
||||
* fix path value validation for `getpath` function (`path(getpath([[0]][0]))`)
|
||||
* fix path value validation for custom iterator functions
|
||||
* fix `walk` function with argument emitting multiple values (`[1],{x:1} | walk(.,0)`)
|
||||
* fix `@csv`, `@tsv`, `@sh` to escape the null character (`["\u0000"] | @csv,@tsv,@sh`)
|
||||
* improve performance of assignment operator (`=`), update-assignment operator (`|=`),
|
||||
`map_values`, `del`, `delpaths`, `walk`, `ascii_downcase`, and `ascii_upcase` functions
|
||||
|
||||
## [v0.12.9](https://github.com/itchyny/gojq/compare/v0.12.8..v0.12.9) (2022-09-01)
|
||||
* fix `fromjson` to emit error on unexpected trailing string
|
||||
* fix path analyzer on variable argument evaluation (`def f($x): .y; path(f(.x))`)
|
||||
* fix raw input option `--raw-input` (`-R`) to keep carriage returns and support 64KiB+ lines
|
||||
|
||||
## [v0.12.8](https://github.com/itchyny/gojq/compare/v0.12.7..v0.12.8) (2022-06-01)
|
||||
* implement `gojq.Compare` for comparing values in custom internal functions
|
||||
* implement `gojq.TypeOf` for obtaining type name of values in custom internal functions
|
||||
* implement `gojq.Preview` for previewing values for error messages of custom internal functions
|
||||
* fix query lexer to parse string literals as JSON to support surrogate pairs (`"\ud83d\ude04"`)
|
||||
* fix priority bug of declared and builtin functions (`def empty: .; null | select(.)`)
|
||||
* fix string indexing by index out of bounds to emit `null` (`"abc" | .[3]`)
|
||||
* fix array binding pattern not to match against strings (`"abc" as [$a] ?// $a | $a`)
|
||||
* fix `sub` and `gsub` functions to emit results in the same order of jq
|
||||
* fix `fromjson` to keep integer precision (`"10000000000000000" | fromjson + 1`)
|
||||
* fix stream option to raise error against incomplete JSON input
|
||||
* improve array updating index and string repetition to increase limitations
|
||||
* improve `mktime` to support nanoseconds, just like `gmtime` and `now`
|
||||
* improve query lexer to report unterminated string literals
|
||||
* improve performance of string indexing and slicing by reducing allocations
|
||||
* improve performance of object and array indexing, slicing, and iteration,
|
||||
by validating path values by comparing data addresses. This change improves jq
|
||||
compatibility of path value validation (`{} | {}.x = 0`, `[0] | [.[]][] = 1`).
|
||||
Also optimize constant indexing and slicing by specialized instruction
|
||||
* improve performance of `add` (on array of strings), `flatten`, `min`, `max`,
|
||||
`sort`, `unique`, `join`, `to_entries`, `from_entries`, `indices`, `index`,
|
||||
`rindex`, `startswith`, `endswith`, `ltrimstr`, `rtrimstr`, `explode`,
|
||||
`capture`, `sub`, and `gsub` functions
|
||||
|
||||
## [v0.12.7](https://github.com/itchyny/gojq/compare/v0.12.6..v0.12.7) (2022-03-01)
|
||||
* fix precedence of try expression against operators (`try 0 * error(0)`)
|
||||
* fix iterator suffix with optional operator (`0 | .x[]?`)
|
||||
* fix stream option with slurp option or `input`, `inputs` functions
|
||||
* fix the command flag parser to support equal sign in short options with argument
|
||||
* fix string conversion of query including empty strings in module and import metadata
|
||||
* improve performance of `isempty` function
|
||||
|
||||
## [v0.12.6](https://github.com/itchyny/gojq/compare/v0.12.5..v0.12.6) (2021-12-01)
|
||||
* implement options for consuming remaining arguments (`--args`, `--jsonargs`, `$ARGS.positional`)
|
||||
* fix `delpaths` function with overlapped paths
|
||||
* fix `--exit-status` flag with `halt`, `halt_error` functions
|
||||
* fix `input_filename` function with null input option
|
||||
* fix path value validation for `nan`
|
||||
* fix crash on branch optimization (`if 0 then . else 0|0 end`)
|
||||
* add validation on regular expression flags to reject unsupported ones
|
||||
* improve performance of `range`, `join`, `flatten` functions
|
||||
* improve constant value optimization for object with quoted keys
|
||||
* remove dependency on forked `go-flags` package
|
||||
|
||||
## [v0.12.5](https://github.com/itchyny/gojq/compare/v0.12.4..v0.12.5) (2021-09-01)
|
||||
* implement `input_filename` function for the command
|
||||
* fix priority bug of declared functions and arguments (`def g: 1; def f(g): g; f(2)`)
|
||||
* fix label handling to catch the correct break error (`first((0, 0) | first(0))`)
|
||||
* fix `null|error` and `error(null)` to behave like `empty` (`null | [0, error, error(null), 1]`)
|
||||
* fix integer division to keep precision when divisible (`1 / 1 * 1000000000000000000000`)
|
||||
* fix modulo operator on negative number and large number (`(-1) % 10000000000`)
|
||||
* fix combination of slurp (`--slurp`) and raw input option (`--raw-input`) to keep newlines
|
||||
* change the default module paths to `~/.jq`, `$ORIGIN/../lib/gojq`, `$ORIGIN/lib`
|
||||
where `$ORIGIN` is the directory where the executable is located in
|
||||
* improve command argument parser to recognize query with leading hyphen,
|
||||
allow hyphen for standard input, and force posix style on Windows
|
||||
* improve `@base64d` to allow input without padding characters
|
||||
* improve `fromdate`, `fromdateiso8601` to parse date time strings with timezone offset
|
||||
* improve `halt_error` to print error values without prefix
|
||||
* improve `sub`, `gsub` to allow the replacement string emitting multiple values
|
||||
* improve encoding `\b` and `\f` in strings
|
||||
* improve module loader for search path in query, and absolute path
|
||||
* improve query lexer to support string literal including newlines
|
||||
* improve performance of `index`, `rindex`, `indices`, `transpose`, and `walk` functions
|
||||
* improve performance of value preview in errors and debug mode
|
||||
* improve runtime performance including tail call optimization
|
||||
* switch Docker base image to `distroless/static:debug`
|
||||
|
||||
## [v0.12.4](https://github.com/itchyny/gojq/compare/v0.12.3..v0.12.4) (2021-06-01)
|
||||
* fix numeric conversion of large floating-point numbers in modulo operator
|
||||
* implement a compiler option for adding custom iterator functions
|
||||
* implement `gojq.NewIter` function for creating a new iterator from values
|
||||
* implement `$ARGS.named` for listing command line variables
|
||||
* remove `debug` and `stderr` functions from the library
|
||||
* stop printing newlines on `stderr` function for jq compatibility
|
||||
|
||||
## [v0.12.3](https://github.com/itchyny/gojq/compare/v0.12.2..v0.12.3) (2021-04-01)
|
||||
* fix array slicing with infinities and large numbers (`[0][-infinite:infinite], [0][:1e20]`)
|
||||
* fix multiplying strings and modulo by infinities on MIPS 64 architecture
|
||||
* fix git revision information in Docker images
|
||||
* release multi-platform Docker images for ARM 64
|
||||
* switch to `distroless` image for Docker base image
|
||||
|
||||
## [v0.12.2](https://github.com/itchyny/gojq/compare/v0.12.1..v0.12.2) (2021-03-01)
|
||||
* implement `GOJQ_COLORS` environment variable to configure individual colors
|
||||
* respect `--color-output` (`-C`) option even if `NO_COLOR` is set
|
||||
* implement `gojq.ValueError` interface for custom internal functions
|
||||
* fix crash on timestamps in YAML input
|
||||
* fix calculation on `infinite` (`infinite-infinite | isnan`)
|
||||
* fix comparison on `nan` (`nan < nan`)
|
||||
* fix validation of `implode` (`[-1] | implode`)
|
||||
* fix number normalization for custom JSON module loader
|
||||
* print error line numbers on invalid JSON and YAML
|
||||
* improve `strftime`, `strptime` for time zone offsets
|
||||
* improve performance on reading a large JSON file given by command line argument
|
||||
* improve performance and reduce memory allocation of the lexer, compiler and executor
|
||||
|
||||
## [v0.12.1](https://github.com/itchyny/gojq/compare/v0.12.0..v0.12.1) (2021-01-17)
|
||||
* skip adding `$HOME/.jq` to module paths when `$HOME` is unset
|
||||
* fix optional operator followed by division operator (`1?/1`)
|
||||
* fix undefined format followed by optional operator (`@foo?`)
|
||||
* fix parsing invalid consecutive dots while scanning a number (`0..[empty]`)
|
||||
* fix panic on printing a query with `%#v`
|
||||
* improve performance and reduce memory allocation of `query.String()`
|
||||
* change all methods of `ModuleLoader` optional
|
||||
|
||||
## [v0.12.0](https://github.com/itchyny/gojq/compare/v0.11.2..v0.12.0) (2020-12-24)
|
||||
* implement tab indentation option (`--tab`)
|
||||
* implement a compiler option for adding custom internal functions
|
||||
* implement `gojq.Marshal` function for jq-flavored encoding
|
||||
* fix slurp option with JSON file arguments
|
||||
* fix escaping characters in object keys
|
||||
* fix normalizing negative `int64` to `int` on 32-bit architecture
|
||||
* fix crash on continuing iteration after emitting an error
|
||||
* `iter.Next()` does not normalize `NaN` and infinities anymore. Library users
|
||||
should take care of them. To handle them for encoding as JSON bytes, use
|
||||
`gojq.Marshal`. Also, `iter.Next()` does not clone values deeply anymore for
|
||||
performance reason. Users must not update the elements of the returned arrays
|
||||
and objects
|
||||
* improve performance of outputting JSON values by about 3.5 times
|
||||
|
||||
## [v0.11.2](https://github.com/itchyny/gojq/compare/v0.11.1..v0.11.2) (2020-10-01)
|
||||
* fix build for 32bit architecture
|
||||
* release to [GitHub Container Registry](https://github.com/users/itchyny/packages/container/package/gojq)
|
||||
|
||||
## [v0.11.1](https://github.com/itchyny/gojq/compare/v0.11.0..v0.11.1) (2020-08-22)
|
||||
* improve compatibility of `strftime`, `strptime` functions with jq
|
||||
* fix YAML input with numbers in keys
|
||||
* fix crash on multiplying a large number or `infinite` to a string
|
||||
* fix crash on error while slicing a string (`""[:{}]`)
|
||||
* fix crash on modulo by a number near 0.0 (`1 % 0.1`)
|
||||
* include `CREDITS` file in artifacts
|
||||
|
||||
## [v0.11.0](https://github.com/itchyny/gojq/compare/v0.10.4..v0.11.0) (2020-07-08)
|
||||
* improve parsing performance significantly
|
||||
* rewrite the parser from `participle` library to `goyacc` generated parser
|
||||
* release to [itchyny/gojq - Docker Hub](https://hub.docker.com/r/itchyny/gojq)
|
||||
* support string interpolation for object pattern key
|
||||
|
||||
## [v0.10.4](https://github.com/itchyny/gojq/compare/v0.10.3..v0.10.4) (2020-06-30)
|
||||
* implement variable in object key (`. as $x | { $x: 1 }`)
|
||||
* fix modify operator (`|=`) with `try` `catch` expression
|
||||
* fix optional operator (`?`) with alternative operator (`//`) in `map_values` function
|
||||
* fix normalizing numeric types for library users
|
||||
* export `gojq.NewModuleLoader` function for library users
|
||||
|
||||
## [v0.10.3](https://github.com/itchyny/gojq/compare/v0.10.2..v0.10.3) (2020-06-06)
|
||||
* implement `add`, `unique_by`, `max_by`, `min_by`, `reverse` by internal
|
||||
functions for performance and reducing the binary size
|
||||
* improve performance of `setpath`, `delpaths` functions
|
||||
* fix assignment against nested slicing (`[1,2,3] | .[1:][:1] = [5]`)
|
||||
* limit the array index of assignment operator
|
||||
* optimize constant arrays and objects
|
||||
|
||||
## [v0.10.2](https://github.com/itchyny/gojq/compare/v0.10.1..v0.10.2) (2020-05-24)
|
||||
* implement `sort_by`, `group_by`, `bsearch` by internal functions for performance
|
||||
and reducing the binary size
|
||||
* fix object construction and constant object to allow trailing commas
|
||||
* fix `tonumber` function to allow leading zeros
|
||||
* minify the builtin functions to reduce the binary size
|
||||
|
||||
## [v0.10.1](https://github.com/itchyny/gojq/compare/v0.10.0..v0.10.1) (2020-04-24)
|
||||
* fix array addition not to modify the left hand side
|
||||
|
||||
## [v0.10.0](https://github.com/itchyny/gojq/compare/v0.9.0..v0.10.0) (2020-04-02)
|
||||
* implement various functions (`format`, `significand`, `modulemeta`, `halt_error`)
|
||||
* implement `input`, `inputs` functions
|
||||
* implement stream option (`--stream`)
|
||||
* implement slicing with object (`.[{"start": 1, "end": 2}]`)
|
||||
* implement `NO_COLOR` environment variable support
|
||||
* implement `nul` output option (`-0`, `--nul-output`)
|
||||
* implement exit status option (`-e`, `--exit-status`)
|
||||
* implement `search` field of module meta object
|
||||
* implement combination of `--yaml-input` and `--slurp`
|
||||
* improve string token lexer and support nested string interpolation
|
||||
* improve the exit code for jq compatibility
|
||||
* improve default module search paths for jq compatibility
|
||||
* improve documentation for the usage as a library
|
||||
* change methods of `ModuleLoader` optional, implement `LoadModuleWithMeta` and `LoadJSONWithMeta`
|
||||
* fix number normalization for JSON arguments (`--argjson`, `--slurpfile`)
|
||||
* fix `0/0` and `infinite/infinite`
|
||||
* fix `error` function against `null`
|
||||
|
||||
## [v0.9.0](https://github.com/itchyny/gojq/compare/v0.8.0..v0.9.0) (2020-03-15)
|
||||
* implement various functions (`infinite`, `isfinite`, `isinfinite`, `finites`, `isnormal`, `normals`)
|
||||
* implement environment variables loader as a compiler option
|
||||
* implement `$NAME::NAME` syntax for imported JSON variable
|
||||
* fix modify operator with empty against array (`[range(9)] | (.[] | select(. % 2 > 0)) |= empty`)
|
||||
* fix variable and function scopes (`{ x: 1 } | . as $x | (.x as $x | $x) | ., $x`)
|
||||
* fix path analyzer
|
||||
* fix type check in `startswith` and `endswith`
|
||||
* ignore type error of `ltrimstr` and `rtrimstr`
|
||||
* remove nano seconds from `mktime` output
|
||||
* trim newline at the end of error messages
|
||||
* improve documents and examples
|
||||
|
||||
## [v0.8.0](https://github.com/itchyny/gojq/compare/v0.7.0..v0.8.0) (2020-03-02)
|
||||
* implement format strings (`@text`, `@json`, `@html`, `@uri`, `@csv`, `@tsv`,
|
||||
`@sh`, `@base64`, `@base64d`)
|
||||
* implement modules feature (`-L` option for directory to search modules from)
|
||||
* implement options for binding variables from arguments (`--arg`, `--argjson`)
|
||||
* implement options for binding variables from files (`--slurpfile`, `--rawfile`)
|
||||
* implement an option for indentation count (`--indent`)
|
||||
* fix `isnan` for `null`
|
||||
* fix path analyzer
|
||||
* fix error after optional operator (`1? | .x`)
|
||||
* add `$ENV` variable
|
||||
* add zsh completion file
|
||||
|
||||
## [v0.7.0](https://github.com/itchyny/gojq/compare/v0.6.0..v0.7.0) (2019-12-22)
|
||||
* implement YAML input (`--yaml-input`) and output (`--yaml-output`)
|
||||
* fix pipe in object value
|
||||
* fix precedence of `if`, `try`, `reduce` and `foreach` expressions
|
||||
* release from GitHub Actions
|
||||
|
||||
## [v0.6.0](https://github.com/itchyny/gojq/compare/v0.5.0..v0.6.0) (2019-08-26)
|
||||
* implement arbitrary-precision integer calculation
|
||||
* implement various functions (`repeat`, `pow10`, `nan`, `isnan`, `nearbyint`,
|
||||
`halt`, `INDEX`, `JOIN`, `IN`)
|
||||
* implement long options (`--compact-output`, `--raw-output`, `--join-output`,
|
||||
`--color-output`, `--monochrome-output`, `--null-input`, `--raw-input`,
|
||||
`--slurp`, `--from-file`, `--version`)
|
||||
* implement join output options (`-j`, `--join-output`)
|
||||
* implement color/monochrome output options (`-C`, `--color-output`,
|
||||
`-M`, `--monochrome-output`)
|
||||
* refactor builtin functions
|
||||
|
||||
## [v0.5.0](https://github.com/itchyny/gojq/compare/v0.4.0..v0.5.0) (2019-08-03)
|
||||
* implement various functions (`with_entries`, `from_entries`, `leaf_paths`,
|
||||
`contains`, `inside`, `split`, `stream`, `fromstream`, `truncate_stream`,
|
||||
`bsearch`, `path`, `paths`, `map_values`, `del`, `delpaths`, `getpath`,
|
||||
`gmtime`, `localtime`, `mktime`, `strftime`, `strflocaltime`, `strptime`,
|
||||
`todate`, `fromdate`, `now`, `match`, `test`, `capture`, `scan`, `splits`,
|
||||
`sub`, `gsub`, `debug`, `stderr`)
|
||||
* implement assignment operator (`=`)
|
||||
* implement modify operator (`|=`)
|
||||
* implement update operators (`+=`, `-=`, `*=`, `/=`, `%=`, `//=`)
|
||||
* implement destructuring alternative operator (`?//`)
|
||||
* allow function declaration inside query
|
||||
* implement `-f` flag for loading query from file
|
||||
* improve error message for parsing multiple line query
|
||||
|
||||
## [v0.4.0](https://github.com/itchyny/gojq/compare/v0.3.0..v0.4.0) (2019-07-20)
|
||||
* improve performance significantly
|
||||
* rewrite from recursive interpreter to stack machine based interpreter
|
||||
* allow debugging with `make install-debug` and `export GOJQ_DEBUG=1`
|
||||
* parse built-in functions and generate syntax trees before compilation
|
||||
* optimize tail recursion
|
||||
* fix behavior of optional operator
|
||||
* fix scopes of arguments of recursive function call
|
||||
* fix duplicate function argument names
|
||||
* implement `setpath` function
|
||||
|
||||
## [v0.3.0](https://github.com/itchyny/gojq/compare/v0.2.0..v0.3.0) (2019-06-05)
|
||||
* implement `reduce`, `foreach`, `label`, `break` syntax
|
||||
* improve binding variable syntax to bind to an object or an array
|
||||
* implement string interpolation
|
||||
* implement object index by string (`."example"`)
|
||||
* implement various functions (`add`, `flatten`, `min`, `min_by`, `max`,
|
||||
`max_by`, `sort`, `sort_by`, `group_by`, `unique`, `unique_by`, `tostring`,
|
||||
`indices`, `index`, `rindex`, `walk`, `transpose`, `first`, `last`, `nth`,
|
||||
`limit`, `all`, `any`, `isempty`, `error`, `builtins`, `env`)
|
||||
* implement math functions (`sin`, `cos`, `tan`, `asin`, `acos`, `atan`,
|
||||
`sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `floor`, `round`,
|
||||
`rint`, `ceil`, `trunc`, `fabs`, `sqrt`, `cbrt`, `exp`, `exp10`, `exp2`,
|
||||
`expm1`, `frexp`, `modf`, `log`, `log10`, `log1p`, `log2`, `logb`,
|
||||
`gamma`, `tgamma`, `lgamma`, `erf`, `erfc`, `j0`, `j1`, `y0`, `y1`,
|
||||
`atan2/2`, `copysign/2`, `drem/2`, `fdim/2`, `fmax/2`, `fmin/2`, `fmod/2`,
|
||||
`hypot/2`, `jn/2`, `ldexp/2`, `nextafter/2`, `nexttoward/2`, `remainder/2`,
|
||||
`scalb/2`, `scalbln/2`, `pow/2`, `yn/2`, `fma/3`)
|
||||
* support object construction with variables
|
||||
* support indexing against strings
|
||||
* fix function evaluation for recursive call
|
||||
* fix error handling of `//` operator
|
||||
* fix string representation of NaN and Inf
|
||||
* implement `-R` flag for reading input as raw strings
|
||||
* implement `-c` flag for compact output
|
||||
* implement `-n` flag for using null as input value
|
||||
* implement `-r` flag for outputting raw string
|
||||
* implement `-s` flag for reading all inputs into an array
|
||||
|
||||
## [v0.2.0](https://github.com/itchyny/gojq/compare/v0.1.0..v0.2.0) (2019-05-06)
|
||||
* implement binding variable syntax (`... as $var`)
|
||||
* implement `try` `catch` syntax
|
||||
* implement alternative operator (`//`)
|
||||
* implement various functions (`in`, `to_entries`, `startswith`, `endswith`,
|
||||
`ltrimstr`, `rtrimstr`, `combinations`, `ascii_downcase`, `ascii_upcase`,
|
||||
`tojson`, `fromjson`)
|
||||
* support query for object indexing
|
||||
* support object construction with variables
|
||||
* support indexing against strings
|
||||
|
||||
## [v0.1.0](https://github.com/itchyny/gojq/compare/v0.0.1..v0.1.0) (2019-05-02)
|
||||
* implement binary operators (`+`, `-`, `*`, `/`, `%`, `==`, `!=`, `>`, `<`,
|
||||
`>=`, `<=`, `and`, `or`)
|
||||
* implement unary operators (`+`, `-`)
|
||||
* implement booleans (`false`, `true`), `null`, number and string constant
|
||||
values
|
||||
* implement `empty` value
|
||||
* implement conditional syntax (`if` `then` `elif` `else` `end`)
|
||||
* implement various functions (`length`, `utf8bytelength`, `not`, `keys`,
|
||||
`has`, `map`, `select`, `recurse`, `while`, `until`, `range`, `tonumber`,
|
||||
`type`, `arrays`, `objects`, `iterables`, `booleans`, `numbers`, `strings`,
|
||||
`nulls`, `values`, `scalars`, `reverse`, `explode`, `implode`, `join`)
|
||||
* support function declaration
|
||||
* support iterators in object keys
|
||||
* support object construction shortcut
|
||||
* support query in array indices
|
||||
* support negative number indexing against arrays
|
||||
* support json file name arguments
|
||||
|
||||
## [v0.0.1](https://github.com/itchyny/gojq/compare/0fa3241..v0.0.1) (2019-04-14)
|
||||
* initial implementation
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
FROM golang:1.26 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY go.* ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=0
|
||||
RUN make build
|
||||
|
||||
FROM gcr.io/distroless/static:debug
|
||||
|
||||
COPY --from=builder /app/gojq /
|
||||
ENTRYPOINT ["/gojq"]
|
||||
CMD ["--help"]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-2026 itchyny
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
BIN := gojq
|
||||
VERSION := $$(make -s show-version)
|
||||
VERSION_PATH := cli
|
||||
CURRENT_REVISION = $(shell git rev-parse --short HEAD)
|
||||
BUILD_LDFLAGS = "-s -w -X github.com/itchyny/$(BIN)/cli.revision=$(CURRENT_REVISION)"
|
||||
GOBIN ?= $(shell go env GOPATH)/bin
|
||||
SHELL := /bin/bash
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) ./cmd/$(BIN)
|
||||
|
||||
.PHONY: build-dev
|
||||
build-dev: parser.go builtin.go
|
||||
go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) ./cmd/$(BIN)
|
||||
|
||||
.PHONY: build-debug
|
||||
build-debug: parser.go builtin.go
|
||||
go build -tags gojq_debug -ldflags=$(BUILD_LDFLAGS) -o $(BIN) ./cmd/$(BIN)
|
||||
|
||||
builtin.go: builtin.jq parser.go.y parser.go query.go operator.go _tools/*
|
||||
GOOS= GOARCH= go generate
|
||||
|
||||
.SUFFIXES:
|
||||
parser.go: parser.go.y $(GOBIN)/goyacc
|
||||
goyacc -o $@ $<
|
||||
|
||||
$(GOBIN)/goyacc:
|
||||
@go install golang.org/x/tools/cmd/goyacc@latest
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install -ldflags=$(BUILD_LDFLAGS) ./cmd/$(BIN)
|
||||
|
||||
.PHONY: install-dev
|
||||
install-dev: parser.go builtin.go
|
||||
go install -ldflags=$(BUILD_LDFLAGS) ./cmd/$(BIN)
|
||||
|
||||
.PHONY: install-debug
|
||||
install-debug: parser.go builtin.go
|
||||
go install -tags gojq_debug -ldflags=$(BUILD_LDFLAGS) ./cmd/$(BIN)
|
||||
|
||||
.PHONY: show-version
|
||||
show-version: $(GOBIN)/gobump
|
||||
@gobump show -r "$(VERSION_PATH)"
|
||||
|
||||
$(GOBIN)/gobump:
|
||||
@go install github.com/x-motemen/gobump/cmd/gobump@latest
|
||||
|
||||
.PHONY: cross
|
||||
cross: $(GOBIN)/goxz CREDITS
|
||||
goxz -n $(BIN) -pv=v$(VERSION) -include _$(BIN) \
|
||||
-build-ldflags=$(BUILD_LDFLAGS) ./cmd/$(BIN)
|
||||
|
||||
$(GOBIN)/goxz:
|
||||
go install github.com/Songmu/goxz/cmd/goxz@latest
|
||||
|
||||
CREDITS: $(GOBIN)/gocredits go.sum
|
||||
go mod tidy
|
||||
gocredits -w .
|
||||
|
||||
$(GOBIN)/gocredits:
|
||||
go install github.com/Songmu/gocredits/cmd/gocredits@latest
|
||||
|
||||
.PHONY: test
|
||||
test: build
|
||||
go test -v -race ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint: $(GOBIN)/staticcheck
|
||||
go vet ./...
|
||||
staticcheck -checks all -tags gojq_debug ./...
|
||||
|
||||
$(GOBIN)/staticcheck:
|
||||
go install honnef.co/go/tools/cmd/staticcheck@v0.6.1
|
||||
|
||||
.PHONY: check-tools
|
||||
check-tools:
|
||||
go run _tools/print_builtin.go
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BIN) goxz CREDITS
|
||||
go clean
|
||||
|
||||
.PHONY: update
|
||||
update: export GOPROXY=direct
|
||||
update:
|
||||
go get -u ./... && go get github.com/itchyny/go-yaml@gojq && go mod tidy
|
||||
rm go.dev.sum && go get -u -modfile=go.dev.mod github.com/itchyny/{astgen,timefmt}-go && go generate
|
||||
|
||||
.PHONY: bump
|
||||
bump: $(GOBIN)/gobump
|
||||
test -z "$$(git status --porcelain || echo .)"
|
||||
test "$$(git branch --show-current)" = "main"
|
||||
@gobump up -w "$(VERSION_PATH)"
|
||||
git commit -am "bump up version to $(VERSION)"
|
||||
git tag "v$(VERSION)"
|
||||
git push --atomic origin main tag "v$(VERSION)"
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
# gojq
|
||||
[](https://github.com/itchyny/gojq/actions?query=branch:main)
|
||||
[](https://goreportcard.com/report/github.com/itchyny/gojq)
|
||||
[](https://github.com/itchyny/gojq/blob/main/LICENSE)
|
||||
[](https://github.com/itchyny/gojq/releases)
|
||||
[](https://pkg.go.dev/github.com/itchyny/gojq)
|
||||
|
||||
### Pure Go implementation of [jq](https://github.com/jqlang/jq)
|
||||
This is an implementation of jq command written in Go language.
|
||||
You can also embed gojq as a library to your Go products.
|
||||
|
||||
## Usage
|
||||
```sh
|
||||
$ echo '{"foo": 128}' | gojq '.foo'
|
||||
128
|
||||
$ echo '{"a": {"b": 42}}' | gojq '.a.b'
|
||||
42
|
||||
$ echo '{"id": "sample", "10": {"b": 42}}' | gojq '{(.id): .["10"].b}'
|
||||
{
|
||||
"sample": 42
|
||||
}
|
||||
$ echo '[{"id":1},{"id":2},{"id":3}]' | gojq '.[] | .id'
|
||||
1
|
||||
2
|
||||
3
|
||||
$ echo '{"a":1,"b":2}' | gojq '.a += 1 | .b *= 2'
|
||||
{
|
||||
"a": 2,
|
||||
"b": 4
|
||||
}
|
||||
$ echo '{"a":1} [2] 3' | gojq '. as {$a} ?// [$a] ?// $a | $a'
|
||||
1
|
||||
2
|
||||
3
|
||||
$ echo '{"foo": 4722366482869645213696}' | gojq .foo
|
||||
4722366482869645213696 # keeps the precision of large numbers
|
||||
$ gojq -n 'def fact($n): if $n < 1 then 1 else $n * fact($n - 1) end; fact(50)'
|
||||
30414093201713378043612608166064768844377641568960512000000000000 # arbitrary-precision integer calculation
|
||||
```
|
||||
|
||||
Nice error messages.
|
||||
```sh
|
||||
$ echo '[1,2,3]' | gojq '.foo & .bar'
|
||||
gojq: invalid query: .foo & .bar
|
||||
.foo & .bar
|
||||
^ unexpected token "&"
|
||||
$ echo '{"foo": { bar: [] } }' | gojq '.'
|
||||
gojq: invalid json: <stdin>
|
||||
{"foo": { bar: [] } }
|
||||
^ invalid character 'b' looking for beginning of object key string
|
||||
```
|
||||
|
||||
## Installation
|
||||
### Homebrew
|
||||
```sh
|
||||
brew install gojq
|
||||
```
|
||||
|
||||
### mise
|
||||
```sh
|
||||
mise use -g gojq@latest
|
||||
```
|
||||
|
||||
### Zero Install
|
||||
```sh
|
||||
0install add gojq https://apps.0install.net/utils/gojq.xml
|
||||
```
|
||||
|
||||
### Build from source
|
||||
```sh
|
||||
go install github.com/itchyny/gojq/cmd/gojq@latest
|
||||
```
|
||||
|
||||
### Docker
|
||||
```sh
|
||||
docker run -i --rm itchyny/gojq
|
||||
docker run -i --rm ghcr.io/itchyny/gojq
|
||||
```
|
||||
|
||||
## Difference to jq
|
||||
- gojq is purely implemented with Go language and is completely portable. jq depends on the C standard library so the availability of math functions depends on the library. jq also depends on the regular expression library and it makes build scripts complex.
|
||||
- gojq does not keep the order of object keys. I understand this might cause problems for some scripts but basically, we should not rely on the order of object keys. Due to this limitation, gojq does not have `keys_unsorted` function and `--sort-keys` (`-S`) option. I would implement when ordered map is implemented in the standard library of Go but I'm less motivated.
|
||||
- gojq supports arbitrary-precision integer calculation while jq does not; jq loses the precision of large integers when calculation is involved. Note that even with gojq, all mathematical functions, including `floor` and `round`, convert integers to floating-point numbers; only addition, subtraction, multiplication, modulo, and division operators (when divisible) keep the integer precision. To calculate floor division of integers without losing the precision, use `def idivide($n): (. - . % $n) / $n;`. To round down floating-point numbers to integers, use `def ifloor: floor | tostring | tonumber;`, but note that this function does not work with large floating-point numbers and also loses the precision of large integers.
|
||||
- gojq behaves differently than jq in some features, expecting jq to fix its behavior in the future. gojq supports string indexing; `"abcde"[2]` ([jq#1520](https://github.com/jqlang/jq/issues/1520)). gojq fixes handling files with no newline characters at the end ([jq#2374](https://github.com/jqlang/jq/issues/2374)). gojq fixes `@base64d` to allow binary string as the decoded string ([jq#1931](https://github.com/jqlang/jq/issues/1931)). gojq improves time formatting and parsing; deals with `%f` in `strftime` and `strptime` ([jq#1409](https://github.com/jqlang/jq/issues/1409)), parses timezone offsets with `fromdate` and `fromdateiso8601` ([jq#1053](https://github.com/jqlang/jq/issues/1053)), supports timezone name/offset with `%Z`/`%z` in `strptime` ([jq#929](https://github.com/jqlang/jq/issues/929), [jq#2195](https://github.com/jqlang/jq/issues/2195)). gojq supports nanoseconds in date and time functions.
|
||||
- gojq does not support some functions intentionally; `get_jq_origin`, `get_prog_origin`, `get_search_list` (unstable, not listed in jq document), `input_line_number`, `$__loc__` (performance issue). gojq does not support some flags; `--ascii-output, -a` (performance issue), `--seq` (not used commonly), `--sort-keys, -S` (sorts by default because `map[string]any` does not keep the order), `--unbuffered` (unbuffered by default). gojq does not parse JSON extensions supported by jq; `NaN`, `Infinity`, and `[000]`. gojq does not support some regular expression metacharacters, backreferences, look-around assertions, and some flags (regular expression engine differences). gojq does not support BOM (`encoding/json` does not support this). gojq disallows using keywords for function names (`def true: .; true` is a confusing query), and module name prefixes in function declarations (using module prefixes like `def m::f: .;` is undocumented).
|
||||
- gojq supports reading from YAML input (`--yaml-input`) while jq does not. gojq also supports YAML output (`--yaml-output`).
|
||||
|
||||
### Color configuration
|
||||
The gojq command automatically disables coloring output when the output is not a tty.
|
||||
To force coloring output, specify `--color-output` (`-C`) option.
|
||||
When [`NO_COLOR` environment variable](https://no-color.org/) is present or `--monochrome-output` (`-M`) option is specified, gojq disables coloring output.
|
||||
|
||||
Use `GOJQ_COLORS` environment variable to configure individual colors.
|
||||
The variable is a colon-separated list of ANSI escape sequences of `null`, `false`, `true`, numbers, strings, object keys, arrays, and objects.
|
||||
The default configuration is `90:33:33:36:32:34;1`.
|
||||
|
||||
## Usage as a library
|
||||
You can use the gojq parser and interpreter from your Go products.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/itchyny/gojq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
query, err := gojq.Parse(".foo | ..")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
input := map[string]any{"foo": []any{1, 2, 3}}
|
||||
iter := query.Run(input) // or query.RunWithContext
|
||||
for {
|
||||
v, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if err, ok := v.(error); ok {
|
||||
if err, ok := err.(*gojq.HaltError); ok && err.Value() == nil {
|
||||
break
|
||||
}
|
||||
log.Fatalln(err)
|
||||
}
|
||||
fmt.Printf("%#v\n", v)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Firstly, use [`gojq.Parse(string) (*Query, error)`](https://pkg.go.dev/github.com/itchyny/gojq#Parse) to get the query from a string.
|
||||
- Use [`gojq.ParseError`](https://pkg.go.dev/github.com/itchyny/gojq#ParseError) to get the error position and token of the parsing error.
|
||||
- Secondly, get the result iterator
|
||||
- using [`query.Run`](https://pkg.go.dev/github.com/itchyny/gojq#Query.Run) or [`query.RunWithContext`](https://pkg.go.dev/github.com/itchyny/gojq#Query.RunWithContext)
|
||||
- or alternatively, compile the query using [`gojq.Compile`](https://pkg.go.dev/github.com/itchyny/gojq#Compile) and then [`code.Run`](https://pkg.go.dev/github.com/itchyny/gojq#Code.Run) or [`code.RunWithContext`](https://pkg.go.dev/github.com/itchyny/gojq#Code.RunWithContext). You can reuse the `*Code` against multiple inputs to avoid compilation of the same query.
|
||||
- In either case, you cannot use custom type values as the query input. The type should be `[]any` for an array and `map[string]any` for a map (just like decoded to an `any` using the [encoding/json](https://golang.org/pkg/encoding/json/) package). You can't use `[]int` or `map[string]string`, for example. If you want to query your custom struct, marshal to JSON, unmarshal to `any` and use it as the query input.
|
||||
- Thirdly, iterate through the results using [`iter.Next() (any, bool)`](https://pkg.go.dev/github.com/itchyny/gojq#Iter). The iterator can emit an error so make sure to handle it. The method returns `true` with results, and `false` when the iterator terminates.
|
||||
- The return type is not `(any, error)` because the iterator may emit multiple errors. The `jq` and `gojq` commands stop the iteration on the first error, but the library user can choose to stop the iteration on errors, or to continue until it terminates.
|
||||
- In any case, it is recommended to stop the iteration on [`gojq.HaltError`](https://pkg.go.dev/github.com/itchyny/gojq#HaltError), which is emitted by `halt` and `halt_error` functions, although these functions are rarely used.
|
||||
The error implements [`gojq.ValueError`](https://pkg.go.dev/github.com/itchyny/gojq#ValueError), and if the error value is `nil`, stop the iteration without handling the error.
|
||||
Technically speaking, we can fix the iterator to terminate on the halting error, but it does not terminate at the moment.
|
||||
The `halt` function in jq not only stops the iteration, but also terminates the command execution, even if there are still input values.
|
||||
So, gojq leaves it up to the library user how to handle the halting error.
|
||||
- Note that the result iterator may emit infinite number of values; `repeat(0)` and `range(infinite)`. It may stuck with no output value; `def f: f; f`. Use `RunWithContext` when you want to limit the execution time.
|
||||
|
||||
[`gojq.Compile`](https://pkg.go.dev/github.com/itchyny/gojq#Compile) allows to configure the following compiler options.
|
||||
|
||||
- [`gojq.WithModuleLoader`](https://pkg.go.dev/github.com/itchyny/gojq#WithModuleLoader) allows to load modules. By default, the module feature is disabled. If you want to load modules from the file system, use [`gojq.NewModuleLoader`](https://pkg.go.dev/github.com/itchyny/gojq#NewModuleLoader).
|
||||
- [`gojq.WithEnvironLoader`](https://pkg.go.dev/github.com/itchyny/gojq#WithEnvironLoader) allows to configure the environment variables referenced by `env` and `$ENV`. By default, OS environment variables are not accessible due to security reasons. You can use `gojq.WithEnvironLoader(os.Environ)` if you want.
|
||||
- [`gojq.WithVariables`](https://pkg.go.dev/github.com/itchyny/gojq#WithVariables) allows to configure the variables which can be used in the query. Pass the values of the variables to [`code.Run`](https://pkg.go.dev/github.com/itchyny/gojq#Code.Run) in the same order.
|
||||
- [`gojq.WithFunction`](https://pkg.go.dev/github.com/itchyny/gojq#WithFunction) allows to add a custom internal function. An internal function can return a single value (which can be an error) each invocation. To add a jq function (which may include a comma operator to emit multiple values, `empty` function, accept a filter for its argument, or call another built-in function), use `LoadInitModules` of the module loader.
|
||||
- [`gojq.WithIterFunction`](https://pkg.go.dev/github.com/itchyny/gojq#WithIterFunction) allows to add a custom iterator function. An iterator function returns an iterator to emit multiple values. You cannot define both iterator and non-iterator functions of the same name (with possibly different arities). You can use [`gojq.NewIter`](https://pkg.go.dev/github.com/itchyny/gojq#NewIter) to convert values or an error to a [`gojq.Iter`](https://pkg.go.dev/github.com/itchyny/gojq#Iter).
|
||||
- [`gojq.WithInputIter`](https://pkg.go.dev/github.com/itchyny/gojq#WithInputIter) allows to use `input` and `inputs` functions. By default, these functions are disabled.
|
||||
|
||||
## Bug Tracker
|
||||
Report bug at [Issues・itchyny/gojq - GitHub](https://github.com/itchyny/gojq/issues).
|
||||
|
||||
## Author
|
||||
itchyny (<https://github.com/itchyny>)
|
||||
|
||||
## License
|
||||
This software is released under the MIT License, see LICENSE.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#compdef gojq
|
||||
|
||||
_gojq()
|
||||
{
|
||||
_arguments -s -S \
|
||||
'(-r --raw-output --raw-output0 -j --join-output)'{-r,--raw-output}'[output raw strings]' \
|
||||
'(-r --raw-output -j --join-output)--raw-output0[implies -r with NUL character delimiter]' \
|
||||
'(-r --raw-output --raw-output0 -j --join-output)'{-j,--join-output}'[implies -r with no newline delimiter]' \
|
||||
'(-c --compact-output --indent --tab --yaml-output)'{-c,--compact-output}'[output without pretty-printing]' \
|
||||
'(-c --compact-output --tab --yaml-output)--indent[number of spaces for indentation]:indentation count:(2 4 8)' \
|
||||
'(-c --compact-output --indent --yaml-output)--tab[use tabs for indentation]' \
|
||||
'(-c --compact-output --indent --tab )--yaml-output[output in YAML format]' \
|
||||
'(-C --color-output -M --monochrome-output)'{-C,--color-output}'[output with colors even if piped]' \
|
||||
'(-C --color-output -M --monochrome-output)'{-M,--monochrome-output}'[output without colors]' \
|
||||
'(-n --null-input)'{-n,--null-input}'[use null as input value]' \
|
||||
'(-R --raw-input --stream --yaml-input)'{-R,--raw-input}'[read input as raw strings]' \
|
||||
'(-R --raw-input --yaml-input)--stream[parse input in stream fashion]' \
|
||||
'(-R --raw-input --stream )--yaml-input[read input as YAML format]' \
|
||||
'(-s --slurp)'{-s,--slurp}'[read all inputs into an array]' \
|
||||
'(-f --from-file 1)'{-f,--from-file}'[load query from file]:filename of jq query:_files' \
|
||||
'*'{-L,--library-path}'[directory to search modules from]:module directory:_directories' \
|
||||
'*--arg[set a string value to a variable]:variable name: :string value' \
|
||||
'*--argjson[set a JSON value to a variable]:variable name: :JSON value' \
|
||||
'*--slurpfile[set the JSON contents of a file to a variable]:variable name: :JSON file:_files' \
|
||||
'*--rawfile[set the contents of a file to a variable]:variable name: :file:_files' \
|
||||
'*--args[consume remaining arguments as positional string values]' \
|
||||
'*--jsonargs[consume remaining arguments as positional JSON values]' \
|
||||
'(-e --exit-status)'{-e,--exit-status}'[exit 1 when the last value is false or null]' \
|
||||
'(- 1 *)'{-v,--version}'[display version information]' \
|
||||
'(- 1 *)'{-h,--help}'[display help information]' \
|
||||
'1: :_guard "^-([[:alpha:]0]#|-*)" "jq query"' \
|
||||
'*: :_gojq_args'
|
||||
}
|
||||
|
||||
_gojq_args() {
|
||||
if (($words[(I)--args] > $words[(I)--jsonargs])); then
|
||||
_message 'string value'
|
||||
elif (($words[(I)--args] < $words[(I)--jsonargs])); then
|
||||
_message 'JSON value'
|
||||
else
|
||||
_arguments '*:input file:_files'
|
||||
fi
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// Code generated by _tools/gen_builtin.go; DO NOT EDIT.
|
||||
|
||||
package gojq
|
||||
|
||||
func init() {
|
||||
builtinFuncDefs = map[string][]*FuncDef{
|
||||
"IN": {{Name: "IN", Args: []string{"s"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "s"}}}, Right: &Query{Term: &Term{Type: TermTypeIdentity}}, Op: OpEq}, {Term: &Term{Type: TermTypeIdentity}}}}}}}, {Name: "IN", Args: []string{"src", "s"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "src"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "s"}}}, Op: OpEq}, {Term: &Term{Type: TermTypeIdentity}}}}}}}},
|
||||
"INDEX": {{Name: "INDEX", Args: []string{"stream", "idx_expr"}, Body: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "stream"}}}, Pattern: &Pattern{Name: "$row"}, Start: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{}}}, Update: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$row"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "idx_expr"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "tostring"}}}, Op: OpPipe}, Op: OpPipe}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$row"}}}, Op: OpAssign}}}}}, {Name: "INDEX", Args: []string{"idx_expr"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "INDEX", Args: []*Query{{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "idx_expr"}}}}}}}}},
|
||||
"JOIN": {{Name: "JOIN", Args: []string{"$idx", "idx_expr"}, Body: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}}, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$idx"}, SuffixList: []*Suffix{{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "idx_expr"}}}}}}}}, Op: OpComma}}}}, Op: OpPipe}}}}}, {Name: "JOIN", Args: []string{"$idx", "stream", "idx_expr"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "stream"}}}, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$idx"}, SuffixList: []*Suffix{{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "idx_expr"}}}}}}}}, Op: OpComma}}}}, Op: OpPipe}}, {Name: "JOIN", Args: []string{"$idx", "stream", "idx_expr", "join_expr"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "stream"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$idx"}, SuffixList: []*Suffix{{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "idx_expr"}}}}}}}}, Op: OpComma}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "join_expr"}}}, Op: OpPipe}, Op: OpPipe}}},
|
||||
"_assign": {},
|
||||
"_last": {},
|
||||
"_modify": {},
|
||||
"add": {{Name: "add", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "add"}}}, Op: OpPipe}}},
|
||||
"all": {{Name: "all", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "all", Args: []*Query{{Term: &Term{Type: TermTypeIdentity}}}}}}}, {Name: "all", Args: []string{"y"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "all", Args: []*Query{{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "y"}}}}}}}}, {Name: "all", Args: []string{"g", "y"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "isempty", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "y"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "not"}}}, Op: OpPipe}}}}}, Op: OpPipe}}}}}}},
|
||||
"any": {{Name: "any", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{{Term: &Term{Type: TermTypeIdentity}}}}}}}, {Name: "any", Args: []string{"y"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "y"}}}}}}}}, {Name: "any", Args: []string{"g", "y"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "isempty", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "y"}}}}}}}, Op: OpPipe}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "not"}}}, Op: OpPipe}}},
|
||||
"arrays": {{Name: "arrays", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}, Op: OpEq}}}}}}},
|
||||
"booleans": {{Name: "booleans", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "boolean"}}}, Op: OpEq}}}}}}},
|
||||
"capture": {{Name: "capture", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "capture", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeNull}}}}}}}, {Name: "capture", Args: []string{"$re", "$flags"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "captures"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_captures"}}}, Op: OpPipe}, Op: OpPipe}}},
|
||||
"combinations": {{Name: "combinations", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "length"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}, Else: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, SuffixList: []*Suffix{{Iter: true}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$x"}}}}}}, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}, IsSlice: true}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "combinations"}}}, Op: OpPipe}}}, Op: OpAdd}, Patterns: []*Pattern{{Name: "$x"}}, Op: OpPipe}}}}}, {Name: "combinations", Args: []string{"n"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "limit", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "n"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "repeat", Args: []*Query{{Term: &Term{Type: TermTypeIdentity}}}}}}}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "combinations"}}}, Op: OpPipe}}},
|
||||
"del": {{Name: "del", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "delpaths", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}}}}}}}}}},
|
||||
"finites": {{Name: "finites", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "isfinite"}}}}}}}}},
|
||||
"first": {{Name: "first", Body: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}}, {Name: "first", Args: []string{"g"}, Body: &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{Ident: "$out", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}, Op: OpComma}, Op: OpPipe}}}}}},
|
||||
"from_entries": {{Name: "from_entries", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{{KeyQuery: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "key"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "Key"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "name"}}}, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "Name"}}}, Op: OpAlt}, Op: OpAlt}, Op: OpAlt}, Val: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "has", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "value"}}}}}}}, Then: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "value"}}}, Else: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "Value"}}}}}}}}}}}}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "add"}}}, Right: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{}}}, Op: OpAlt}, Op: OpPipe}}},
|
||||
"fromdate": {{Name: "fromdate", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "fromdateiso8601"}}}}},
|
||||
"fromdateiso8601": {{Name: "fromdateiso8601", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "strptime", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "%Y-%m-%dT%H:%M:%S%z"}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "mktime"}}}, Op: OpPipe}}},
|
||||
"fromstream": {{Name: "fromstream", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Pattern: &Pattern{Name: "$pv"}, Start: &Query{Term: &Term{Type: TermTypeNull}}, Update: &Query{Left: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "e"}}}, Then: &Query{Term: &Term{Type: TermTypeNull}}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$pv"}}}, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$pv"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "length"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "2"}}, Op: OpEq}, Op: OpPipe}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "v"}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}, Op: OpAdd}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$v"}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "e"}}}}}}, {Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "length"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpEq}, Op: OpPipe}}}}}, Op: OpPipe}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "e"}}}}}}, {Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "length"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}, Op: OpEq}, Op: OpPipe}}}}}}}}, Patterns: []*Pattern{{Array: []*Pattern{{Name: "$p"}, {Name: "$v"}}}}, Op: OpPipe}, Op: OpPipe}, Extract: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "e"}}}, Then: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "v"}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}}}}}}},
|
||||
"group_by": {{Name: "group_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_group_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}}}}}}}}}},
|
||||
"gsub": {{Name: "gsub", Args: []string{"$re", "str"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "sub", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "str"}}}, {Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}}}}}}, {Name: "gsub", Args: []string{"$re", "str", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "sub", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "str"}}}, {Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}, Op: OpAdd}}}}}}},
|
||||
"in": {{Name: "in", Args: []string{"xs"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "xs"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "has", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$x"}}}}}}}, Op: OpPipe}, Patterns: []*Pattern{{Name: "$x"}}, Op: OpPipe}}},
|
||||
"inputs": {{Name: "inputs", Body: &Query{Term: &Term{Type: TermTypeTry, Try: &Try{Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "repeat", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "input"}}}}}}}, Catch: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "break"}}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "error"}}}}}}}}}}},
|
||||
"isempty": {{Name: "isempty", Args: []string{"g"}, Body: &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{Ident: "$out", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFalse}}, Right: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}, Op: OpComma}, Op: OpPipe}}}, Right: &Query{Term: &Term{Type: TermTypeTrue}}, Op: OpComma}}}}}},
|
||||
"iterables": {{Name: "iterables", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Left: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}, Op: OpEq}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}, Op: OpEq}, Op: OpOr}, Op: OpPipe}}}}}}},
|
||||
"last": {{Name: "last", Body: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeUnary, Unary: &Unary{Op: OpSub, Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}}, {Name: "last", Args: []string{"g"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_last", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}}}}}}},
|
||||
"limit": {{Name: "limit", Args: []string{"$n", "g"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpGt}, Then: &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{Ident: "$out", Body: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}, Pattern: &Pattern{Name: "$item"}, Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Update: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}, Op: OpSub}, Extract: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$item"}}}, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpLe}, Then: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}}, Op: OpComma}}}}}}}, Elif: []*IfElif{{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "error", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "limit doesn't support negative count"}}}}}}}}}}}},
|
||||
"map": {{Name: "map", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Op: OpPipe}}}}}},
|
||||
"map_values": {{Name: "map_values", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Op: OpModify}}},
|
||||
"match": {{Name: "match", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeNull}}}}}}}, {Name: "match", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}, {Term: &Term{Type: TermTypeFalse}}}}, SuffixList: []*Suffix{{Iter: true}}}}}},
|
||||
"max_by": {{Name: "max_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_max_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}}}}}}}}}},
|
||||
"min_by": {{Name: "min_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_min_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}}}}}}}}}},
|
||||
"normals": {{Name: "normals", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "isnormal"}}}}}}}}},
|
||||
"not": {{Name: "not", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeIdentity}}, Then: &Query{Term: &Term{Type: TermTypeFalse}}, Else: &Query{Term: &Term{Type: TermTypeTrue}}}}}}},
|
||||
"nth": {{Name: "nth", Args: []string{"$n"}, Body: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}}}}}, {Name: "nth", Args: []string{"$n", "g"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpGe}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "first", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "skip", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}}}}}}}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "error", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "nth doesn't support negative index"}}}}}}}}}}}},
|
||||
"nulls": {{Name: "nulls", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeNull}}, Op: OpEq}}}}}}},
|
||||
"numbers": {{Name: "numbers", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "number"}}}, Op: OpEq}}}}}}},
|
||||
"objects": {{Name: "objects", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}, Op: OpEq}}}}}}},
|
||||
"paths": {{Name: "paths", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{Term: &Term{Type: TermTypeRecurse}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}, Op: OpNe}}}}}, Op: OpPipe}}, {Name: "paths", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeRecurse}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}, Op: OpPipe}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}, Op: OpNe}}}}}, Op: OpPipe}}},
|
||||
"pick": {{Name: "pick", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}, Pattern: &Pattern{Name: "$p"}, Start: &Query{Term: &Term{Type: TermTypeNull}}, Update: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}, {Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$v"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "getpath", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}}}}}, Op: OpPipe}}}}}}}}, Patterns: []*Pattern{{Name: "$v"}}, Op: OpPipe}}},
|
||||
"range": {{Name: "range", Args: []string{"$end"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_range", Args: []*Query{{Term: &Term{Type: TermTypeNumber, Number: "0"}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$end"}}}, {Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}, {Name: "range", Args: []string{"$start", "$end"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_range", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$start"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$end"}}}, {Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}, {Name: "range", Args: []string{"$start", "$end", "$step"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_range", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$start"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$end"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$step"}}}}}}}}},
|
||||
"recurse": {{Name: "recurse", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "recurse", Args: []*Query{{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}, {Optional: true}}}}}}}}}, {Name: "recurse", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "r", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "r"}}}, Op: OpPipe}}}, Op: OpComma}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "r"}}}}, {Name: "recurse", Args: []string{"f", "cond"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "r", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "cond"}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "r"}}}, Op: OpPipe}, Op: OpPipe}}}, Op: OpComma}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "r"}}}}},
|
||||
"repeat": {{Name: "repeat", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_repeat", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_repeat"}}}, Op: OpComma}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_repeat"}}}}},
|
||||
"scalars": {{Name: "scalars", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Left: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}, Op: OpNe}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}, Op: OpNe}, Op: OpAnd}, Op: OpPipe}}}}}}},
|
||||
"scan": {{Name: "scan", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "scan", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeNull}}}}}}}, {Name: "scan", Args: []string{"$re", "$flags"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}, Op: OpAdd}}}}}, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "captures"}}}, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "string"}}}, Else: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "captures"}, SuffixList: []*Suffix{{Iter: true}, {Index: &Index{Name: "string"}}}}}}}}}}}, Op: OpPipe}}},
|
||||
"select": {{Name: "select", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Then: &Query{Term: &Term{Type: TermTypeIdentity}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}}}},
|
||||
"skip": {{Name: "skip", Args: []string{"$n", "g"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpGt}, Then: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}, Pattern: &Pattern{Name: "$item"}, Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Update: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}, Op: OpSub}, Extract: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpLt}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$item"}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}}}}}, Elif: []*IfElif{{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "error", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "skip doesn't support negative count"}}}}}}}}}}}},
|
||||
"sort_by": {{Name: "sort_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_sort_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}}}}}}}}}},
|
||||
"split": {{Name: "split", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "splits", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}}}}}}}}}},
|
||||
"splits": {{Name: "splits", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Query: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}, Op: OpAdd}}}}}, Right: &Query{Term: &Term{Type: TermTypeNull}}, Op: OpComma}}}, Pattern: &Pattern{Object: []*PatternObject{{Key: "$offset"}, {Key: "$length"}}}, Start: &Query{Term: &Term{Type: TermTypeNull}}, Update: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{{Key: "start", Val: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "next"}}}}, {Key: "end", Val: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$offset"}}}}, {Key: "next", Val: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$offset"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$length"}}}, Op: OpAdd}}}}}}}}}}}}}, {Name: "splits", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "splits", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeNull}}}}}}}},
|
||||
"strings": {{Name: "strings", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "string"}}}, Op: OpEq}}}}}}},
|
||||
"sub": {{Name: "sub", Args: []string{"$re", "str"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "sub", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "str"}}}, {Term: &Term{Type: TermTypeNull}}}}}}}, {Name: "sub", Args: []string{"$re", "str", "$flags"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}}}}}, Pattern: &Pattern{Object: []*PatternObject{{Key: "$offset"}, {Key: "$length"}, {Key: "$captures"}}}, Start: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{{Key: "s", Val: &Query{Term: &Term{Type: TermTypeIdentity}}}, {Key: "r", Val: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}}}}}}, Update: &Query{Left: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Query: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$captures"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_captures"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "str"}}}, Op: OpPipe}, Op: OpPipe}}}, Pattern: &Pattern{Name: "$s"}, Start: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "i"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, Op: OpAssign}, Update: &Query{Left: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "r"}, SuffixList: []*Suffix{{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "i"}}}}}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "s"}, SuffixList: []*Suffix{{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "next"}}}, End: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$offset"}}}, IsSlice: true}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$s"}}}, Op: OpAdd}, Op: OpUpdateAdd}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "i"}}}, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}, Op: OpUpdateAdd}, Op: OpPipe}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "next"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$offset"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$length"}}}, Op: OpAdd}, Op: OpAssign}, Op: OpPipe}}}}, Right: &Query{Left: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "r"}, SuffixList: []*Suffix{{Iter: true}}}}, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "s"}, SuffixList: []*Suffix{{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "next"}}}, IsSlice: true}}}}}, Op: OpAdd}, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "s"}}}, Op: OpAlt}, Op: OpPipe}}},
|
||||
"test": {{Name: "test", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "test", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeNull}}}}}}}, {Name: "test", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_match", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$re"}}}, {Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$flags"}}}, {Term: &Term{Type: TermTypeTrue}}}}}}}},
|
||||
"to_entries": {{Name: "to_entries", Body: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "keys"}, SuffixList: []*Suffix{{Iter: true}}}}, Right: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{{Key: "key", Val: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$k"}}}}, {Key: "value", Val: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$k"}}}}}}}}}}}, Patterns: []*Pattern{{Name: "$k"}}, Op: OpPipe}}}}}},
|
||||
"todate": {{Name: "todate", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "todateiso8601"}}}}},
|
||||
"todateiso8601": {{Name: "todateiso8601", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "strftime", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "%Y-%m-%dT%H:%M:%SZ"}}}}}}}}},
|
||||
"tostream": {{Name: "tostream", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{FuncDefs: []*FuncDef{{Name: "r", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}, {Optional: true}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "r"}}}, Op: OpPipe}}}, Right: &Query{Term: &Term{Type: TermTypeIdentity}}, Op: OpComma}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "r"}}}}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "getpath", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}, {Optional: true}}}}}}}}, Pattern: &Pattern{Name: "$q"}, Start: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}, Right: &Query{Term: &Term{Type: TermTypeIdentity}}, Op: OpComma}}}}, Update: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$p"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$q"}}}, Op: OpAdd}}}}}}}, Op: OpPipe}, Patterns: []*Pattern{{Name: "$p"}}, Op: OpPipe}}},
|
||||
"truncate_stream": {{Name: "truncate_stream", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeNull}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "length"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, Op: OpGt}, Op: OpPipe}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$n"}}}, IsSlice: true}}}, Op: OpModify}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}}, Op: OpPipe}, Op: OpPipe}, Patterns: []*Pattern{{Name: "$n"}}, Op: OpPipe}}},
|
||||
"unique_by": {{Name: "unique_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_unique_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}}}}}}}}}},
|
||||
"until": {{Name: "until", Args: []string{"cond", "next"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_until", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "cond"}}}, Then: &Query{Term: &Term{Type: TermTypeIdentity}}, Else: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "next"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_until"}}}, Op: OpPipe}}}}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_until"}}}}},
|
||||
"values": {{Name: "values", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeNull}}, Op: OpNe}}}}}}},
|
||||
"walk": {{Name: "walk", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_walk", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_walk"}}}}}}}, Elif: []*IfElif{{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "type"}}}, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}, Op: OpEq}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map_values", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_walk"}}}}}}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}, Op: OpPipe}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_walk"}}}}},
|
||||
"while": {{Name: "while", Args: []string{"cond", "update"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_while", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "cond"}}}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity}}, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "update"}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_while"}}}, Op: OpPipe}}}, Op: OpComma}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "empty"}}}}}}}}, Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_while"}}}}},
|
||||
"with_entries": {{Name: "with_entries", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "to_entries"}}}, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}}}}}}, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "from_entries"}}}, Op: OpPipe}, Op: OpPipe}}},
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
def map(f): [.[] | f];
|
||||
def add(f): [f] | add;
|
||||
def map_values(f): .[] |= f;
|
||||
def in(xs): . as $x | xs | has($x);
|
||||
def not: if . then false else true end;
|
||||
def select(f): if f then . else empty end;
|
||||
|
||||
def recurse: recurse(.[]?);
|
||||
def recurse(f): def r: ., (f | r); r;
|
||||
def recurse(f; cond): def r: ., (f | select(cond) | r); r;
|
||||
|
||||
def to_entries: [keys[] as $k | {key: $k, value: .[$k]}];
|
||||
def from_entries: map({ (.key // .Key // .name // .Name):
|
||||
if has("value") then .value else .Value end }) | add // {};
|
||||
def with_entries(f): to_entries | map(f) | from_entries;
|
||||
|
||||
def while(cond; update):
|
||||
def _while: if cond then ., (update | _while) else empty end;
|
||||
_while;
|
||||
def until(cond; next):
|
||||
def _until: if cond then . else next | _until end;
|
||||
_until;
|
||||
def repeat(f):
|
||||
def _repeat: f, _repeat;
|
||||
_repeat;
|
||||
def range($end): _range(0; $end; 1);
|
||||
def range($start; $end): _range($start; $end; 1);
|
||||
def range($start; $end; $step): _range($start; $end; $step);
|
||||
|
||||
def min_by(f): _min_by(map([f]));
|
||||
def max_by(f): _max_by(map([f]));
|
||||
def sort_by(f): _sort_by(map([f]));
|
||||
def group_by(f): _group_by(map([f]));
|
||||
def unique_by(f): _unique_by(map([f]));
|
||||
|
||||
def arrays: select(type == "array");
|
||||
def objects: select(type == "object");
|
||||
def iterables: select(type | . == "array" or . == "object");
|
||||
def booleans: select(type == "boolean");
|
||||
def numbers: select(type == "number");
|
||||
def finites: select(isfinite);
|
||||
def normals: select(isnormal);
|
||||
def strings: select(type == "string");
|
||||
def nulls: select(. == null);
|
||||
def values: select(. != null);
|
||||
def scalars: select(type | . != "array" and . != "object");
|
||||
|
||||
def combinations:
|
||||
if length == 0 then
|
||||
[]
|
||||
else
|
||||
.[0][] as $x | [$x] + (.[1:] | combinations)
|
||||
end;
|
||||
def combinations(n): [limit(n; repeat(.))] | combinations;
|
||||
|
||||
def walk(f):
|
||||
def _walk:
|
||||
if type == "array" then
|
||||
map(_walk)
|
||||
elif type == "object" then
|
||||
map_values(_walk)
|
||||
end | f;
|
||||
_walk;
|
||||
|
||||
def first: .[0];
|
||||
def first(g): label $out | g | ., break $out;
|
||||
def last: .[-1];
|
||||
def last(g): _last(g);
|
||||
def isempty(g): label $out | (g | false, break $out), true;
|
||||
def all: all(.);
|
||||
def all(y): all(.[]; y);
|
||||
def all(g; y): isempty(g | select(y | not));
|
||||
def any: any(.);
|
||||
def any(y): any(.[]; y);
|
||||
def any(g; y): isempty(g | select(y)) | not;
|
||||
|
||||
def limit($n; g):
|
||||
if $n > 0 then
|
||||
label $out |
|
||||
foreach g as $item (
|
||||
$n;
|
||||
. - 1;
|
||||
$item, if . <= 0 then break $out else empty end
|
||||
)
|
||||
elif $n == 0 then
|
||||
empty
|
||||
else
|
||||
error("limit doesn't support negative count")
|
||||
end;
|
||||
def skip($n; g):
|
||||
if $n > 0 then
|
||||
foreach g as $item (
|
||||
$n;
|
||||
. - 1;
|
||||
if . < 0 then $item else empty end
|
||||
)
|
||||
elif $n == 0 then
|
||||
g
|
||||
else
|
||||
error("skip doesn't support negative count")
|
||||
end;
|
||||
def nth($n): .[$n];
|
||||
def nth($n; g):
|
||||
if $n >= 0 then
|
||||
first(skip($n; g))
|
||||
else
|
||||
error("nth doesn't support negative index")
|
||||
end;
|
||||
|
||||
def truncate_stream(f):
|
||||
. as $n | null | f |
|
||||
if .[0] | length > $n then .[0] |= .[$n:] else empty end;
|
||||
def fromstream(f):
|
||||
foreach f as $pv (
|
||||
null;
|
||||
if .e then null end |
|
||||
$pv as [$p, $v] |
|
||||
if $pv | length == 2 then
|
||||
setpath(["v"] + $p; $v) |
|
||||
setpath(["e"]; $p | length == 0)
|
||||
else
|
||||
setpath(["e"]; $p | length == 1)
|
||||
end;
|
||||
if .e then .v else empty end
|
||||
);
|
||||
def tostream:
|
||||
path(def r: (.[]? | r), .; r) as $p |
|
||||
getpath($p) |
|
||||
reduce path(.[]?) as $q ([$p, .]; [$p + $q]);
|
||||
|
||||
def del(f): delpaths([path(f)]);
|
||||
def paths: path(..) | select(. != []);
|
||||
def paths(f): path(.. | select(f)) | select(. != []);
|
||||
def pick(f): . as $v |
|
||||
reduce path(f) as $p (null; setpath($p; $v | getpath($p)));
|
||||
|
||||
def fromdateiso8601: strptime("%Y-%m-%dT%H:%M:%S%z") | mktime;
|
||||
def todateiso8601: strftime("%Y-%m-%dT%H:%M:%SZ");
|
||||
def fromdate: fromdateiso8601;
|
||||
def todate: todateiso8601;
|
||||
|
||||
def match($re): match($re; null);
|
||||
def match($re; $flags): _match($re; $flags; false)[];
|
||||
def test($re): test($re; null);
|
||||
def test($re; $flags): _match($re; $flags; true);
|
||||
def capture($re): capture($re; null);
|
||||
def capture($re; $flags): match($re; $flags) | .captures | _captures;
|
||||
def scan($re): scan($re; null);
|
||||
def scan($re; $flags):
|
||||
match($re; $flags + "g") |
|
||||
if .captures == [] then
|
||||
.string
|
||||
else
|
||||
[.captures[].string]
|
||||
end;
|
||||
def splits($re; $flags):
|
||||
.[foreach (match($re; $flags + "g"), null) as {$offset, $length}
|
||||
(null; {start: .next, end: $offset, next: $offset + $length})];
|
||||
def splits($re): splits($re; null);
|
||||
def split($re; $flags): [splits($re; $flags)];
|
||||
def sub($re; str): sub($re; str; null);
|
||||
def sub($re; str; $flags):
|
||||
reduce match($re; $flags) as {$offset, $length, $captures}
|
||||
({s: ., r: []};
|
||||
reduce ($captures | _captures | str) as $s
|
||||
(.i = 0; .r[.i] += .s[.next:$offset] + $s | .i += 1) |
|
||||
.next = $offset + $length) | .r[] + .s[.next:] // .s;
|
||||
def gsub($re; str): sub($re; str; "g");
|
||||
def gsub($re; str; $flags): sub($re; str; $flags + "g");
|
||||
|
||||
def inputs:
|
||||
try
|
||||
repeat(input)
|
||||
catch
|
||||
if . == "break" then empty else error end;
|
||||
|
||||
def INDEX(stream; idx_expr):
|
||||
reduce stream as $row ({}; .[$row | idx_expr | tostring] = $row);
|
||||
def INDEX(idx_expr):
|
||||
INDEX(.[]; idx_expr);
|
||||
def JOIN($idx; idx_expr):
|
||||
[.[] | [., $idx[idx_expr]]];
|
||||
def JOIN($idx; stream; idx_expr):
|
||||
stream | [., $idx[idx_expr]];
|
||||
def JOIN($idx; stream; idx_expr; join_expr):
|
||||
stream | [., $idx[idx_expr]] | join_expr;
|
||||
def IN(s): any(s == .; .);
|
||||
def IN(src; s): any(src == s; .);
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package gojq
|
||||
|
||||
type code struct {
|
||||
v any
|
||||
op opcode
|
||||
}
|
||||
|
||||
type opcode int
|
||||
|
||||
const (
|
||||
opnop opcode = iota
|
||||
oppush
|
||||
oppop
|
||||
opdup
|
||||
opconst
|
||||
opload
|
||||
opstore
|
||||
opobject
|
||||
opappend
|
||||
opfork
|
||||
opforktrybegin
|
||||
opforktryend
|
||||
opforkalt
|
||||
opforklabel
|
||||
opbacktrack
|
||||
opjump
|
||||
opjumpifnot
|
||||
opindex
|
||||
opindexarray
|
||||
opcall
|
||||
opcallrec
|
||||
oppushpc
|
||||
opcallpc
|
||||
opscope
|
||||
opret
|
||||
opiter
|
||||
opexpbegin
|
||||
opexpend
|
||||
oppathbegin
|
||||
oppathend
|
||||
)
|
||||
|
||||
func (op opcode) String() string {
|
||||
switch op {
|
||||
case opnop:
|
||||
return "nop"
|
||||
case oppush:
|
||||
return "push"
|
||||
case oppop:
|
||||
return "pop"
|
||||
case opdup:
|
||||
return "dup"
|
||||
case opconst:
|
||||
return "const"
|
||||
case opload:
|
||||
return "load"
|
||||
case opstore:
|
||||
return "store"
|
||||
case opobject:
|
||||
return "object"
|
||||
case opappend:
|
||||
return "append"
|
||||
case opfork:
|
||||
return "fork"
|
||||
case opforktrybegin:
|
||||
return "forktrybegin"
|
||||
case opforktryend:
|
||||
return "forktryend"
|
||||
case opforkalt:
|
||||
return "forkalt"
|
||||
case opforklabel:
|
||||
return "forklabel"
|
||||
case opbacktrack:
|
||||
return "backtrack"
|
||||
case opjump:
|
||||
return "jump"
|
||||
case opjumpifnot:
|
||||
return "jumpifnot"
|
||||
case opindex:
|
||||
return "index"
|
||||
case opindexarray:
|
||||
return "indexarray"
|
||||
case opcall:
|
||||
return "call"
|
||||
case opcallrec:
|
||||
return "callrec"
|
||||
case oppushpc:
|
||||
return "pushpc"
|
||||
case opcallpc:
|
||||
return "callpc"
|
||||
case opscope:
|
||||
return "scope"
|
||||
case opret:
|
||||
return "ret"
|
||||
case opiter:
|
||||
return "iter"
|
||||
case opexpbegin:
|
||||
return "expbegin"
|
||||
case opexpend:
|
||||
return "expend"
|
||||
case oppathbegin:
|
||||
return "pathbegin"
|
||||
case oppathend:
|
||||
return "pathend"
|
||||
default:
|
||||
panic(op)
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Compare l and r, and returns jq-flavored comparison value.
|
||||
// The result will be 0 if l == r, -1 if l < r, and +1 if l > r.
|
||||
// This comparison is used by built-in operators and functions.
|
||||
func Compare(l, r any) int {
|
||||
return binopTypeSwitch(l, r,
|
||||
cmp.Compare,
|
||||
func(l, r float64) int {
|
||||
switch {
|
||||
case lt(l, r):
|
||||
return -1
|
||||
case l == r:
|
||||
return 0
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
},
|
||||
(*big.Int).Cmp,
|
||||
cmp.Compare,
|
||||
func(l, r []any) int {
|
||||
for i := range min(len(l), len(r)) {
|
||||
if cmp := Compare(l[i], r[i]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
return cmp.Compare(len(l), len(r))
|
||||
},
|
||||
func(l, r map[string]any) int {
|
||||
lk, rk := funcKeys(l), funcKeys(r)
|
||||
if cmp := Compare(lk, rk); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
for _, k := range lk.([]any) {
|
||||
if cmp := Compare(l[k.(string)], r[k.(string)]); cmp != 0 {
|
||||
return cmp
|
||||
}
|
||||
}
|
||||
return 0
|
||||
},
|
||||
func(l, r any) int {
|
||||
return cmp.Compare(typeIndex(l), typeIndex(r))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func lt(l, r float64) bool {
|
||||
return l < r || math.IsNaN(l)
|
||||
}
|
||||
|
||||
func typeIndex(v any) int {
|
||||
switch v := v.(type) {
|
||||
default:
|
||||
return 0
|
||||
case bool:
|
||||
if !v {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
case int, float64, *big.Int, json.Number:
|
||||
return 3
|
||||
case string:
|
||||
return 4
|
||||
case []any:
|
||||
return 5
|
||||
case map[string]any:
|
||||
return 6
|
||||
}
|
||||
}
|
||||
+1715
File diff suppressed because it is too large
Load Diff
+208
@@ -0,0 +1,208 @@
|
||||
//go:build gojq_debug
|
||||
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
debug bool
|
||||
debugOut io.Writer
|
||||
)
|
||||
|
||||
func init() {
|
||||
if out := os.Getenv("GOJQ_DEBUG"); out != "" {
|
||||
debug = true
|
||||
if out == "stdout" {
|
||||
debugOut = os.Stdout
|
||||
} else {
|
||||
debugOut = os.Stderr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type codeinfo struct {
|
||||
name string
|
||||
pc int
|
||||
}
|
||||
|
||||
func (c *compiler) appendCodeInfo(x any) {
|
||||
if !debug {
|
||||
return
|
||||
}
|
||||
var name string
|
||||
switch x := x.(type) {
|
||||
case string:
|
||||
name = x
|
||||
default:
|
||||
name = fmt.Sprint(x)
|
||||
}
|
||||
var diff int
|
||||
if c.codes[len(c.codes)-1] != nil && c.codes[len(c.codes)-1].op == opret && strings.HasPrefix(name, "end of ") {
|
||||
diff = -1
|
||||
}
|
||||
c.codeinfos = append(c.codeinfos, codeinfo{name, len(c.codes) + diff})
|
||||
}
|
||||
|
||||
func (c *compiler) deleteCodeInfo(name string) {
|
||||
for i := 0; i < len(c.codeinfos); i++ {
|
||||
if strings.HasSuffix(c.codeinfos[i].name, name) {
|
||||
copy(c.codeinfos[i:], c.codeinfos[i+1:])
|
||||
c.codeinfos = c.codeinfos[:len(c.codeinfos)-1]
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (env *env) lookupInfoName(pc int) string {
|
||||
var name string
|
||||
for _, ci := range env.codeinfos {
|
||||
if ci.pc == pc {
|
||||
if name != "" {
|
||||
name += ", "
|
||||
}
|
||||
name += ci.name
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (env *env) debugCodes() {
|
||||
if !debug {
|
||||
return
|
||||
}
|
||||
for i, c := range env.codes {
|
||||
pc := i
|
||||
switch c.op {
|
||||
case opcall, opcallrec:
|
||||
if x, ok := c.v.(int); ok {
|
||||
pc = x
|
||||
}
|
||||
case opjump:
|
||||
x := c.v.(int)
|
||||
if x > 0 && env.codes[x-1].op == opscope {
|
||||
pc = x - 1
|
||||
}
|
||||
}
|
||||
var s string
|
||||
if name := env.lookupInfoName(pc); name != "" {
|
||||
switch c.op {
|
||||
case opcall, opcallrec, opjump:
|
||||
if !strings.HasPrefix(name, "module ") {
|
||||
s = "\t## call " + name
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
s = "\t## " + name
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(debugOut, "\t%d\t%-*s%s%s\n", i, 25, c.op, debugOperand(c), s)
|
||||
}
|
||||
fmt.Fprintln(debugOut, "\t"+strings.Repeat("-", 40)+"+")
|
||||
}
|
||||
|
||||
func (env *env) debugState(pc int, backtrack bool) {
|
||||
if !debug {
|
||||
return
|
||||
}
|
||||
var sb strings.Builder
|
||||
c := env.codes[pc]
|
||||
op := c.op.String()
|
||||
if backtrack {
|
||||
op += " <backtrack>"
|
||||
}
|
||||
fmt.Fprintf(&sb, "\t%d\t%-*s%s\t|", pc, 25, op, debugOperand(c))
|
||||
var xs []int
|
||||
for i := env.stack.index; i >= 0; i = env.stack.data[i].next {
|
||||
xs = append(xs, i)
|
||||
}
|
||||
for i := len(xs) - 1; i >= 0; i-- {
|
||||
sb.WriteString("\t")
|
||||
sb.WriteString(debugValue(env.stack.data[xs[i]].value))
|
||||
}
|
||||
switch c.op {
|
||||
case opcall, opcallrec:
|
||||
if x, ok := c.v.(int); ok {
|
||||
pc = x
|
||||
}
|
||||
case opjump:
|
||||
x := c.v.(int)
|
||||
if x > 0 && env.codes[x-1].op == opscope {
|
||||
pc = x - 1
|
||||
}
|
||||
}
|
||||
if name := env.lookupInfoName(pc); name != "" {
|
||||
switch c.op {
|
||||
case opcall, opcallrec, opjump:
|
||||
if !strings.HasPrefix(name, "module ") {
|
||||
sb.WriteString("\t\t\t## call " + name)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
sb.WriteString("\t\t\t## " + name)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(debugOut, sb.String())
|
||||
}
|
||||
|
||||
func (env *env) debugForks(pc int, op string) {
|
||||
if !debug {
|
||||
return
|
||||
}
|
||||
var sb strings.Builder
|
||||
for i, v := range env.forks {
|
||||
if i > 0 {
|
||||
sb.WriteByte('\t')
|
||||
}
|
||||
if i == len(env.forks)-1 {
|
||||
sb.WriteByte('<')
|
||||
}
|
||||
fmt.Fprintf(&sb, "%d, %s", v.pc, debugValue(env.stack.data[v.stackindex].value))
|
||||
if i == len(env.forks)-1 {
|
||||
sb.WriteByte('>')
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(debugOut, "\t-\t%-*s%d\t|\t%s\n", 25, op, pc, sb.String())
|
||||
}
|
||||
|
||||
func debugOperand(c *code) string {
|
||||
switch c.op {
|
||||
case opcall, opcallrec:
|
||||
switch v := c.v.(type) {
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case [3]any:
|
||||
return fmt.Sprintf("%s/%d", v[2], v[1])
|
||||
default:
|
||||
panic(c)
|
||||
}
|
||||
default:
|
||||
return debugValue(c.v)
|
||||
}
|
||||
}
|
||||
|
||||
func debugValue(v any) string {
|
||||
switch v := v.(type) {
|
||||
case Iter:
|
||||
return fmt.Sprintf("gojq.Iter(%#v)", v)
|
||||
case []pathValue:
|
||||
return fmt.Sprintf("[]gojq.pathValue(%v)", v)
|
||||
case [2]int:
|
||||
return fmt.Sprintf("[%d,%d]", v[0], v[1])
|
||||
case [3]int:
|
||||
return fmt.Sprintf("[%d,%d,%d]", v[0], v[1], v[2])
|
||||
case [3]any:
|
||||
return fmt.Sprintf("[%v,%v,%v]", v[0], v[1], v[2])
|
||||
case allocator:
|
||||
return fmt.Sprintf("%v", v)
|
||||
default:
|
||||
return Preview(v)
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Marshal returns the jq-flavored JSON encoding of v.
|
||||
//
|
||||
// This method accepts only limited types (nil, bool, int, float64, *big.Int,
|
||||
// json.Number, string, []any, and map[string]any) because these are the
|
||||
// possible types a gojq iterator can emit. This method marshals NaN to null,
|
||||
// truncates infinities to (+|-) math.MaxFloat64, uses \b and \f in strings,
|
||||
// and does not escape '<', '>', '&', '\u2028', and '\u2029'. These behaviors
|
||||
// are based on the marshaler of jq command, and different from json.Marshal in
|
||||
// the Go standard library. Note that the result is not safe to embed in HTML.
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
(&encoder{w: &b}).encode(v)
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func jsonMarshal(v any) string {
|
||||
var sb strings.Builder
|
||||
(&encoder{w: &sb}).encode(v)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func jsonEncodeString(sb *strings.Builder, v string) {
|
||||
(&encoder{w: sb}).encodeString(v)
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
w interface {
|
||||
io.Writer
|
||||
io.ByteWriter
|
||||
io.StringWriter
|
||||
}
|
||||
buf [64]byte
|
||||
}
|
||||
|
||||
func (e *encoder) encode(v any) {
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
e.w.WriteString("null")
|
||||
case bool:
|
||||
if v {
|
||||
e.w.WriteString("true")
|
||||
} else {
|
||||
e.w.WriteString("false")
|
||||
}
|
||||
case int:
|
||||
e.w.Write(strconv.AppendInt(e.buf[:0], int64(v), 10))
|
||||
case float64:
|
||||
e.encodeFloat64(v)
|
||||
case *big.Int:
|
||||
e.w.Write(v.Append(e.buf[:0], 10))
|
||||
case json.Number:
|
||||
e.w.WriteString(v.String())
|
||||
case string:
|
||||
e.encodeString(v)
|
||||
case []any:
|
||||
e.encodeArray(v)
|
||||
case map[string]any:
|
||||
e.encodeObject(v)
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v))
|
||||
}
|
||||
}
|
||||
|
||||
// ref: floatEncoder in encoding/json
|
||||
func (e *encoder) encodeFloat64(f float64) {
|
||||
if math.IsNaN(f) {
|
||||
e.w.WriteString("null")
|
||||
return
|
||||
}
|
||||
f = min(max(f, -math.MaxFloat64), math.MaxFloat64)
|
||||
format := byte('f')
|
||||
if x := math.Abs(f); x != 0 && x < 1e-6 || x >= 1e21 {
|
||||
format = 'e'
|
||||
}
|
||||
buf := strconv.AppendFloat(e.buf[:0], f, format, -1, 64)
|
||||
if format == 'e' {
|
||||
// clean up e-09 to e-9
|
||||
if n := len(buf); n >= 4 && buf[n-4] == 'e' && buf[n-3] == '-' && buf[n-2] == '0' {
|
||||
buf[n-2] = buf[n-1]
|
||||
buf = buf[:n-1]
|
||||
}
|
||||
}
|
||||
e.w.Write(buf)
|
||||
}
|
||||
|
||||
// ref: encodeState#string in encoding/json
|
||||
func (e *encoder) encodeString(s string) {
|
||||
e.w.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if ' ' <= b && b <= '~' && b != '"' && b != '\\' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
e.w.WriteString(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '"':
|
||||
e.w.WriteString(`\"`)
|
||||
case '\\':
|
||||
e.w.WriteString(`\\`)
|
||||
case '\b':
|
||||
e.w.WriteString(`\b`)
|
||||
case '\f':
|
||||
e.w.WriteString(`\f`)
|
||||
case '\n':
|
||||
e.w.WriteString(`\n`)
|
||||
case '\r':
|
||||
e.w.WriteString(`\r`)
|
||||
case '\t':
|
||||
e.w.WriteString(`\t`)
|
||||
default:
|
||||
const hex = "0123456789abcdef"
|
||||
e.w.WriteString(`\u00`)
|
||||
e.w.WriteByte(hex[b>>4])
|
||||
e.w.WriteByte(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
if c == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
e.w.WriteString(s[start:i])
|
||||
}
|
||||
e.w.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
e.w.WriteString(s[start:])
|
||||
}
|
||||
e.w.WriteByte('"')
|
||||
}
|
||||
|
||||
func (e *encoder) encodeArray(vs []any) {
|
||||
e.w.WriteByte('[')
|
||||
for i, v := range vs {
|
||||
if i > 0 {
|
||||
e.w.WriteByte(',')
|
||||
}
|
||||
e.encode(v)
|
||||
}
|
||||
e.w.WriteByte(']')
|
||||
}
|
||||
|
||||
func (e *encoder) encodeObject(vs map[string]any) {
|
||||
e.w.WriteByte('{')
|
||||
type keyVal struct {
|
||||
key string
|
||||
val any
|
||||
}
|
||||
kvs := make([]keyVal, len(vs))
|
||||
var i int
|
||||
for k, v := range vs {
|
||||
kvs[i] = keyVal{k, v}
|
||||
i++
|
||||
}
|
||||
sort.Slice(kvs, func(i, j int) bool {
|
||||
return kvs[i].key < kvs[j].key
|
||||
})
|
||||
for i, kv := range kvs {
|
||||
if i > 0 {
|
||||
e.w.WriteByte(',')
|
||||
}
|
||||
e.encodeString(kv.key)
|
||||
e.w.WriteByte(':')
|
||||
e.encode(kv.val)
|
||||
}
|
||||
e.w.WriteByte('}')
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package gojq
|
||||
|
||||
import "context"
|
||||
|
||||
type env struct {
|
||||
pc int
|
||||
stack *stack
|
||||
paths *stack
|
||||
scopes *scopeStack
|
||||
values []any
|
||||
codes []*code
|
||||
codeinfos []codeinfo
|
||||
forks []fork
|
||||
backtrack bool
|
||||
offset int
|
||||
expdepth int
|
||||
label int
|
||||
args [32]any // len(env.args) > maxarity
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newEnv(ctx context.Context) *env {
|
||||
return &env{
|
||||
stack: newStack(),
|
||||
paths: newStack(),
|
||||
scopes: newScopeStack(),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
type scope struct {
|
||||
id int
|
||||
offset int
|
||||
pc int
|
||||
saveindex int
|
||||
outerindex int
|
||||
}
|
||||
|
||||
type fork struct {
|
||||
pc int
|
||||
stackindex int
|
||||
stacklimit int
|
||||
scopeindex int
|
||||
scopelimit int
|
||||
pathindex int
|
||||
pathlimit int
|
||||
offset int
|
||||
expdepth int
|
||||
}
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
package gojq
|
||||
|
||||
import "strconv"
|
||||
|
||||
// ValueError is an interface for errors with a value for internal function.
|
||||
// Return an error implementing this interface when you want to catch error
|
||||
// values (not error messages) by try-catch, just like built-in error function.
|
||||
// Refer to [WithFunction] to add a custom internal function.
|
||||
type ValueError interface {
|
||||
error
|
||||
Value() any
|
||||
}
|
||||
|
||||
type expectedObjectError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *expectedObjectError) Error() string {
|
||||
return "expected an object but got: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type expectedArrayError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *expectedArrayError) Error() string {
|
||||
return "expected an array but got: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type iteratorError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *iteratorError) Error() string {
|
||||
return "cannot iterate over: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type arrayIndexNegativeError struct {
|
||||
v int
|
||||
}
|
||||
|
||||
func (err *arrayIndexNegativeError) Error() string {
|
||||
return "array index should not be negative: " + Preview(err.v)
|
||||
}
|
||||
|
||||
type arrayIndexTooLargeError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *arrayIndexTooLargeError) Error() string {
|
||||
return "array index too large: " + Preview(err.v)
|
||||
}
|
||||
|
||||
type repeatStringTooLargeError struct {
|
||||
s string
|
||||
n float64
|
||||
}
|
||||
|
||||
func (err *repeatStringTooLargeError) Error() string {
|
||||
return "repeat string result too large: " + Preview(err.s) + " * " + Preview(err.n)
|
||||
}
|
||||
|
||||
type objectKeyNotStringError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *objectKeyNotStringError) Error() string {
|
||||
return "expected a string for object key but got: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type arrayIndexNotNumberError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *arrayIndexNotNumberError) Error() string {
|
||||
return "expected a number for indexing an array but got: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type stringIndexNotNumberError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *stringIndexNotNumberError) Error() string {
|
||||
return "expected a number for indexing a string but got: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type expectedStartEndError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *expectedStartEndError) Error() string {
|
||||
return `expected "start" and "end" for slicing but got: ` + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type lengthMismatchError struct{}
|
||||
|
||||
func (*lengthMismatchError) Error() string {
|
||||
return "length mismatch"
|
||||
}
|
||||
|
||||
type inputNotAllowedError struct{}
|
||||
|
||||
func (*inputNotAllowedError) Error() string {
|
||||
return "input(s)/0 is not allowed"
|
||||
}
|
||||
|
||||
type funcNotFoundError struct {
|
||||
f *Func
|
||||
}
|
||||
|
||||
func (err *funcNotFoundError) Error() string {
|
||||
return "function not defined: " + err.f.Name + "/" + strconv.Itoa(len(err.f.Args))
|
||||
}
|
||||
|
||||
type func0TypeError struct {
|
||||
name string
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *func0TypeError) Error() string {
|
||||
return err.name + " cannot be applied to: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type func1TypeError struct {
|
||||
name string
|
||||
v, w any
|
||||
}
|
||||
|
||||
func (err *func1TypeError) Error() string {
|
||||
return err.name + "(" + Preview(err.w) + ") cannot be applied to: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type func2TypeError struct {
|
||||
name string
|
||||
v, w, x any
|
||||
}
|
||||
|
||||
func (err *func2TypeError) Error() string {
|
||||
return err.name + "(" + Preview(err.w) + "; " + Preview(err.x) + ") cannot be applied to: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type func0WrapError struct {
|
||||
name string
|
||||
v any
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *func0WrapError) Error() string {
|
||||
return err.name + " cannot be applied to " + Preview(err.v) + ": " + err.err.Error()
|
||||
}
|
||||
|
||||
type func1WrapError struct {
|
||||
name string
|
||||
v, w any
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *func1WrapError) Error() string {
|
||||
return err.name + "(" + Preview(err.w) + ") cannot be applied to " + Preview(err.v) + ": " + err.err.Error()
|
||||
}
|
||||
|
||||
type func2WrapError struct {
|
||||
name string
|
||||
v, w, x any
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *func2WrapError) Error() string {
|
||||
return err.name + "(" + Preview(err.w) + "; " + Preview(err.x) + ") cannot be applied to " + Preview(err.v) + ": " + err.err.Error()
|
||||
}
|
||||
|
||||
type exitCodeError struct {
|
||||
value any
|
||||
code int
|
||||
}
|
||||
|
||||
func (err *exitCodeError) Error() string {
|
||||
if s, ok := err.value.(string); ok {
|
||||
return "error: " + s
|
||||
}
|
||||
return "error: " + jsonMarshal(err.value)
|
||||
}
|
||||
|
||||
func (err *exitCodeError) Value() any {
|
||||
return err.value
|
||||
}
|
||||
|
||||
func (err *exitCodeError) ExitCode() int {
|
||||
return err.code
|
||||
}
|
||||
|
||||
// HaltError is an error emitted by halt and halt_error functions.
|
||||
// It implements [ValueError], and if the value is nil, discard the error
|
||||
// and stop the iteration. Consider a query like "1, halt, 2";
|
||||
// the first value is 1, and the second value is a HaltError with nil value.
|
||||
// You might think the iterator should not emit an error this case, but it
|
||||
// should so that we can recognize the halt error to stop the outer loop
|
||||
// of iterating input values; echo 1 2 3 | gojq "., halt".
|
||||
type HaltError exitCodeError
|
||||
|
||||
func (err *HaltError) Error() string {
|
||||
return "halt " + (*exitCodeError)(err).Error()
|
||||
}
|
||||
|
||||
// Value returns the value of the error. This implements [ValueError],
|
||||
// but halt error is not catchable by try-catch.
|
||||
func (err *HaltError) Value() any {
|
||||
return (*exitCodeError)(err).Value()
|
||||
}
|
||||
|
||||
// ExitCode returns the exit code of the error.
|
||||
func (err *HaltError) ExitCode() int {
|
||||
return (*exitCodeError)(err).ExitCode()
|
||||
}
|
||||
|
||||
type flattenDepthError struct {
|
||||
v float64
|
||||
}
|
||||
|
||||
func (err *flattenDepthError) Error() string {
|
||||
return "flatten depth should not be negative: " + Preview(err.v)
|
||||
}
|
||||
|
||||
type timeArrayError struct{}
|
||||
|
||||
func (*timeArrayError) Error() string {
|
||||
return "expected an array of 8 numbers"
|
||||
}
|
||||
|
||||
type unaryTypeError struct {
|
||||
name string
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *unaryTypeError) Error() string {
|
||||
return "cannot " + err.name + ": " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type binopTypeError struct {
|
||||
name string
|
||||
l, r any
|
||||
}
|
||||
|
||||
func (err *binopTypeError) Error() string {
|
||||
return "cannot " + err.name + ": " + typeErrorPreview(err.l) + " and " + typeErrorPreview(err.r)
|
||||
}
|
||||
|
||||
type zeroDivisionError struct {
|
||||
l, r any
|
||||
}
|
||||
|
||||
func (err *zeroDivisionError) Error() string {
|
||||
return "cannot divide " + typeErrorPreview(err.l) + " by: " + typeErrorPreview(err.r)
|
||||
}
|
||||
|
||||
type zeroModuloError struct {
|
||||
l, r any
|
||||
}
|
||||
|
||||
func (err *zeroModuloError) Error() string {
|
||||
return "cannot modulo " + typeErrorPreview(err.l) + " by: " + typeErrorPreview(err.r)
|
||||
}
|
||||
|
||||
type formatNotFoundError struct {
|
||||
n string
|
||||
}
|
||||
|
||||
func (err *formatNotFoundError) Error() string {
|
||||
return "format not defined: " + err.n
|
||||
}
|
||||
|
||||
type formatRowError struct {
|
||||
typ string
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *formatRowError) Error() string {
|
||||
return "@" + err.typ + " cannot format an array including: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type tooManyVariableValuesError struct{}
|
||||
|
||||
func (*tooManyVariableValuesError) Error() string {
|
||||
return "too many variable values provided"
|
||||
}
|
||||
|
||||
type expectedVariableError struct {
|
||||
n string
|
||||
}
|
||||
|
||||
func (err *expectedVariableError) Error() string {
|
||||
return "variable defined but not bound: " + err.n
|
||||
}
|
||||
|
||||
type variableNotFoundError struct {
|
||||
n string
|
||||
}
|
||||
|
||||
func (err *variableNotFoundError) Error() string {
|
||||
return "variable not defined: " + err.n
|
||||
}
|
||||
|
||||
type variableNameError struct {
|
||||
n string
|
||||
}
|
||||
|
||||
func (err *variableNameError) Error() string {
|
||||
return "invalid variable name: " + err.n
|
||||
}
|
||||
|
||||
type breakError struct {
|
||||
n string
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *breakError) Error() string {
|
||||
return "label not defined: " + err.n
|
||||
}
|
||||
|
||||
func (*breakError) ExitCode() int {
|
||||
return 3
|
||||
}
|
||||
|
||||
type tryEndError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *tryEndError) Error() string {
|
||||
return err.err.Error()
|
||||
}
|
||||
|
||||
type invalidPathError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *invalidPathError) Error() string {
|
||||
return "invalid path against: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type invalidPathIterError struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (err *invalidPathIterError) Error() string {
|
||||
return "invalid path on iterating against: " + typeErrorPreview(err.v)
|
||||
}
|
||||
|
||||
type queryParseError struct {
|
||||
fname, contents string
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *queryParseError) QueryParseError() (string, string, error) {
|
||||
return err.fname, err.contents, err.err
|
||||
}
|
||||
|
||||
func (err *queryParseError) Error() string {
|
||||
return "invalid query: " + err.fname + ": " + err.err.Error()
|
||||
}
|
||||
|
||||
type jsonParseError struct {
|
||||
fname, contents string
|
||||
err error
|
||||
}
|
||||
|
||||
func (err *jsonParseError) JSONParseError() (string, string, error) {
|
||||
return err.fname, err.contents, err.err
|
||||
}
|
||||
|
||||
func (err *jsonParseError) Error() string {
|
||||
return "invalid json: " + err.fname + ": " + err.err.Error()
|
||||
}
|
||||
|
||||
func typeErrorPreview(v any) string {
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return "null"
|
||||
case Iter:
|
||||
return "gojq.Iter"
|
||||
default:
|
||||
return TypeOf(v) + " (" + Preview(v) + ")"
|
||||
}
|
||||
}
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (env *env) execute(bc *Code, v any, vars ...any) Iter {
|
||||
env.codes = bc.codes
|
||||
env.codeinfos = bc.codeinfos
|
||||
env.push(v)
|
||||
for i := len(vars) - 1; i >= 0; i-- {
|
||||
env.push(vars[i])
|
||||
}
|
||||
env.debugCodes()
|
||||
return env
|
||||
}
|
||||
|
||||
func (env *env) Next() (any, bool) {
|
||||
var err error
|
||||
pc, callpc, index := env.pc, len(env.codes)-1, -1
|
||||
backtrack, hasCtx := env.backtrack, env.ctx != context.Background()
|
||||
defer func() { env.pc, env.backtrack = pc, true }()
|
||||
loop:
|
||||
for ; pc < len(env.codes); pc++ {
|
||||
env.debugState(pc, backtrack)
|
||||
code := env.codes[pc]
|
||||
if hasCtx {
|
||||
select {
|
||||
case <-env.ctx.Done():
|
||||
pc, env.forks = len(env.codes), nil
|
||||
return env.ctx.Err(), true
|
||||
default:
|
||||
}
|
||||
}
|
||||
switch code.op {
|
||||
case opnop:
|
||||
// nop
|
||||
case oppush:
|
||||
env.push(code.v)
|
||||
case oppop:
|
||||
env.pop()
|
||||
case opdup:
|
||||
v := env.pop()
|
||||
env.push(v)
|
||||
env.push(v)
|
||||
case opconst:
|
||||
env.pop()
|
||||
env.push(code.v)
|
||||
case opload:
|
||||
env.push(env.values[env.index(code.v.([2]int))])
|
||||
case opstore:
|
||||
env.values[env.index(code.v.([2]int))] = env.pop()
|
||||
case opobject:
|
||||
if backtrack {
|
||||
break loop
|
||||
}
|
||||
n := code.v.(int)
|
||||
m := make(map[string]any, n)
|
||||
for range n {
|
||||
v, k := env.pop(), env.pop()
|
||||
s, ok := k.(string)
|
||||
if !ok {
|
||||
err = &objectKeyNotStringError{k}
|
||||
break loop
|
||||
}
|
||||
if _, ok := m[s]; !ok {
|
||||
m[s] = v
|
||||
}
|
||||
}
|
||||
env.push(m)
|
||||
case opappend:
|
||||
i := env.index(code.v.([2]int))
|
||||
env.values[i] = append(env.values[i].([]any), env.pop())
|
||||
case opfork:
|
||||
if backtrack {
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
pc, backtrack = code.v.(int), false
|
||||
goto loop
|
||||
}
|
||||
env.pushfork(pc)
|
||||
case opforktrybegin:
|
||||
if backtrack {
|
||||
if err == nil {
|
||||
break loop
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *tryEndError:
|
||||
err = e.err
|
||||
break loop
|
||||
case *breakError, *HaltError:
|
||||
break loop
|
||||
case ValueError:
|
||||
env.pop()
|
||||
env.push(e.Value())
|
||||
default:
|
||||
env.pop()
|
||||
env.push(err.Error())
|
||||
}
|
||||
pc, backtrack, err = code.v.(int), false, nil
|
||||
goto loop
|
||||
}
|
||||
env.pushfork(pc)
|
||||
case opforktryend:
|
||||
if backtrack {
|
||||
if err != nil {
|
||||
err = &tryEndError{err}
|
||||
}
|
||||
break loop
|
||||
}
|
||||
env.pushfork(pc)
|
||||
case opforkalt:
|
||||
if backtrack {
|
||||
if err == nil {
|
||||
break loop
|
||||
}
|
||||
pc, backtrack, err = code.v.(int), false, nil
|
||||
goto loop
|
||||
}
|
||||
env.pushfork(pc)
|
||||
case opforklabel:
|
||||
if backtrack {
|
||||
label := env.pop()
|
||||
if e, ok := err.(*breakError); ok && e.v == label {
|
||||
err = nil
|
||||
}
|
||||
break loop
|
||||
}
|
||||
env.push(env.label)
|
||||
env.pushfork(pc)
|
||||
env.pop()
|
||||
env.values[env.index(code.v.([2]int))] = env.label
|
||||
env.label++
|
||||
case opbacktrack:
|
||||
break loop
|
||||
case opjump:
|
||||
pc = code.v.(int)
|
||||
goto loop
|
||||
case opjumpifnot:
|
||||
if v := env.pop(); v == nil || v == false {
|
||||
pc = code.v.(int)
|
||||
goto loop
|
||||
}
|
||||
case opindex, opindexarray:
|
||||
if backtrack {
|
||||
break loop
|
||||
}
|
||||
p, v := code.v, env.pop()
|
||||
if code.op == opindexarray && v != nil {
|
||||
if _, ok := v.([]any); !ok {
|
||||
err = &expectedArrayError{v}
|
||||
break loop
|
||||
}
|
||||
}
|
||||
w := funcIndex2(nil, v, p)
|
||||
if e, ok := w.(error); ok {
|
||||
err = e
|
||||
break loop
|
||||
}
|
||||
env.push(w)
|
||||
if !env.paths.empty() && env.expdepth == 0 {
|
||||
if !env.pathIntact(v) {
|
||||
err = &invalidPathError{v}
|
||||
break loop
|
||||
}
|
||||
env.paths.push(pathValue{path: p, value: w})
|
||||
}
|
||||
case opcall:
|
||||
if backtrack {
|
||||
break loop
|
||||
}
|
||||
switch v := code.v.(type) {
|
||||
case int:
|
||||
pc, callpc, index = v, pc, env.scopes.index
|
||||
goto loop
|
||||
case [3]any:
|
||||
argcnt := v[1].(int)
|
||||
x, args := env.pop(), env.args[:argcnt]
|
||||
for i := range argcnt {
|
||||
args[i] = env.pop()
|
||||
}
|
||||
w := v[0].(func(any, []any) any)(x, args)
|
||||
if e, ok := w.(error); ok {
|
||||
err = e
|
||||
break loop
|
||||
}
|
||||
env.push(w)
|
||||
if !env.paths.empty() && env.expdepth == 0 {
|
||||
switch v[2].(string) {
|
||||
case "_index":
|
||||
if x = args[0]; !env.pathIntact(x) {
|
||||
err = &invalidPathError{x}
|
||||
break loop
|
||||
}
|
||||
env.paths.push(pathValue{path: args[1], value: w})
|
||||
case "_slice":
|
||||
if x = args[0]; !env.pathIntact(x) {
|
||||
err = &invalidPathError{x}
|
||||
break loop
|
||||
}
|
||||
env.paths.push(pathValue{
|
||||
path: map[string]any{"start": args[2], "end": args[1]},
|
||||
value: w,
|
||||
})
|
||||
case "getpath":
|
||||
if !env.pathIntact(x) {
|
||||
err = &invalidPathError{x}
|
||||
break loop
|
||||
}
|
||||
for _, p := range args[0].([]any) {
|
||||
env.paths.push(pathValue{path: p, value: w})
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic(v)
|
||||
}
|
||||
case opcallrec:
|
||||
pc, callpc, index = code.v.(int), -1, env.scopes.index
|
||||
goto loop
|
||||
case oppushpc:
|
||||
env.push([2]int{code.v.(int), env.scopes.index})
|
||||
case opcallpc:
|
||||
xs := env.pop().([2]int)
|
||||
pc, callpc, index = xs[0], pc, xs[1]
|
||||
goto loop
|
||||
case opscope:
|
||||
xs := code.v.([3]int)
|
||||
var saveindex, outerindex int
|
||||
if index == env.scopes.index {
|
||||
if callpc >= 0 {
|
||||
saveindex = index
|
||||
} else {
|
||||
callpc, saveindex = env.popscope()
|
||||
}
|
||||
} else {
|
||||
saveindex = env.scopes.index
|
||||
}
|
||||
if outerindex = index; outerindex >= 0 {
|
||||
if s := env.scopes.data[outerindex].value; s.id == xs[0] {
|
||||
outerindex = s.outerindex
|
||||
}
|
||||
}
|
||||
env.scopes.push(scope{xs[0], env.offset, callpc, saveindex, outerindex})
|
||||
env.offset += xs[1]
|
||||
if env.offset > len(env.values) {
|
||||
vs := make([]any, env.offset*2)
|
||||
copy(vs, env.values)
|
||||
env.values = vs
|
||||
}
|
||||
case opret:
|
||||
if backtrack {
|
||||
break loop
|
||||
}
|
||||
pc, env.scopes.index = env.popscope()
|
||||
if env.scopes.empty() {
|
||||
return env.pop(), true
|
||||
}
|
||||
case opiter:
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
backtrack = false
|
||||
var xs []pathValue
|
||||
switch v := env.pop().(type) {
|
||||
case []pathValue:
|
||||
xs = v
|
||||
case []any:
|
||||
if !env.paths.empty() && env.expdepth == 0 && !env.pathIntact(v) {
|
||||
err = &invalidPathIterError{v}
|
||||
break loop
|
||||
}
|
||||
if len(v) == 0 {
|
||||
break loop
|
||||
}
|
||||
xs = make([]pathValue, len(v))
|
||||
for i, v := range v {
|
||||
xs[i] = pathValue{path: i, value: v}
|
||||
}
|
||||
case map[string]any:
|
||||
if !env.paths.empty() && env.expdepth == 0 && !env.pathIntact(v) {
|
||||
err = &invalidPathIterError{v}
|
||||
break loop
|
||||
}
|
||||
if len(v) == 0 {
|
||||
break loop
|
||||
}
|
||||
xs = make([]pathValue, len(v))
|
||||
var i int
|
||||
for k, v := range v {
|
||||
xs[i] = pathValue{path: k, value: v}
|
||||
i++
|
||||
}
|
||||
sort.Slice(xs, func(i, j int) bool {
|
||||
return xs[i].path.(string) < xs[j].path.(string)
|
||||
})
|
||||
case Iter:
|
||||
if w, ok := v.Next(); ok {
|
||||
env.push(v)
|
||||
env.pushfork(pc)
|
||||
env.pop()
|
||||
if e, ok := w.(error); ok {
|
||||
err = e
|
||||
break loop
|
||||
}
|
||||
env.push(w)
|
||||
continue
|
||||
}
|
||||
break loop
|
||||
default:
|
||||
err = &iteratorError{v}
|
||||
env.push(emptyIter{})
|
||||
break loop
|
||||
}
|
||||
if len(xs) > 1 {
|
||||
env.push(xs[1:])
|
||||
env.pushfork(pc)
|
||||
env.pop()
|
||||
}
|
||||
env.push(xs[0].value)
|
||||
if !env.paths.empty() && env.expdepth == 0 {
|
||||
env.paths.push(xs[0])
|
||||
}
|
||||
case opexpbegin:
|
||||
env.expdepth++
|
||||
case opexpend:
|
||||
env.expdepth--
|
||||
case oppathbegin:
|
||||
env.paths.push(env.expdepth)
|
||||
env.paths.push(pathValue{value: env.stack.top()})
|
||||
env.expdepth = 0
|
||||
case oppathend:
|
||||
if backtrack {
|
||||
break loop
|
||||
}
|
||||
env.pop()
|
||||
if v := env.pop(); !env.pathIntact(v) {
|
||||
err = &invalidPathError{v}
|
||||
break loop
|
||||
}
|
||||
env.push(env.poppaths())
|
||||
env.expdepth = env.paths.pop().(int)
|
||||
default:
|
||||
panic(code.op)
|
||||
}
|
||||
}
|
||||
if len(env.forks) > 0 {
|
||||
pc, backtrack = env.popfork(), true
|
||||
goto loop
|
||||
}
|
||||
if err != nil {
|
||||
return err, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (env *env) push(v any) {
|
||||
env.stack.push(v)
|
||||
}
|
||||
|
||||
func (env *env) pop() any {
|
||||
return env.stack.pop()
|
||||
}
|
||||
|
||||
func (env *env) popscope() (int, int) {
|
||||
free := env.scopes.index > env.scopes.limit
|
||||
s := env.scopes.pop()
|
||||
if free {
|
||||
env.offset = s.offset
|
||||
}
|
||||
return s.pc, s.saveindex
|
||||
}
|
||||
|
||||
func (env *env) pushfork(pc int) {
|
||||
f := fork{pc: pc, offset: env.offset, expdepth: env.expdepth}
|
||||
f.stackindex, f.stacklimit = env.stack.save()
|
||||
f.scopeindex, f.scopelimit = env.scopes.save()
|
||||
f.pathindex, f.pathlimit = env.paths.save()
|
||||
env.forks = append(env.forks, f)
|
||||
env.debugForks(pc, ">>>")
|
||||
}
|
||||
|
||||
func (env *env) popfork() int {
|
||||
f := env.forks[len(env.forks)-1]
|
||||
env.debugForks(f.pc, "<<<")
|
||||
env.forks, env.offset, env.expdepth =
|
||||
env.forks[:len(env.forks)-1], f.offset, f.expdepth
|
||||
env.stack.restore(f.stackindex, f.stacklimit)
|
||||
env.scopes.restore(f.scopeindex, f.scopelimit)
|
||||
env.paths.restore(f.pathindex, f.pathlimit)
|
||||
return f.pc
|
||||
}
|
||||
|
||||
func (env *env) index(v [2]int) int {
|
||||
for id, i := v[0], env.scopes.index; i >= 0; {
|
||||
s := env.scopes.data[i].value
|
||||
if s.id == id {
|
||||
return s.offset + v[1]
|
||||
}
|
||||
i = s.outerindex
|
||||
}
|
||||
panic("env.index")
|
||||
}
|
||||
|
||||
type pathValue struct {
|
||||
path, value any
|
||||
}
|
||||
|
||||
func (env *env) pathIntact(v any) bool {
|
||||
w := env.paths.top().(pathValue).value
|
||||
switch v := v.(type) {
|
||||
case []any, map[string]any:
|
||||
switch w.(type) {
|
||||
case []any, map[string]any:
|
||||
v, w := reflect.ValueOf(v), reflect.ValueOf(w)
|
||||
return v.Pointer() == w.Pointer() && v.Len() == w.Len()
|
||||
}
|
||||
case float64:
|
||||
if w, ok := w.(float64); ok {
|
||||
return v == w || math.IsNaN(v) && math.IsNaN(w)
|
||||
}
|
||||
}
|
||||
return v == w
|
||||
}
|
||||
|
||||
func (env *env) poppaths() []any {
|
||||
xs := []any{}
|
||||
for {
|
||||
p := env.paths.pop().(pathValue)
|
||||
if p.path == nil {
|
||||
break
|
||||
}
|
||||
xs = append(xs, p.path)
|
||||
}
|
||||
for i, j := 0, len(xs)-1; i < j; i, j = i+1, j-1 {
|
||||
xs[i], xs[j] = xs[j], xs[i]
|
||||
}
|
||||
return xs
|
||||
}
|
||||
+2190
File diff suppressed because it is too large
Load Diff
+8
@@ -0,0 +1,8 @@
|
||||
module github.com/itchyny/gojq
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/itchyny/astgen-go v0.0.0-20250520171007-4331c963041e // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.8 // indirect
|
||||
)
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
github.com/itchyny/astgen-go v0.0.0-20250520171007-4331c963041e h1:Pu7Ev38D+75aWdsHzULyVZfMP3WrgIHNUaAUH81CTqE=
|
||||
github.com/itchyny/astgen-go v0.0.0-20250520171007-4331c963041e/go.mod h1:8EXgnGrgW1p0qVnOF1A/4ORQEUAi8TvxQdIR+zJfKxM=
|
||||
github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI=
|
||||
github.com/itchyny/timefmt-go v0.1.8/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// Package gojq provides the parser and the interpreter of gojq.
|
||||
// Please refer to [Usage as a library] for introduction.
|
||||
//
|
||||
// [Usage as a library]: https://github.com/itchyny/gojq#usage-as-a-library
|
||||
package gojq
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package gojq
|
||||
|
||||
// Iter is an interface for an iterator.
|
||||
type Iter interface {
|
||||
Next() (any, bool)
|
||||
}
|
||||
|
||||
// NewIter creates a new [Iter] from values.
|
||||
func NewIter[T any](values ...T) Iter {
|
||||
switch len(values) {
|
||||
case 0:
|
||||
return emptyIter{}
|
||||
case 1:
|
||||
return &unitIter{value: values[0]}
|
||||
default:
|
||||
iter := sliceIter[T](values)
|
||||
return &iter
|
||||
}
|
||||
}
|
||||
|
||||
type emptyIter struct{}
|
||||
|
||||
func (emptyIter) Next() (any, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type unitIter struct {
|
||||
value any
|
||||
done bool
|
||||
}
|
||||
|
||||
func (iter *unitIter) Next() (any, bool) {
|
||||
if iter.done {
|
||||
return nil, false
|
||||
}
|
||||
iter.done = true
|
||||
return iter.value, true
|
||||
}
|
||||
|
||||
type sliceIter[T any] []T
|
||||
|
||||
func (iter *sliceIter[T]) Next() (any, bool) {
|
||||
if len(*iter) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
value := (*iter)[0]
|
||||
*iter = (*iter)[1:]
|
||||
return value, true
|
||||
}
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
source string
|
||||
offset int
|
||||
result *Query
|
||||
token string
|
||||
tokenType int
|
||||
inString bool
|
||||
err error
|
||||
}
|
||||
|
||||
func newLexer(src string) *lexer {
|
||||
return &lexer{source: src}
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
var keywords = map[string]int{
|
||||
"or": tokOrOp,
|
||||
"and": tokAndOp,
|
||||
"module": tokModule,
|
||||
"import": tokImport,
|
||||
"include": tokInclude,
|
||||
"def": tokDef,
|
||||
"as": tokAs,
|
||||
"label": tokLabel,
|
||||
"break": tokBreak,
|
||||
"null": tokNull,
|
||||
"true": tokTrue,
|
||||
"false": tokFalse,
|
||||
"if": tokIf,
|
||||
"then": tokThen,
|
||||
"elif": tokElif,
|
||||
"else": tokElse,
|
||||
"end": tokEnd,
|
||||
"try": tokTry,
|
||||
"catch": tokCatch,
|
||||
"reduce": tokReduce,
|
||||
"foreach": tokForeach,
|
||||
}
|
||||
|
||||
func (l *lexer) Lex(lval *yySymType) (tokenType int) {
|
||||
defer func() { l.tokenType = tokenType }()
|
||||
if len(l.source) == l.offset {
|
||||
l.token = ""
|
||||
return eof
|
||||
}
|
||||
if l.inString {
|
||||
tok, str := l.scanString(l.offset)
|
||||
lval.token = str
|
||||
return tok
|
||||
}
|
||||
ch, iseof := l.next()
|
||||
if iseof {
|
||||
l.token = ""
|
||||
return eof
|
||||
}
|
||||
switch {
|
||||
case isIdent(ch, false):
|
||||
i := l.offset - 1
|
||||
j, isModule := l.scanIdentOrModule()
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
if isModule {
|
||||
return tokModuleIdent
|
||||
}
|
||||
if tok, ok := keywords[l.token]; ok {
|
||||
return tok
|
||||
}
|
||||
return tokIdent
|
||||
case isNumber(ch):
|
||||
i := l.offset - 1
|
||||
j := l.scanNumber(numberStateLead)
|
||||
if j < 0 {
|
||||
l.token = l.source[i:-j]
|
||||
return tokInvalid
|
||||
}
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
return tokNumber
|
||||
}
|
||||
switch ch {
|
||||
case '.':
|
||||
ch := l.peek()
|
||||
switch {
|
||||
case ch == '.':
|
||||
l.offset++
|
||||
l.token = ".."
|
||||
return tokRecurse
|
||||
case isIdent(ch, false):
|
||||
l.token = l.source[l.offset-1 : l.scanIdent()]
|
||||
lval.token = l.token[1:]
|
||||
return tokIndex
|
||||
case isNumber(ch):
|
||||
i := l.offset - 1
|
||||
j := l.scanNumber(numberStateFloat)
|
||||
if j < 0 {
|
||||
l.token = l.source[i:-j]
|
||||
return tokInvalid
|
||||
}
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
return tokNumber
|
||||
default:
|
||||
return '.'
|
||||
}
|
||||
case '$':
|
||||
if isIdent(l.peek(), false) {
|
||||
i := l.offset - 1
|
||||
j, isModule := l.scanIdentOrModule()
|
||||
l.token = l.source[i:j]
|
||||
lval.token = l.token
|
||||
if isModule {
|
||||
return tokModuleVariable
|
||||
}
|
||||
return tokVariable
|
||||
}
|
||||
case '|':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "|="
|
||||
lval.operator = OpModify
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '?':
|
||||
if l.peek() == '/' {
|
||||
l.offset++
|
||||
if l.peek() == '/' {
|
||||
l.offset++
|
||||
l.token = "?//"
|
||||
return tokDestAltOp
|
||||
}
|
||||
l.offset--
|
||||
}
|
||||
case '+':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "+="
|
||||
lval.operator = OpUpdateAdd
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '-':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "-="
|
||||
lval.operator = OpUpdateSub
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '*':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "*="
|
||||
lval.operator = OpUpdateMul
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '/':
|
||||
switch l.peek() {
|
||||
case '=':
|
||||
l.offset++
|
||||
l.token = "/="
|
||||
lval.operator = OpUpdateDiv
|
||||
return tokUpdateOp
|
||||
case '/':
|
||||
l.offset++
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "//="
|
||||
lval.operator = OpUpdateAlt
|
||||
return tokUpdateOp
|
||||
}
|
||||
l.token = "//"
|
||||
lval.operator = OpAlt
|
||||
return tokAltOp
|
||||
}
|
||||
case '%':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "%="
|
||||
lval.operator = OpUpdateMod
|
||||
return tokUpdateOp
|
||||
}
|
||||
case '=':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "=="
|
||||
lval.operator = OpEq
|
||||
return tokCompareOp
|
||||
}
|
||||
l.token = "="
|
||||
lval.operator = OpAssign
|
||||
return tokUpdateOp
|
||||
case '!':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "!="
|
||||
lval.operator = OpNe
|
||||
return tokCompareOp
|
||||
}
|
||||
case '>':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = ">="
|
||||
lval.operator = OpGe
|
||||
return tokCompareOp
|
||||
}
|
||||
l.token = ">"
|
||||
lval.operator = OpGt
|
||||
return tokCompareOp
|
||||
case '<':
|
||||
if l.peek() == '=' {
|
||||
l.offset++
|
||||
l.token = "<="
|
||||
lval.operator = OpLe
|
||||
return tokCompareOp
|
||||
}
|
||||
l.token = "<"
|
||||
lval.operator = OpLt
|
||||
return tokCompareOp
|
||||
case '@':
|
||||
if isIdent(l.peek(), true) {
|
||||
l.token = l.source[l.offset-1 : l.scanIdent()]
|
||||
lval.token = l.token
|
||||
return tokFormat
|
||||
}
|
||||
case '"':
|
||||
tok, str := l.scanString(l.offset - 1)
|
||||
lval.token = str
|
||||
return tok
|
||||
default:
|
||||
if ch >= utf8.RuneSelf {
|
||||
r, size := utf8.DecodeRuneInString(l.source[l.offset-1:])
|
||||
// -1 to adjust for first byte consumed by next()
|
||||
l.offset += size - 1
|
||||
l.token = string(r)
|
||||
}
|
||||
}
|
||||
return int(ch)
|
||||
}
|
||||
|
||||
func (l *lexer) next() (byte, bool) {
|
||||
for {
|
||||
ch := l.source[l.offset]
|
||||
l.offset++
|
||||
if ch == '#' {
|
||||
if l.skipComment() {
|
||||
return 0, true
|
||||
}
|
||||
} else if !isWhite(ch) {
|
||||
return ch, false
|
||||
} else if len(l.source) == l.offset {
|
||||
return 0, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) skipComment() bool {
|
||||
for {
|
||||
switch l.peek() {
|
||||
case 0:
|
||||
return true
|
||||
case '\\':
|
||||
switch l.offset++; l.peek() {
|
||||
case '\\', '\n':
|
||||
l.offset++
|
||||
case '\r':
|
||||
if l.offset++; l.peek() == '\n' {
|
||||
l.offset++
|
||||
}
|
||||
}
|
||||
case '\n', '\r':
|
||||
return false
|
||||
default:
|
||||
l.offset++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) peek() byte {
|
||||
if len(l.source) == l.offset {
|
||||
return 0
|
||||
}
|
||||
return l.source[l.offset]
|
||||
}
|
||||
|
||||
func (l *lexer) scanIdent() int {
|
||||
for isIdent(l.peek(), true) {
|
||||
l.offset++
|
||||
}
|
||||
return l.offset
|
||||
}
|
||||
|
||||
func (l *lexer) scanIdentOrModule() (int, bool) {
|
||||
index := l.scanIdent()
|
||||
var isModule bool
|
||||
if l.peek() == ':' {
|
||||
l.offset++
|
||||
if l.peek() == ':' {
|
||||
l.offset++
|
||||
if isIdent(l.peek(), false) {
|
||||
l.offset++
|
||||
index = l.scanIdent()
|
||||
isModule = true
|
||||
} else {
|
||||
l.offset -= 2
|
||||
}
|
||||
} else {
|
||||
l.offset--
|
||||
}
|
||||
}
|
||||
return index, isModule
|
||||
}
|
||||
|
||||
func (l *lexer) validVarName() bool {
|
||||
if l.peek() != '$' {
|
||||
return false
|
||||
}
|
||||
l.offset++
|
||||
return isIdent(l.peek(), false) && l.scanIdent() == len(l.source)
|
||||
}
|
||||
|
||||
const (
|
||||
numberStateLead = iota
|
||||
numberStateFloat
|
||||
numberStateExpLead
|
||||
numberStateExp
|
||||
)
|
||||
|
||||
func (l *lexer) scanNumber(state int) int {
|
||||
for {
|
||||
switch state {
|
||||
case numberStateLead, numberStateFloat:
|
||||
if ch := l.peek(); isNumber(ch) {
|
||||
l.offset++
|
||||
} else {
|
||||
switch ch {
|
||||
case '.':
|
||||
if state != numberStateLead {
|
||||
l.offset++
|
||||
return -l.offset
|
||||
}
|
||||
l.offset++
|
||||
state = numberStateFloat
|
||||
case 'e', 'E':
|
||||
l.offset++
|
||||
switch l.peek() {
|
||||
case '-', '+':
|
||||
l.offset++
|
||||
}
|
||||
state = numberStateExpLead
|
||||
default:
|
||||
if isIdent(ch, false) {
|
||||
l.offset++
|
||||
return -l.offset
|
||||
}
|
||||
return l.offset
|
||||
}
|
||||
}
|
||||
case numberStateExpLead, numberStateExp:
|
||||
if ch := l.peek(); !isNumber(ch) {
|
||||
if isIdent(ch, false) {
|
||||
l.offset++
|
||||
return -l.offset
|
||||
}
|
||||
if state == numberStateExpLead {
|
||||
return -l.offset
|
||||
}
|
||||
return l.offset
|
||||
}
|
||||
l.offset++
|
||||
state = numberStateExp
|
||||
default:
|
||||
panic(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) validNumber() bool {
|
||||
ch := l.peek()
|
||||
switch ch {
|
||||
case '+', '-':
|
||||
l.offset++
|
||||
ch = l.peek()
|
||||
}
|
||||
state := numberStateLead
|
||||
if ch == '.' {
|
||||
l.offset++
|
||||
ch = l.peek()
|
||||
state = numberStateFloat
|
||||
}
|
||||
return isNumber(ch) && l.scanNumber(state) == len(l.source)
|
||||
}
|
||||
|
||||
func (l *lexer) scanString(start int) (int, string) {
|
||||
var decode bool
|
||||
var controls int
|
||||
unquote := func(src string, quote bool) (string, error) {
|
||||
if !decode {
|
||||
if quote {
|
||||
return src, nil
|
||||
}
|
||||
return src[1 : len(src)-1], nil
|
||||
}
|
||||
var buf []byte
|
||||
if !quote && controls == 0 {
|
||||
buf = []byte(src)
|
||||
} else {
|
||||
buf = quoteAndEscape(src, quote, controls)
|
||||
}
|
||||
if err := json.Unmarshal(buf, &src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
for i := l.offset; i < len(l.source); i++ {
|
||||
ch := l.source[i]
|
||||
switch ch {
|
||||
case '\\':
|
||||
if i++; i >= len(l.source) {
|
||||
break
|
||||
}
|
||||
switch l.source[i] {
|
||||
case 'u':
|
||||
for j := 1; j <= 4; j++ {
|
||||
if i+j >= len(l.source) || !isHex(l.source[i+j]) {
|
||||
l.offset = i + j
|
||||
l.token = l.source[i-1 : l.offset]
|
||||
return tokInvalidEscapeSequence, ""
|
||||
}
|
||||
}
|
||||
i += 4
|
||||
fallthrough
|
||||
case '"', '/', '\\', 'b', 'f', 'n', 'r', 't':
|
||||
decode = true
|
||||
case '(':
|
||||
if !l.inString {
|
||||
l.inString = true
|
||||
return tokStringStart, ""
|
||||
}
|
||||
if i == l.offset+1 {
|
||||
l.offset += 2
|
||||
l.inString = false
|
||||
return tokStringQuery, ""
|
||||
}
|
||||
l.offset = i - 1
|
||||
l.token = l.source[start:l.offset]
|
||||
str, err := unquote(l.token, true)
|
||||
if err != nil {
|
||||
return tokInvalid, ""
|
||||
}
|
||||
return tokString, str
|
||||
default:
|
||||
l.offset = i + 1
|
||||
l.token = l.source[l.offset-2 : l.offset]
|
||||
return tokInvalidEscapeSequence, ""
|
||||
}
|
||||
case '"':
|
||||
if !l.inString {
|
||||
l.offset = i + 1
|
||||
l.token = l.source[start:l.offset]
|
||||
str, err := unquote(l.token, false)
|
||||
if err != nil {
|
||||
return tokInvalid, ""
|
||||
}
|
||||
return tokString, str
|
||||
}
|
||||
if i > l.offset {
|
||||
l.offset = i
|
||||
l.token = l.source[start:l.offset]
|
||||
str, err := unquote(l.token, true)
|
||||
if err != nil {
|
||||
return tokInvalid, ""
|
||||
}
|
||||
return tokString, str
|
||||
}
|
||||
l.inString = false
|
||||
l.offset = i + 1
|
||||
return tokStringEnd, ""
|
||||
default:
|
||||
if !decode {
|
||||
decode = ch > '~'
|
||||
}
|
||||
if ch < ' ' { // ref: unquoteBytes in encoding/json
|
||||
controls++
|
||||
}
|
||||
}
|
||||
}
|
||||
l.offset = len(l.source)
|
||||
l.token = ""
|
||||
return tokUnterminatedString, ""
|
||||
}
|
||||
|
||||
func quoteAndEscape(src string, quote bool, controls int) []byte {
|
||||
size := len(src) + controls*5
|
||||
if quote {
|
||||
size += 2
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
var j int
|
||||
if quote {
|
||||
buf[0] = '"'
|
||||
buf[len(buf)-1] = '"'
|
||||
j++
|
||||
}
|
||||
for i := range len(src) {
|
||||
if ch := src[i]; ch < ' ' {
|
||||
const hex = "0123456789abcdef"
|
||||
copy(buf[j:], `\u00`)
|
||||
buf[j+4] = hex[ch>>4]
|
||||
buf[j+5] = hex[ch&0xF]
|
||||
j += 6
|
||||
} else {
|
||||
buf[j] = ch
|
||||
j++
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// ParseError represents a description of a query parsing error.
|
||||
type ParseError struct {
|
||||
Offset int // the error occurred after reading Offset bytes
|
||||
Token string // the Token that caused the error (may be empty)
|
||||
tokenType int
|
||||
}
|
||||
|
||||
func (err *ParseError) Error() string {
|
||||
switch err.tokenType {
|
||||
case eof:
|
||||
return "unexpected EOF"
|
||||
case tokInvalid:
|
||||
return "invalid token " + jsonMarshal(err.Token)
|
||||
case tokInvalidEscapeSequence:
|
||||
return `invalid escape sequence "` + err.Token + `" in string literal`
|
||||
case tokUnterminatedString:
|
||||
return "unterminated string literal"
|
||||
default:
|
||||
return "unexpected token " + jsonMarshal(err.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) Error(string) {
|
||||
offset, token := l.offset, l.token
|
||||
if l.tokenType != eof && l.tokenType < utf8.RuneSelf {
|
||||
token = string(rune(l.tokenType))
|
||||
}
|
||||
l.err = &ParseError{offset, token, l.tokenType}
|
||||
}
|
||||
|
||||
func isWhite(ch byte) bool {
|
||||
switch ch {
|
||||
case '\t', '\n', '\r', ' ':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isIdent(ch byte, tail bool) bool {
|
||||
return 'a' <= ch && ch <= 'z' ||
|
||||
'A' <= ch && ch <= 'Z' || ch == '_' ||
|
||||
tail && isNumber(ch)
|
||||
}
|
||||
|
||||
func isHex(ch byte) bool {
|
||||
return 'a' <= ch && ch <= 'f' ||
|
||||
'A' <= ch && ch <= 'F' ||
|
||||
isNumber(ch)
|
||||
}
|
||||
|
||||
func isNumber(ch byte) bool {
|
||||
return '0' <= ch && ch <= '9'
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ModuleLoader is the interface for loading modules.
|
||||
//
|
||||
// Implement following optional methods. Use [NewModuleLoader] to load local modules.
|
||||
//
|
||||
// LoadInitModules() ([]*Query, error)
|
||||
// LoadModule(string) (*Query, error)
|
||||
// LoadModuleWithMeta(string, map[string]any) (*Query, error)
|
||||
// LoadJSON(string) (any, error)
|
||||
// LoadJSONWithMeta(string, map[string]any) (any, error)
|
||||
type ModuleLoader any
|
||||
|
||||
// NewModuleLoader creates a new [ModuleLoader] loading local modules in the paths.
|
||||
// Note that user can load modules outside the paths using "search" path of metadata.
|
||||
// Empty paths are ignored, so specify "." for the current working directory.
|
||||
func NewModuleLoader(paths []string) ModuleLoader {
|
||||
ps := make([]string, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
if path = resolvePath(path, ""); path != "" {
|
||||
ps = append(ps, path)
|
||||
}
|
||||
}
|
||||
return &moduleLoader{ps}
|
||||
}
|
||||
|
||||
type moduleLoader struct {
|
||||
paths []string
|
||||
}
|
||||
|
||||
func (l *moduleLoader) LoadInitModules() ([]*Query, error) {
|
||||
var qs []*Query
|
||||
for _, path := range l.paths {
|
||||
if filepath.Base(path) != ".jq" {
|
||||
continue
|
||||
}
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
cnt, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q, err := parseModule(string(cnt), filepath.Dir(path))
|
||||
if err != nil {
|
||||
return nil, &queryParseError{path, string(cnt), err}
|
||||
}
|
||||
qs = append(qs, q)
|
||||
}
|
||||
return qs, nil
|
||||
}
|
||||
|
||||
func (l *moduleLoader) LoadModuleWithMeta(name string, meta map[string]any) (*Query, error) {
|
||||
path, err := l.lookupModule(name, ".jq", meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cnt, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q, err := parseModule(string(cnt), filepath.Dir(path))
|
||||
if err != nil {
|
||||
return nil, &queryParseError{path, string(cnt), err}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (l *moduleLoader) LoadJSONWithMeta(name string, meta map[string]any) (any, error) {
|
||||
path, err := l.lookupModule(name, ".json", meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
vals := []any{}
|
||||
dec := json.NewDecoder(f)
|
||||
dec.UseNumber()
|
||||
for {
|
||||
var val any
|
||||
if err := dec.Decode(&val); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cnt, er := io.ReadAll(f)
|
||||
if er != nil {
|
||||
return nil, er
|
||||
}
|
||||
return nil, &jsonParseError{path, string(cnt), err}
|
||||
}
|
||||
vals = append(vals, val)
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
func (l *moduleLoader) lookupModule(name, extension string, meta map[string]any) (string, error) {
|
||||
paths := l.paths
|
||||
if path, ok := meta["search"].(string); ok {
|
||||
if path = resolvePath(path, ""); path != "" {
|
||||
paths = append([]string{path}, paths...)
|
||||
}
|
||||
}
|
||||
for _, base := range paths {
|
||||
path := filepath.Join(base, name+extension)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, err
|
||||
}
|
||||
path = filepath.Join(base, name, filepath.Base(name)+extension)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path, err
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("module not found: %q", name)
|
||||
}
|
||||
|
||||
func parseModule(cnt, dir string) (*Query, error) {
|
||||
q, err := Parse(cnt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, i := range q.Imports {
|
||||
if i.Meta != nil {
|
||||
for _, e := range i.Meta.KeyVals {
|
||||
if e.Key == "search" || e.KeyString == "search" {
|
||||
if path, ok := e.Val.toString(); ok {
|
||||
if path = resolvePath(path, dir); path != "" {
|
||||
e.Val.Str = path
|
||||
} else {
|
||||
e.Val.Null = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func resolvePath(path, dir string) string {
|
||||
switch {
|
||||
case filepath.IsAbs(path):
|
||||
return path
|
||||
case strings.HasPrefix(path, "~/"):
|
||||
dir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(dir, path[2:])
|
||||
case strings.HasPrefix(path, "$ORIGIN/"):
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
exe, err = filepath.EvalSymlinks(exe)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(filepath.Dir(exe), path[8:])
|
||||
default:
|
||||
return filepath.Join(dir, path)
|
||||
}
|
||||
}
|
||||
+557
@@ -0,0 +1,557 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Operator ...
|
||||
type Operator int
|
||||
|
||||
// Operators ...
|
||||
const (
|
||||
OpPipe Operator = iota + 1
|
||||
OpComma
|
||||
OpAdd
|
||||
OpSub
|
||||
OpMul
|
||||
OpDiv
|
||||
OpMod
|
||||
OpEq
|
||||
OpNe
|
||||
OpGt
|
||||
OpLt
|
||||
OpGe
|
||||
OpLe
|
||||
OpAnd
|
||||
OpOr
|
||||
OpAlt
|
||||
OpAssign
|
||||
OpModify
|
||||
OpUpdateAdd
|
||||
OpUpdateSub
|
||||
OpUpdateMul
|
||||
OpUpdateDiv
|
||||
OpUpdateMod
|
||||
OpUpdateAlt
|
||||
)
|
||||
|
||||
// String implements [fmt.Stringer].
|
||||
func (op Operator) String() string {
|
||||
switch op {
|
||||
case OpPipe:
|
||||
return "|"
|
||||
case OpComma:
|
||||
return ","
|
||||
case OpAdd:
|
||||
return "+"
|
||||
case OpSub:
|
||||
return "-"
|
||||
case OpMul:
|
||||
return "*"
|
||||
case OpDiv:
|
||||
return "/"
|
||||
case OpMod:
|
||||
return "%"
|
||||
case OpEq:
|
||||
return "=="
|
||||
case OpNe:
|
||||
return "!="
|
||||
case OpGt:
|
||||
return ">"
|
||||
case OpLt:
|
||||
return "<"
|
||||
case OpGe:
|
||||
return ">="
|
||||
case OpLe:
|
||||
return "<="
|
||||
case OpAnd:
|
||||
return "and"
|
||||
case OpOr:
|
||||
return "or"
|
||||
case OpAlt:
|
||||
return "//"
|
||||
case OpAssign:
|
||||
return "="
|
||||
case OpModify:
|
||||
return "|="
|
||||
case OpUpdateAdd:
|
||||
return "+="
|
||||
case OpUpdateSub:
|
||||
return "-="
|
||||
case OpUpdateMul:
|
||||
return "*="
|
||||
case OpUpdateDiv:
|
||||
return "/="
|
||||
case OpUpdateMod:
|
||||
return "%="
|
||||
case OpUpdateAlt:
|
||||
return "//="
|
||||
default:
|
||||
panic(op)
|
||||
}
|
||||
}
|
||||
|
||||
// GoString implements [fmt.GoStringer].
|
||||
func (op Operator) GoString() (str string) {
|
||||
defer func() { str = "gojq." + str }()
|
||||
switch op {
|
||||
case Operator(0):
|
||||
return "Operator(0)"
|
||||
case OpPipe:
|
||||
return "OpPipe"
|
||||
case OpComma:
|
||||
return "OpComma"
|
||||
case OpAdd:
|
||||
return "OpAdd"
|
||||
case OpSub:
|
||||
return "OpSub"
|
||||
case OpMul:
|
||||
return "OpMul"
|
||||
case OpDiv:
|
||||
return "OpDiv"
|
||||
case OpMod:
|
||||
return "OpMod"
|
||||
case OpEq:
|
||||
return "OpEq"
|
||||
case OpNe:
|
||||
return "OpNe"
|
||||
case OpGt:
|
||||
return "OpGt"
|
||||
case OpLt:
|
||||
return "OpLt"
|
||||
case OpGe:
|
||||
return "OpGe"
|
||||
case OpLe:
|
||||
return "OpLe"
|
||||
case OpAnd:
|
||||
return "OpAnd"
|
||||
case OpOr:
|
||||
return "OpOr"
|
||||
case OpAlt:
|
||||
return "OpAlt"
|
||||
case OpAssign:
|
||||
return "OpAssign"
|
||||
case OpModify:
|
||||
return "OpModify"
|
||||
case OpUpdateAdd:
|
||||
return "OpUpdateAdd"
|
||||
case OpUpdateSub:
|
||||
return "OpUpdateSub"
|
||||
case OpUpdateMul:
|
||||
return "OpUpdateMul"
|
||||
case OpUpdateDiv:
|
||||
return "OpUpdateDiv"
|
||||
case OpUpdateMod:
|
||||
return "OpUpdateMod"
|
||||
case OpUpdateAlt:
|
||||
return "OpUpdateAlt"
|
||||
default:
|
||||
panic(op)
|
||||
}
|
||||
}
|
||||
|
||||
func (op Operator) getFunc() string {
|
||||
switch op {
|
||||
case OpAdd, OpUpdateAdd:
|
||||
return "_add"
|
||||
case OpSub, OpUpdateSub:
|
||||
return "_subtract"
|
||||
case OpMul, OpUpdateMul:
|
||||
return "_multiply"
|
||||
case OpDiv, OpUpdateDiv:
|
||||
return "_divide"
|
||||
case OpMod, OpUpdateMod:
|
||||
return "_modulo"
|
||||
case OpEq:
|
||||
return "_equal"
|
||||
case OpNe:
|
||||
return "_notequal"
|
||||
case OpGt:
|
||||
return "_greater"
|
||||
case OpLt:
|
||||
return "_less"
|
||||
case OpGe:
|
||||
return "_greatereq"
|
||||
case OpLe:
|
||||
return "_lesseq"
|
||||
case OpAssign:
|
||||
return "_assign"
|
||||
case OpModify:
|
||||
return "_modify"
|
||||
case OpUpdateAlt:
|
||||
return "_alternative"
|
||||
default:
|
||||
panic(op)
|
||||
}
|
||||
}
|
||||
|
||||
func binopTypeSwitch[T any](
|
||||
l, r any,
|
||||
callbackInts func(_, _ int) T,
|
||||
callbackFloats func(_, _ float64) T,
|
||||
callbackBigInts func(_, _ *big.Int) T,
|
||||
callbackStrings func(_, _ string) T,
|
||||
callbackArrays func(_, _ []any) T,
|
||||
callbackMaps func(_, _ map[string]any) T,
|
||||
fallback func(_, _ any) T) T {
|
||||
if n, ok := l.(json.Number); ok {
|
||||
l = parseNumber(n)
|
||||
}
|
||||
if n, ok := r.(json.Number); ok {
|
||||
r = parseNumber(n)
|
||||
}
|
||||
switch l := l.(type) {
|
||||
case int:
|
||||
switch r := r.(type) {
|
||||
case int:
|
||||
return callbackInts(l, r)
|
||||
case float64:
|
||||
return callbackFloats(float64(l), r)
|
||||
case *big.Int:
|
||||
return callbackBigInts(big.NewInt(int64(l)), r)
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
case float64:
|
||||
switch r := r.(type) {
|
||||
case int:
|
||||
return callbackFloats(l, float64(r))
|
||||
case float64:
|
||||
return callbackFloats(l, r)
|
||||
case *big.Int:
|
||||
return callbackFloats(l, bigToFloat(r))
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
case *big.Int:
|
||||
switch r := r.(type) {
|
||||
case int:
|
||||
return callbackBigInts(l, big.NewInt(int64(r)))
|
||||
case float64:
|
||||
return callbackFloats(bigToFloat(l), r)
|
||||
case *big.Int:
|
||||
return callbackBigInts(l, r)
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
case string:
|
||||
switch r := r.(type) {
|
||||
case string:
|
||||
return callbackStrings(l, r)
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
case []any:
|
||||
switch r := r.(type) {
|
||||
case []any:
|
||||
return callbackArrays(l, r)
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
case map[string]any:
|
||||
switch r := r.(type) {
|
||||
case map[string]any:
|
||||
return callbackMaps(l, r)
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
default:
|
||||
return fallback(l, r)
|
||||
}
|
||||
}
|
||||
|
||||
func funcOpPlus(v any) any {
|
||||
switch v := v.(type) {
|
||||
case int:
|
||||
return v
|
||||
case float64:
|
||||
return v
|
||||
case *big.Int:
|
||||
return v
|
||||
case json.Number:
|
||||
return v
|
||||
default:
|
||||
return &unaryTypeError{"plus", v}
|
||||
}
|
||||
}
|
||||
|
||||
func negate(v int) any {
|
||||
if v == math.MinInt {
|
||||
return new(big.Int).Neg(big.NewInt(int64(v)))
|
||||
}
|
||||
return -v
|
||||
}
|
||||
|
||||
func funcOpNegate(v any) any {
|
||||
switch v := v.(type) {
|
||||
case int:
|
||||
return negate(v)
|
||||
case float64:
|
||||
return -v
|
||||
case *big.Int:
|
||||
return new(big.Int).Neg(v)
|
||||
case json.Number:
|
||||
if strings.HasPrefix(v.String(), "-") {
|
||||
return v[1:]
|
||||
}
|
||||
return "-" + v
|
||||
default:
|
||||
return &unaryTypeError{"negate", v}
|
||||
}
|
||||
}
|
||||
|
||||
func funcOpAdd(_, l, r any) any {
|
||||
return binopTypeSwitch(l, r,
|
||||
func(l, r int) any {
|
||||
if v := l + r; (v >= l) == (r >= 0) {
|
||||
return v
|
||||
}
|
||||
x, y := big.NewInt(int64(l)), big.NewInt(int64(r))
|
||||
return x.Add(x, y)
|
||||
},
|
||||
func(l, r float64) any { return l + r },
|
||||
func(l, r *big.Int) any { return new(big.Int).Add(l, r) },
|
||||
func(l, r string) any { return l + r },
|
||||
func(l, r []any) any {
|
||||
if len(l) == 0 {
|
||||
return r
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return l
|
||||
}
|
||||
v := make([]any, len(l)+len(r))
|
||||
copy(v, l)
|
||||
copy(v[len(l):], r)
|
||||
return v
|
||||
},
|
||||
func(l, r map[string]any) any {
|
||||
if len(l) == 0 {
|
||||
return r
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return l
|
||||
}
|
||||
m := make(map[string]any, len(l)+len(r))
|
||||
maps.Copy(m, l)
|
||||
maps.Copy(m, r)
|
||||
return m
|
||||
},
|
||||
func(l, r any) any {
|
||||
if l == nil {
|
||||
return r
|
||||
}
|
||||
if r == nil {
|
||||
return l
|
||||
}
|
||||
return &binopTypeError{"add", l, r}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func funcOpSub(_, l, r any) any {
|
||||
return binopTypeSwitch(l, r,
|
||||
func(l, r int) any {
|
||||
if v := l - r; (v <= l) == (r >= 0) {
|
||||
return v
|
||||
}
|
||||
x, y := big.NewInt(int64(l)), big.NewInt(int64(r))
|
||||
return x.Sub(x, y)
|
||||
},
|
||||
func(l, r float64) any { return l - r },
|
||||
func(l, r *big.Int) any { return new(big.Int).Sub(l, r) },
|
||||
func(l, r string) any { return &binopTypeError{"subtract", l, r} },
|
||||
func(l, r []any) any {
|
||||
v := make([]any, 0, len(l))
|
||||
L:
|
||||
for _, l := range l {
|
||||
for _, r := range r {
|
||||
if Compare(l, r) == 0 {
|
||||
continue L
|
||||
}
|
||||
}
|
||||
v = append(v, l)
|
||||
}
|
||||
return v
|
||||
},
|
||||
func(l, r map[string]any) any { return &binopTypeError{"subtract", l, r} },
|
||||
func(l, r any) any { return &binopTypeError{"subtract", l, r} },
|
||||
)
|
||||
}
|
||||
|
||||
func funcOpMul(_, l, r any) any {
|
||||
return binopTypeSwitch(l, r,
|
||||
func(l, r int) any {
|
||||
if r == -1 {
|
||||
return negate(l)
|
||||
}
|
||||
if v := l * r; r == 0 || v/r == l {
|
||||
return v
|
||||
}
|
||||
x, y := big.NewInt(int64(l)), big.NewInt(int64(r))
|
||||
return x.Mul(x, y)
|
||||
},
|
||||
func(l, r float64) any { return l * r },
|
||||
func(l, r *big.Int) any { return new(big.Int).Mul(l, r) },
|
||||
func(l, r string) any { return &binopTypeError{"multiply", l, r} },
|
||||
func(l, r []any) any { return &binopTypeError{"multiply", l, r} },
|
||||
deepMergeObjects,
|
||||
func(l, r any) any {
|
||||
if l, ok := l.(string); ok {
|
||||
if r, ok := toFloat(r); ok {
|
||||
return repeatString(l, r)
|
||||
}
|
||||
}
|
||||
if r, ok := r.(string); ok {
|
||||
if l, ok := toFloat(l); ok {
|
||||
return repeatString(r, l)
|
||||
}
|
||||
}
|
||||
return &binopTypeError{"multiply", l, r}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func deepMergeObjects(l, r map[string]any) any {
|
||||
m := make(map[string]any, len(l)+len(r))
|
||||
maps.Copy(m, l)
|
||||
for k, v := range r {
|
||||
if mk, ok := m[k]; ok {
|
||||
if mk, ok := mk.(map[string]any); ok {
|
||||
if w, ok := v.(map[string]any); ok {
|
||||
v = deepMergeObjects(mk, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func repeatString(s string, n float64) any {
|
||||
if lt(n, 0) {
|
||||
return nil
|
||||
}
|
||||
c := int(min(n, math.MaxInt32))
|
||||
if uint64(len(s))*uint64(c) >= math.MaxInt32 {
|
||||
return &repeatStringTooLargeError{s, n}
|
||||
}
|
||||
return strings.Repeat(s, c)
|
||||
}
|
||||
|
||||
func funcOpDiv(_, l, r any) any {
|
||||
return binopTypeSwitch(l, r,
|
||||
func(l, r int) any {
|
||||
switch r {
|
||||
case 0:
|
||||
return &zeroDivisionError{l, r}
|
||||
case -1:
|
||||
return negate(l)
|
||||
default:
|
||||
if l%r == 0 {
|
||||
return l / r
|
||||
}
|
||||
return float64(l) / float64(r)
|
||||
}
|
||||
},
|
||||
func(l, r float64) any {
|
||||
if r == 0.0 {
|
||||
return &zeroDivisionError{l, r}
|
||||
}
|
||||
return l / r
|
||||
},
|
||||
func(l, r *big.Int) any {
|
||||
if r.Sign() == 0 {
|
||||
return &zeroDivisionError{l, r}
|
||||
}
|
||||
d, m := new(big.Int).DivMod(l, r, new(big.Int))
|
||||
if m.Sign() == 0 {
|
||||
return d
|
||||
}
|
||||
return bigToFloat(l) / bigToFloat(r)
|
||||
},
|
||||
func(l, r string) any {
|
||||
if l == "" {
|
||||
return []any{}
|
||||
}
|
||||
xs := strings.Split(l, r)
|
||||
vs := make([]any, len(xs))
|
||||
for i, x := range xs {
|
||||
vs[i] = x
|
||||
}
|
||||
return vs
|
||||
},
|
||||
func(l, r []any) any { return &binopTypeError{"divide", l, r} },
|
||||
func(l, r map[string]any) any { return &binopTypeError{"divide", l, r} },
|
||||
func(l, r any) any { return &binopTypeError{"divide", l, r} },
|
||||
)
|
||||
}
|
||||
|
||||
func funcOpMod(_, l, r any) any {
|
||||
return binopTypeSwitch(l, r,
|
||||
func(l, r int) any {
|
||||
switch r {
|
||||
case 0:
|
||||
return &zeroModuloError{l, r}
|
||||
case -1:
|
||||
return 0
|
||||
default:
|
||||
return l % r
|
||||
}
|
||||
},
|
||||
func(l, r float64) any {
|
||||
if math.IsNaN(l) || math.IsNaN(r) {
|
||||
return math.NaN()
|
||||
}
|
||||
ri := floatToInt(r)
|
||||
if ri == 0 {
|
||||
return &zeroModuloError{l, r}
|
||||
}
|
||||
return floatToInt(l) % ri
|
||||
},
|
||||
func(l, r *big.Int) any {
|
||||
if r.Sign() == 0 {
|
||||
return &zeroModuloError{l, r}
|
||||
}
|
||||
return new(big.Int).Rem(l, r)
|
||||
},
|
||||
func(l, r string) any { return &binopTypeError{"modulo", l, r} },
|
||||
func(l, r []any) any { return &binopTypeError{"modulo", l, r} },
|
||||
func(l, r map[string]any) any { return &binopTypeError{"modulo", l, r} },
|
||||
func(l, r any) any { return &binopTypeError{"modulo", l, r} },
|
||||
)
|
||||
}
|
||||
|
||||
func funcOpAlt(_, l, r any) any {
|
||||
if l == nil || l == false {
|
||||
return r
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func funcOpEq(_, l, r any) any {
|
||||
return Compare(l, r) == 0
|
||||
}
|
||||
|
||||
func funcOpNe(_, l, r any) any {
|
||||
return Compare(l, r) != 0
|
||||
}
|
||||
|
||||
func funcOpGt(_, l, r any) any {
|
||||
return Compare(l, r) > 0
|
||||
}
|
||||
|
||||
func funcOpLt(_, l, r any) any {
|
||||
return Compare(l, r) < 0
|
||||
}
|
||||
|
||||
func funcOpGe(_, l, r any) any {
|
||||
return Compare(l, r) >= 0
|
||||
}
|
||||
|
||||
func funcOpLe(_, l, r any) any {
|
||||
return Compare(l, r) <= 0
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package gojq
|
||||
|
||||
import "fmt"
|
||||
|
||||
// CompilerOption is a compiler option.
|
||||
type CompilerOption func(*compiler)
|
||||
|
||||
// WithModuleLoader is a compiler option for module loader.
|
||||
// If you want to load modules from the filesystem, use [NewModuleLoader].
|
||||
func WithModuleLoader(moduleLoader ModuleLoader) CompilerOption {
|
||||
return func(c *compiler) {
|
||||
c.moduleLoader = moduleLoader
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvironLoader is a compiler option for environment variables loader.
|
||||
// The OS environment variables are not accessible by default due to security
|
||||
// reasons. You can specify [os.Environ] as argument if you allow to access.
|
||||
func WithEnvironLoader(environLoader func() []string) CompilerOption {
|
||||
return func(c *compiler) {
|
||||
c.environLoader = environLoader
|
||||
}
|
||||
}
|
||||
|
||||
// WithVariables is a compiler option for variable names. The variables can be
|
||||
// used in the query. You have to give the values to [*Code.Run] in the same order.
|
||||
func WithVariables(variables []string) CompilerOption {
|
||||
return func(c *compiler) {
|
||||
c.variables = variables
|
||||
}
|
||||
}
|
||||
|
||||
// WithFunction is a compiler option for adding a custom internal function.
|
||||
// Specify the minimum and maximum count of the function arguments. These
|
||||
// values should satisfy 0 <= minarity <= maxarity <= 30, otherwise panics.
|
||||
// On handling numbers, take account of int, float64, *big.Int, and json.Number.
|
||||
// These are the number types you are allowed to return, so do not return int64.
|
||||
// Refer to [ValueError] to return a value error just like built-in error
|
||||
// function. If you want to emit multiple values, call the empty function,
|
||||
// accept a filter for its argument, or call another built-in function, then
|
||||
// use LoadInitModules of the module loader.
|
||||
func WithFunction(name string, minarity, maxarity int, f func(any, []any) any) CompilerOption {
|
||||
return withFunction(name, minarity, maxarity, false, f)
|
||||
}
|
||||
|
||||
// WithIterFunction is a compiler option for adding a custom iterator function.
|
||||
// This is like the [WithFunction] option, but you can add a function which
|
||||
// returns an Iter to emit multiple values. You cannot define both iterator and
|
||||
// non-iterator functions of the same name (with possibly different arities).
|
||||
// See also [NewIter], which can be used to convert values or an error to an Iter.
|
||||
func WithIterFunction(name string, minarity, maxarity int, f func(any, []any) Iter) CompilerOption {
|
||||
return withFunction(name, minarity, maxarity, true,
|
||||
func(v any, args []any) any {
|
||||
return f(v, args)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func withFunction(name string, minarity, maxarity int, iter bool, f func(any, []any) any) CompilerOption {
|
||||
if !(0 <= minarity && minarity <= maxarity && maxarity <= 30) {
|
||||
panic(fmt.Sprintf("invalid arity for %q: %d, %d", name, minarity, maxarity))
|
||||
}
|
||||
argcount := 1<<(maxarity+1) - 1<<minarity
|
||||
return func(c *compiler) {
|
||||
if c.customFuncs == nil {
|
||||
c.customFuncs = make(map[string]function)
|
||||
}
|
||||
if fn, ok := c.customFuncs[name]; ok {
|
||||
if fn.iter != iter {
|
||||
panic(fmt.Sprintf("cannot define both iterator and non-iterator functions for %q", name))
|
||||
}
|
||||
c.customFuncs[name] = function{
|
||||
argcount | fn.argcount, iter,
|
||||
func(x any, xs []any) any {
|
||||
if argcount&(1<<len(xs)) != 0 {
|
||||
return f(x, xs)
|
||||
}
|
||||
return fn.callback(x, xs)
|
||||
},
|
||||
}
|
||||
} else {
|
||||
c.customFuncs[name] = function{argcount, iter, f}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithInputIter is a compiler option for input iterator used by input(s)/0.
|
||||
// Note that input and inputs functions are not allowed by default. We have
|
||||
// to distinguish the query input and the values for input(s) functions. For
|
||||
// example, consider using inputs with --null-input. If you want to allow
|
||||
// input(s) functions, create an [Iter] and use WithInputIter option.
|
||||
func WithInputIter(inputIter Iter) CompilerOption {
|
||||
return func(c *compiler) {
|
||||
c.inputIter = inputIter
|
||||
}
|
||||
}
|
||||
+1506
File diff suppressed because it is too large
Load Diff
+669
@@ -0,0 +1,669 @@
|
||||
%{
|
||||
package gojq
|
||||
|
||||
func reverseFuncDef(xs []*FuncDef) []*FuncDef {
|
||||
for i, j := 0, len(xs)-1; i < j; i, j = i+1, j-1 {
|
||||
xs[i], xs[j] = xs[j], xs[i]
|
||||
}
|
||||
return xs
|
||||
}
|
||||
|
||||
func prependFuncDef(xs []*FuncDef, x *FuncDef) []*FuncDef {
|
||||
xs = append(xs, nil)
|
||||
copy(xs[1:], xs)
|
||||
xs[0] = x
|
||||
return xs
|
||||
}
|
||||
%}
|
||||
|
||||
%union {
|
||||
value any
|
||||
token string
|
||||
operator Operator
|
||||
}
|
||||
|
||||
%type<value> program header imports import meta body funcdefs funcdef funcargs query
|
||||
%type<value> bindpatterns pattern arraypatterns objectpatterns objectpattern
|
||||
%type<value> expr term string stringparts suffix args ifelifs ifelse trycatch
|
||||
%type<value> objectkeyvals objectkeyval objectval
|
||||
%type<value> constterm constobject constobjectkeyvals constobjectkeyval constarray constarrayelems
|
||||
%type<token> tokIdentVariable tokIdentModuleIdent tokVariableModuleVariable tokKeyword objectkey
|
||||
%token<operator> tokAltOp tokUpdateOp tokDestAltOp tokCompareOp
|
||||
%token<token> tokOrOp tokAndOp tokModule tokImport tokInclude tokDef tokAs tokLabel tokBreak
|
||||
%token<token> tokNull tokTrue tokFalse
|
||||
%token<token> tokIf tokThen tokElif tokElse tokEnd
|
||||
%token<token> tokTry tokCatch tokReduce tokForeach
|
||||
%token<token> tokIdent tokVariable tokModuleIdent tokModuleVariable
|
||||
%token<token> tokRecurse tokIndex tokNumber tokFormat
|
||||
%token<token> tokString tokStringStart tokStringQuery tokStringEnd
|
||||
%token<token> tokInvalid tokInvalidEscapeSequence tokUnterminatedString
|
||||
|
||||
%nonassoc tokFuncDefQuery tokExpr tokTerm
|
||||
%right '|'
|
||||
%left ','
|
||||
%right tokAltOp
|
||||
%nonassoc tokUpdateOp
|
||||
%left tokOrOp
|
||||
%left tokAndOp
|
||||
%nonassoc tokCompareOp
|
||||
%left '+' '-'
|
||||
%left '*' '/' '%'
|
||||
%nonassoc tokAs tokIndex '.' '?' tokEmptyCatch
|
||||
%nonassoc '[' tokTry tokCatch
|
||||
|
||||
%%
|
||||
|
||||
program
|
||||
: header imports body
|
||||
{
|
||||
query := $3.(*Query)
|
||||
query.Meta = $1.(*ConstObject)
|
||||
query.Imports = $2.([]*Import)
|
||||
yylex.(*lexer).result = query
|
||||
}
|
||||
|
||||
header
|
||||
:
|
||||
{
|
||||
$$ = (*ConstObject)(nil)
|
||||
}
|
||||
| tokModule constobject ';'
|
||||
{
|
||||
$$ = $2;
|
||||
}
|
||||
|
||||
imports
|
||||
:
|
||||
{
|
||||
$$ = []*Import(nil)
|
||||
}
|
||||
| imports import
|
||||
{
|
||||
$$ = append($1.([]*Import), $2.(*Import))
|
||||
}
|
||||
|
||||
import
|
||||
: tokImport tokString tokAs tokIdentVariable meta ';'
|
||||
{
|
||||
$$ = &Import{ImportPath: $2, ImportAlias: $4, Meta: $5.(*ConstObject)}
|
||||
}
|
||||
| tokInclude tokString meta ';'
|
||||
{
|
||||
$$ = &Import{IncludePath: $2, Meta: $3.(*ConstObject)}
|
||||
}
|
||||
|
||||
meta
|
||||
:
|
||||
{
|
||||
$$ = (*ConstObject)(nil)
|
||||
}
|
||||
| constobject
|
||||
|
||||
body
|
||||
: funcdefs
|
||||
{
|
||||
$$ = &Query{FuncDefs: reverseFuncDef($1.([]*FuncDef))}
|
||||
}
|
||||
| query
|
||||
|
||||
funcdefs
|
||||
:
|
||||
{
|
||||
$$ = []*FuncDef(nil)
|
||||
}
|
||||
| funcdef funcdefs
|
||||
{
|
||||
$$ = append($2.([]*FuncDef), $1.(*FuncDef))
|
||||
}
|
||||
|
||||
funcdef
|
||||
: tokDef tokIdent ':' query ';'
|
||||
{
|
||||
$$ = &FuncDef{Name: $2, Body: $4.(*Query)}
|
||||
}
|
||||
| tokDef tokIdent '(' funcargs ')' ':' query ';'
|
||||
{
|
||||
$$ = &FuncDef{$2, $4.([]string), $7.(*Query)}
|
||||
}
|
||||
|
||||
funcargs
|
||||
: tokIdentVariable
|
||||
{
|
||||
$$ = []string{$1}
|
||||
}
|
||||
| funcargs ';' tokIdentVariable
|
||||
{
|
||||
$$ = append($1.([]string), $3)
|
||||
}
|
||||
|
||||
tokIdentVariable
|
||||
: tokIdent
|
||||
| tokVariable
|
||||
|
||||
query
|
||||
: funcdef query %prec tokFuncDefQuery
|
||||
{
|
||||
query := $2.(*Query)
|
||||
query.FuncDefs = prependFuncDef(query.FuncDefs, $1.(*FuncDef))
|
||||
$$ = query
|
||||
}
|
||||
| query '|' query
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpPipe, Right: $3.(*Query)}
|
||||
}
|
||||
| query tokAs bindpatterns '|' query
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpPipe, Right: $5.(*Query), Patterns: $3.([]*Pattern)}
|
||||
}
|
||||
| tokLabel tokVariable '|' query
|
||||
{
|
||||
$$ = &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{$2, $4.(*Query)}}}
|
||||
}
|
||||
| query ',' query
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpComma, Right: $3.(*Query)}
|
||||
}
|
||||
| expr %prec tokExpr
|
||||
|
||||
expr
|
||||
: expr tokAltOp expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: $2, Right: $3.(*Query)}
|
||||
}
|
||||
| expr tokUpdateOp expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: $2, Right: $3.(*Query)}
|
||||
}
|
||||
| expr tokOrOp expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpOr, Right: $3.(*Query)}
|
||||
}
|
||||
| expr tokAndOp expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpAnd, Right: $3.(*Query)}
|
||||
}
|
||||
| expr tokCompareOp expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: $2, Right: $3.(*Query)}
|
||||
}
|
||||
| expr '+' expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpAdd, Right: $3.(*Query)}
|
||||
}
|
||||
| expr '-' expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpSub, Right: $3.(*Query)}
|
||||
}
|
||||
| expr '*' expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpMul, Right: $3.(*Query)}
|
||||
}
|
||||
| expr '/' expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpDiv, Right: $3.(*Query)}
|
||||
}
|
||||
| expr '%' expr
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpMod, Right: $3.(*Query)}
|
||||
}
|
||||
| term %prec tokTerm
|
||||
{
|
||||
$$ = &Query{Term: $1.(*Term)}
|
||||
}
|
||||
|
||||
bindpatterns
|
||||
: pattern
|
||||
{
|
||||
$$ = []*Pattern{$1.(*Pattern)}
|
||||
}
|
||||
| bindpatterns tokDestAltOp pattern
|
||||
{
|
||||
$$ = append($1.([]*Pattern), $3.(*Pattern))
|
||||
}
|
||||
|
||||
pattern
|
||||
: tokVariable
|
||||
{
|
||||
$$ = &Pattern{Name: $1}
|
||||
}
|
||||
| '[' arraypatterns ']'
|
||||
{
|
||||
$$ = &Pattern{Array: $2.([]*Pattern)}
|
||||
}
|
||||
| '{' objectpatterns '}'
|
||||
{
|
||||
$$ = &Pattern{Object: $2.([]*PatternObject)}
|
||||
}
|
||||
|
||||
arraypatterns
|
||||
: pattern
|
||||
{
|
||||
$$ = []*Pattern{$1.(*Pattern)}
|
||||
}
|
||||
| arraypatterns ',' pattern
|
||||
{
|
||||
$$ = append($1.([]*Pattern), $3.(*Pattern))
|
||||
}
|
||||
|
||||
objectpatterns
|
||||
: objectpattern
|
||||
{
|
||||
$$ = []*PatternObject{$1.(*PatternObject)}
|
||||
}
|
||||
| objectpatterns ',' objectpattern
|
||||
{
|
||||
$$ = append($1.([]*PatternObject), $3.(*PatternObject))
|
||||
}
|
||||
|
||||
objectpattern
|
||||
: objectkey ':' pattern
|
||||
{
|
||||
$$ = &PatternObject{Key: $1, Val: $3.(*Pattern)}
|
||||
}
|
||||
| string ':' pattern
|
||||
{
|
||||
$$ = &PatternObject{KeyString: $1.(*String), Val: $3.(*Pattern)}
|
||||
}
|
||||
| '(' query ')' ':' pattern
|
||||
{
|
||||
$$ = &PatternObject{KeyQuery: $2.(*Query), Val: $5.(*Pattern)}
|
||||
}
|
||||
| tokVariable
|
||||
{
|
||||
$$ = &PatternObject{Key: $1}
|
||||
}
|
||||
|
||||
term
|
||||
: '.'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeIdentity}
|
||||
}
|
||||
| tokRecurse
|
||||
{
|
||||
$$ = &Term{Type: TermTypeRecurse}
|
||||
}
|
||||
| tokIndex
|
||||
{
|
||||
$$ = &Term{Type: TermTypeIndex, Index: &Index{Name: $1}}
|
||||
}
|
||||
| '.' suffix
|
||||
{
|
||||
suffix := $2.(*Suffix)
|
||||
if suffix.Iter {
|
||||
$$ = &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{suffix}}
|
||||
} else {
|
||||
$$ = &Term{Type: TermTypeIndex, Index: suffix.Index}
|
||||
}
|
||||
}
|
||||
| '.' string
|
||||
{
|
||||
$$ = &Term{Type: TermTypeIndex, Index: &Index{Str: $2.(*String)}}
|
||||
}
|
||||
| tokNull
|
||||
{
|
||||
$$ = &Term{Type: TermTypeNull}
|
||||
}
|
||||
| tokTrue
|
||||
{
|
||||
$$ = &Term{Type: TermTypeTrue}
|
||||
}
|
||||
| tokFalse
|
||||
{
|
||||
$$ = &Term{Type: TermTypeFalse}
|
||||
}
|
||||
| tokIdentModuleIdent
|
||||
{
|
||||
$$ = &Term{Type: TermTypeFunc, Func: &Func{Name: $1}}
|
||||
}
|
||||
| tokIdentModuleIdent '(' args ')'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeFunc, Func: &Func{Name: $1, Args: $3.([]*Query)}}
|
||||
}
|
||||
| tokVariableModuleVariable
|
||||
{
|
||||
$$ = &Term{Type: TermTypeFunc, Func: &Func{Name: $1}}
|
||||
}
|
||||
| '{' '}'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeObject, Object: &Object{}}
|
||||
}
|
||||
| '{' objectkeyvals '}'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeObject, Object: &Object{$2.([]*ObjectKeyVal)}}
|
||||
}
|
||||
| '{' objectkeyvals ',' '}'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeObject, Object: &Object{$2.([]*ObjectKeyVal)}}
|
||||
}
|
||||
| '[' ']'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeArray, Array: &Array{}}
|
||||
}
|
||||
| '[' query ']'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeArray, Array: &Array{$2.(*Query)}}
|
||||
}
|
||||
| tokNumber
|
||||
{
|
||||
$$ = &Term{Type: TermTypeNumber, Number: $1}
|
||||
}
|
||||
| '+' term
|
||||
{
|
||||
$$ = &Term{Type: TermTypeUnary, Unary: &Unary{OpAdd, $2.(*Term)}}
|
||||
}
|
||||
| '-' term
|
||||
{
|
||||
$$ = &Term{Type: TermTypeUnary, Unary: &Unary{OpSub, $2.(*Term)}}
|
||||
}
|
||||
| tokFormat
|
||||
{
|
||||
$$ = &Term{Type: TermTypeFormat, Format: $1}
|
||||
}
|
||||
| tokFormat string
|
||||
{
|
||||
$$ = &Term{Type: TermTypeFormat, Format: $1, Str: $2.(*String)}
|
||||
}
|
||||
| string
|
||||
{
|
||||
$$ = &Term{Type: TermTypeString, Str: $1.(*String)}
|
||||
}
|
||||
| tokIf query tokThen query ifelifs ifelse tokEnd
|
||||
{
|
||||
$$ = &Term{Type: TermTypeIf, If: &If{$2.(*Query), $4.(*Query), $5.([]*IfElif), $6.(*Query)}}
|
||||
}
|
||||
| tokTry expr trycatch
|
||||
{
|
||||
$$ = &Term{Type: TermTypeTry, Try: &Try{$2.(*Query), $3.(*Query)}}
|
||||
}
|
||||
| tokReduce expr tokAs pattern '(' query ';' query ')'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeReduce, Reduce: &Reduce{$2.(*Query), $4.(*Pattern), $6.(*Query), $8.(*Query)}}
|
||||
}
|
||||
| tokForeach expr tokAs pattern '(' query ';' query ')'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeForeach, Foreach: &Foreach{$2.(*Query), $4.(*Pattern), $6.(*Query), $8.(*Query), nil}}
|
||||
}
|
||||
| tokForeach expr tokAs pattern '(' query ';' query ';' query ')'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeForeach, Foreach: &Foreach{$2.(*Query), $4.(*Pattern), $6.(*Query), $8.(*Query), $10.(*Query)}}
|
||||
}
|
||||
| tokBreak tokVariable
|
||||
{
|
||||
$$ = &Term{Type: TermTypeBreak, Break: $2}
|
||||
}
|
||||
| '(' query ')'
|
||||
{
|
||||
$$ = &Term{Type: TermTypeQuery, Query: $2.(*Query)}
|
||||
}
|
||||
| term tokIndex
|
||||
{
|
||||
$1.(*Term).SuffixList = append($1.(*Term).SuffixList, &Suffix{Index: &Index{Name: $2}})
|
||||
}
|
||||
| term suffix
|
||||
{
|
||||
$1.(*Term).SuffixList = append($1.(*Term).SuffixList, $2.(*Suffix))
|
||||
}
|
||||
| term '?'
|
||||
{
|
||||
$1.(*Term).SuffixList = append($1.(*Term).SuffixList, &Suffix{Optional: true})
|
||||
}
|
||||
| term '.' suffix
|
||||
{
|
||||
$1.(*Term).SuffixList = append($1.(*Term).SuffixList, $3.(*Suffix))
|
||||
}
|
||||
| term '.' string
|
||||
{
|
||||
$1.(*Term).SuffixList = append($1.(*Term).SuffixList, &Suffix{Index: &Index{Str: $3.(*String)}})
|
||||
}
|
||||
|
||||
string
|
||||
: tokString
|
||||
{
|
||||
$$ = &String{Str: $1}
|
||||
}
|
||||
| tokStringStart stringparts tokStringEnd
|
||||
{
|
||||
$$ = &String{Queries: $2.([]*Query)}
|
||||
}
|
||||
|
||||
stringparts
|
||||
:
|
||||
{
|
||||
$$ = []*Query{}
|
||||
}
|
||||
| stringparts tokString
|
||||
{
|
||||
$$ = append($1.([]*Query), &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: $2}}})
|
||||
}
|
||||
| stringparts tokStringQuery query ')'
|
||||
{
|
||||
yylex.(*lexer).inString = true
|
||||
$$ = append($1.([]*Query), &Query{Term: &Term{Type: TermTypeQuery, Query: $3.(*Query)}})
|
||||
}
|
||||
|
||||
tokIdentModuleIdent
|
||||
: tokIdent
|
||||
| tokModuleIdent
|
||||
|
||||
tokVariableModuleVariable
|
||||
: tokVariable
|
||||
| tokModuleVariable
|
||||
|
||||
suffix
|
||||
: '[' ']'
|
||||
{
|
||||
$$ = &Suffix{Iter: true}
|
||||
}
|
||||
| '[' query ']'
|
||||
{
|
||||
$$ = &Suffix{Index: &Index{Start: $2.(*Query)}}
|
||||
}
|
||||
| '[' query ':' ']'
|
||||
{
|
||||
$$ = &Suffix{Index: &Index{Start: $2.(*Query), IsSlice: true}}
|
||||
}
|
||||
| '[' ':' query ']'
|
||||
{
|
||||
$$ = &Suffix{Index: &Index{End: $3.(*Query), IsSlice: true}}
|
||||
}
|
||||
| '[' query ':' query ']'
|
||||
{
|
||||
$$ = &Suffix{Index: &Index{Start: $2.(*Query), End: $4.(*Query), IsSlice: true}}
|
||||
}
|
||||
|
||||
args
|
||||
: query
|
||||
{
|
||||
$$ = []*Query{$1.(*Query)}
|
||||
}
|
||||
| args ';' query
|
||||
{
|
||||
$$ = append($1.([]*Query), $3.(*Query))
|
||||
}
|
||||
|
||||
ifelifs
|
||||
:
|
||||
{
|
||||
$$ = []*IfElif(nil)
|
||||
}
|
||||
| ifelifs tokElif query tokThen query
|
||||
{
|
||||
$$ = append($1.([]*IfElif), &IfElif{$3.(*Query), $5.(*Query)})
|
||||
}
|
||||
|
||||
ifelse
|
||||
:
|
||||
{
|
||||
$$ = (*Query)(nil)
|
||||
}
|
||||
| tokElse query
|
||||
{
|
||||
$$ = $2
|
||||
}
|
||||
|
||||
trycatch
|
||||
: %prec tokEmptyCatch
|
||||
{
|
||||
$$ = (*Query)(nil)
|
||||
}
|
||||
| tokCatch expr
|
||||
{
|
||||
$$ = $2
|
||||
}
|
||||
|
||||
objectkeyvals
|
||||
: objectkeyval
|
||||
{
|
||||
$$ = []*ObjectKeyVal{$1.(*ObjectKeyVal)}
|
||||
}
|
||||
| objectkeyvals ',' objectkeyval
|
||||
{
|
||||
$$ = append($1.([]*ObjectKeyVal), $3.(*ObjectKeyVal))
|
||||
}
|
||||
|
||||
objectkeyval
|
||||
: objectkey ':' objectval
|
||||
{
|
||||
$$ = &ObjectKeyVal{Key: $1, Val: $3.(*Query)}
|
||||
}
|
||||
| string ':' objectval
|
||||
{
|
||||
$$ = &ObjectKeyVal{KeyString: $1.(*String), Val: $3.(*Query)}
|
||||
}
|
||||
| '(' query ')' ':' objectval
|
||||
{
|
||||
$$ = &ObjectKeyVal{KeyQuery: $2.(*Query), Val: $5.(*Query)}
|
||||
}
|
||||
| objectkey
|
||||
{
|
||||
$$ = &ObjectKeyVal{Key: $1}
|
||||
}
|
||||
| string
|
||||
{
|
||||
$$ = &ObjectKeyVal{KeyString: $1.(*String)}
|
||||
}
|
||||
|
||||
objectkey
|
||||
: tokIdent
|
||||
| tokVariable
|
||||
| tokKeyword
|
||||
|
||||
objectval
|
||||
: objectval '|' objectval
|
||||
{
|
||||
$$ = &Query{Left: $1.(*Query), Op: OpPipe, Right: $3.(*Query)}
|
||||
}
|
||||
| expr
|
||||
|
||||
constterm
|
||||
: constobject
|
||||
{
|
||||
$$ = &ConstTerm{Object: $1.(*ConstObject)}
|
||||
}
|
||||
| constarray
|
||||
{
|
||||
$$ = &ConstTerm{Array: $1.(*ConstArray)}
|
||||
}
|
||||
| tokNumber
|
||||
{
|
||||
$$ = &ConstTerm{Number: $1}
|
||||
}
|
||||
| tokString
|
||||
{
|
||||
$$ = &ConstTerm{Str: $1}
|
||||
}
|
||||
| tokNull
|
||||
{
|
||||
$$ = &ConstTerm{Null: true}
|
||||
}
|
||||
| tokTrue
|
||||
{
|
||||
$$ = &ConstTerm{True: true}
|
||||
}
|
||||
| tokFalse
|
||||
{
|
||||
$$ = &ConstTerm{False: true}
|
||||
}
|
||||
|
||||
constobject
|
||||
: '{' '}'
|
||||
{
|
||||
$$ = &ConstObject{}
|
||||
}
|
||||
| '{' constobjectkeyvals '}'
|
||||
{
|
||||
$$ = &ConstObject{$2.([]*ConstObjectKeyVal)}
|
||||
}
|
||||
| '{' constobjectkeyvals ',' '}'
|
||||
{
|
||||
$$ = &ConstObject{$2.([]*ConstObjectKeyVal)}
|
||||
}
|
||||
|
||||
constobjectkeyvals
|
||||
: constobjectkeyval
|
||||
{
|
||||
$$ = []*ConstObjectKeyVal{$1.(*ConstObjectKeyVal)}
|
||||
}
|
||||
| constobjectkeyvals ',' constobjectkeyval
|
||||
{
|
||||
$$ = append($1.([]*ConstObjectKeyVal), $3.(*ConstObjectKeyVal))
|
||||
}
|
||||
|
||||
constobjectkeyval
|
||||
: tokIdent ':' constterm
|
||||
{
|
||||
$$ = &ConstObjectKeyVal{Key: $1, Val: $3.(*ConstTerm)}
|
||||
}
|
||||
| tokKeyword ':' constterm
|
||||
{
|
||||
$$ = &ConstObjectKeyVal{Key: $1, Val: $3.(*ConstTerm)}
|
||||
}
|
||||
| tokString ':' constterm
|
||||
{
|
||||
$$ = &ConstObjectKeyVal{KeyString: $1, Val: $3.(*ConstTerm)}
|
||||
}
|
||||
|
||||
constarray
|
||||
: '[' ']'
|
||||
{
|
||||
$$ = &ConstArray{}
|
||||
}
|
||||
| '[' constarrayelems ']'
|
||||
{
|
||||
$$ = &ConstArray{$2.([]*ConstTerm)}
|
||||
}
|
||||
|
||||
constarrayelems
|
||||
: constterm
|
||||
{
|
||||
$$ = []*ConstTerm{$1.(*ConstTerm)}
|
||||
}
|
||||
| constarrayelems ',' constterm
|
||||
{
|
||||
$$ = append($1.([]*ConstTerm), $3.(*ConstTerm))
|
||||
}
|
||||
|
||||
tokKeyword
|
||||
: tokOrOp
|
||||
| tokAndOp
|
||||
| tokModule
|
||||
| tokImport
|
||||
| tokInclude
|
||||
| tokDef
|
||||
| tokAs
|
||||
| tokLabel
|
||||
| tokBreak
|
||||
| tokNull
|
||||
| tokTrue
|
||||
| tokFalse
|
||||
| tokIf
|
||||
| tokThen
|
||||
| tokElif
|
||||
| tokElse
|
||||
| tokEnd
|
||||
| tokTry
|
||||
| tokCatch
|
||||
| tokReduce
|
||||
| tokForeach
|
||||
|
||||
%%
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package gojq
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// Preview returns the preview string of v. The preview string is basically the
|
||||
// same as the jq-flavored JSON encoding returned by [Marshal], but is truncated
|
||||
// by 30 bytes, and more efficient than truncating the result of [Marshal].
|
||||
//
|
||||
// This method is used by error messages of built-in operators and functions,
|
||||
// and accepts only limited types (nil, bool, int, float64, *big.Int,
|
||||
// json.Number, string, []any, and map[string]any). Note that the maximum width
|
||||
// and trailing strings on truncation may be changed in the future.
|
||||
func Preview(v any) string {
|
||||
bs := jsonLimitedMarshal(v, 32)
|
||||
if l := 30; len(bs) > l {
|
||||
var trailing string
|
||||
switch v.(type) {
|
||||
case string:
|
||||
trailing = ` ..."`
|
||||
case []any:
|
||||
trailing = " ...]"
|
||||
case map[string]any:
|
||||
trailing = " ...}"
|
||||
default:
|
||||
trailing = " ..."
|
||||
}
|
||||
for len(bs) > l-len(trailing) {
|
||||
_, size := utf8.DecodeLastRune(bs)
|
||||
bs = bs[:len(bs)-size]
|
||||
}
|
||||
bs = append(bs, trailing...)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
func jsonLimitedMarshal(v any, n int) (bs []byte) {
|
||||
w := &limitedWriter{buf: make([]byte, n)}
|
||||
defer func() {
|
||||
_ = recover()
|
||||
bs = w.Bytes()
|
||||
}()
|
||||
(&encoder{w: w}).encode(v)
|
||||
return
|
||||
}
|
||||
|
||||
type limitedWriter struct {
|
||||
buf []byte
|
||||
off int
|
||||
}
|
||||
|
||||
func (w *limitedWriter) Write(bs []byte) (int, error) {
|
||||
n := copy(w.buf[w.off:], bs)
|
||||
if w.off += n; w.off == len(w.buf) {
|
||||
panic(struct{}{})
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *limitedWriter) WriteByte(b byte) error {
|
||||
w.buf[w.off] = b
|
||||
if w.off++; w.off == len(w.buf) {
|
||||
panic(struct{}{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *limitedWriter) WriteString(s string) (int, error) {
|
||||
n := copy(w.buf[w.off:], s)
|
||||
if w.off += n; w.off == len(w.buf) {
|
||||
panic(struct{}{})
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *limitedWriter) Bytes() []byte {
|
||||
return w.buf[:w.off]
|
||||
}
|
||||
+942
@@ -0,0 +1,942 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Parse a query string, and returns the query struct.
|
||||
//
|
||||
// If parsing failed, it returns an error of type [*ParseError], which has
|
||||
// the byte offset and the invalid token. The byte offset is the scanned bytes
|
||||
// when the error occurred. The token is empty if the error occurred after
|
||||
// scanning the entire query string.
|
||||
func Parse(src string) (*Query, error) {
|
||||
l := newLexer(src)
|
||||
if yyParse(l) > 0 {
|
||||
return nil, l.err
|
||||
}
|
||||
return l.result, nil
|
||||
}
|
||||
|
||||
// Query represents the abstract syntax tree of a jq query.
|
||||
type Query struct {
|
||||
Meta *ConstObject
|
||||
Imports []*Import
|
||||
FuncDefs []*FuncDef
|
||||
Term *Term
|
||||
Left *Query
|
||||
Right *Query
|
||||
Patterns []*Pattern
|
||||
Op Operator
|
||||
}
|
||||
|
||||
// Run the query.
|
||||
//
|
||||
// It is safe to call this method in goroutines, to reuse a parsed [*Query].
|
||||
func (e *Query) Run(v any) Iter {
|
||||
return e.RunWithContext(context.Background(), v)
|
||||
}
|
||||
|
||||
// RunWithContext runs the query with context.
|
||||
func (e *Query) RunWithContext(ctx context.Context, v any) Iter {
|
||||
code, err := Compile(e)
|
||||
if err != nil {
|
||||
return NewIter(err)
|
||||
}
|
||||
return code.RunWithContext(ctx, v)
|
||||
}
|
||||
|
||||
func (e *Query) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Query) writeTo(s *strings.Builder) {
|
||||
if e.Meta != nil {
|
||||
s.WriteString("module ")
|
||||
e.Meta.writeTo(s)
|
||||
s.WriteString(";\n")
|
||||
}
|
||||
for _, im := range e.Imports {
|
||||
im.writeTo(s)
|
||||
}
|
||||
for _, fd := range e.FuncDefs {
|
||||
fd.writeTo(s)
|
||||
s.WriteByte(' ')
|
||||
}
|
||||
if e.Term != nil {
|
||||
e.Term.writeTo(s)
|
||||
} else if e.Right != nil {
|
||||
e.Left.writeTo(s)
|
||||
if e.Op != OpComma {
|
||||
s.WriteByte(' ')
|
||||
}
|
||||
for i, p := range e.Patterns {
|
||||
if i == 0 {
|
||||
s.WriteString("as ")
|
||||
} else {
|
||||
s.WriteString("?// ")
|
||||
}
|
||||
p.writeTo(s)
|
||||
s.WriteByte(' ')
|
||||
}
|
||||
s.WriteString(e.Op.String())
|
||||
s.WriteByte(' ')
|
||||
e.Right.writeTo(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Query) toIndexKey() any {
|
||||
if e.Term == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Term.toIndexKey()
|
||||
}
|
||||
|
||||
func (e *Query) toIndices(xs []any) []any {
|
||||
if e.Term == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Term.toIndices(xs)
|
||||
}
|
||||
|
||||
// Import ...
|
||||
type Import struct {
|
||||
ImportPath string
|
||||
ImportAlias string
|
||||
IncludePath string
|
||||
Meta *ConstObject
|
||||
}
|
||||
|
||||
func (e *Import) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Import) writeTo(s *strings.Builder) {
|
||||
if e.ImportPath != "" {
|
||||
s.WriteString("import ")
|
||||
jsonEncodeString(s, e.ImportPath)
|
||||
s.WriteString(" as ")
|
||||
s.WriteString(e.ImportAlias)
|
||||
} else {
|
||||
s.WriteString("include ")
|
||||
jsonEncodeString(s, e.IncludePath)
|
||||
}
|
||||
if e.Meta != nil {
|
||||
s.WriteByte(' ')
|
||||
e.Meta.writeTo(s)
|
||||
}
|
||||
s.WriteString(";\n")
|
||||
}
|
||||
|
||||
// FuncDef ...
|
||||
type FuncDef struct {
|
||||
Name string
|
||||
Args []string
|
||||
Body *Query
|
||||
}
|
||||
|
||||
func (e *FuncDef) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *FuncDef) writeTo(s *strings.Builder) {
|
||||
s.WriteString("def ")
|
||||
s.WriteString(e.Name)
|
||||
if len(e.Args) > 0 {
|
||||
s.WriteByte('(')
|
||||
for i, e := range e.Args {
|
||||
if i > 0 {
|
||||
s.WriteString("; ")
|
||||
}
|
||||
s.WriteString(e)
|
||||
}
|
||||
s.WriteByte(')')
|
||||
}
|
||||
s.WriteString(": ")
|
||||
e.Body.writeTo(s)
|
||||
s.WriteByte(';')
|
||||
}
|
||||
|
||||
// Term ...
|
||||
type Term struct {
|
||||
Type TermType
|
||||
Index *Index
|
||||
Func *Func
|
||||
Object *Object
|
||||
Array *Array
|
||||
Number string
|
||||
Unary *Unary
|
||||
Format string
|
||||
Str *String
|
||||
If *If
|
||||
Try *Try
|
||||
Reduce *Reduce
|
||||
Foreach *Foreach
|
||||
Label *Label
|
||||
Break string
|
||||
Query *Query
|
||||
SuffixList []*Suffix
|
||||
}
|
||||
|
||||
func (e *Term) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Term) writeTo(s *strings.Builder) {
|
||||
switch e.Type {
|
||||
case TermTypeIdentity:
|
||||
s.WriteByte('.')
|
||||
case TermTypeRecurse:
|
||||
s.WriteString("..")
|
||||
case TermTypeNull:
|
||||
s.WriteString("null")
|
||||
case TermTypeTrue:
|
||||
s.WriteString("true")
|
||||
case TermTypeFalse:
|
||||
s.WriteString("false")
|
||||
case TermTypeIndex:
|
||||
e.Index.writeTo(s)
|
||||
case TermTypeFunc:
|
||||
e.Func.writeTo(s)
|
||||
case TermTypeObject:
|
||||
e.Object.writeTo(s)
|
||||
case TermTypeArray:
|
||||
e.Array.writeTo(s)
|
||||
case TermTypeNumber:
|
||||
s.WriteString(e.Number)
|
||||
case TermTypeUnary:
|
||||
e.Unary.writeTo(s)
|
||||
case TermTypeFormat:
|
||||
s.WriteString(e.Format)
|
||||
if e.Str != nil {
|
||||
s.WriteByte(' ')
|
||||
e.Str.writeTo(s)
|
||||
}
|
||||
case TermTypeString:
|
||||
e.Str.writeTo(s)
|
||||
case TermTypeIf:
|
||||
e.If.writeTo(s)
|
||||
case TermTypeTry:
|
||||
e.Try.writeTo(s)
|
||||
case TermTypeReduce:
|
||||
e.Reduce.writeTo(s)
|
||||
case TermTypeForeach:
|
||||
e.Foreach.writeTo(s)
|
||||
case TermTypeLabel:
|
||||
e.Label.writeTo(s)
|
||||
case TermTypeBreak:
|
||||
s.WriteString("break ")
|
||||
s.WriteString(e.Break)
|
||||
case TermTypeQuery:
|
||||
s.WriteByte('(')
|
||||
e.Query.writeTo(s)
|
||||
s.WriteByte(')')
|
||||
}
|
||||
for _, e := range e.SuffixList {
|
||||
e.writeTo(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Term) toIndexKey() any {
|
||||
switch e.Type {
|
||||
case TermTypeNumber:
|
||||
return toNumber(e.Number)
|
||||
case TermTypeUnary:
|
||||
return e.Unary.toNumber()
|
||||
case TermTypeString:
|
||||
if e.Str.Queries == nil {
|
||||
return e.Str.Str
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Term) toIndices(xs []any) []any {
|
||||
switch e.Type {
|
||||
case TermTypeIndex:
|
||||
if xs = e.Index.toIndices(xs); xs == nil {
|
||||
return nil
|
||||
}
|
||||
case TermTypeQuery:
|
||||
if xs = e.Query.toIndices(xs); xs == nil {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
for _, s := range e.SuffixList {
|
||||
if xs = s.toIndices(xs); xs == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return xs
|
||||
}
|
||||
|
||||
func (e *Term) toNumber() any {
|
||||
if e.Type == TermTypeNumber {
|
||||
return toNumber(e.Number)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unary ...
|
||||
type Unary struct {
|
||||
Op Operator
|
||||
Term *Term
|
||||
}
|
||||
|
||||
func (e *Unary) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Unary) writeTo(s *strings.Builder) {
|
||||
s.WriteString(e.Op.String())
|
||||
e.Term.writeTo(s)
|
||||
}
|
||||
|
||||
func (e *Unary) toNumber() any {
|
||||
v := e.Term.toNumber()
|
||||
if v != nil && e.Op == OpSub {
|
||||
v = funcOpNegate(v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Pattern ...
|
||||
type Pattern struct {
|
||||
Name string
|
||||
Array []*Pattern
|
||||
Object []*PatternObject
|
||||
}
|
||||
|
||||
func (e *Pattern) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Pattern) writeTo(s *strings.Builder) {
|
||||
if e.Name != "" {
|
||||
s.WriteString(e.Name)
|
||||
} else if len(e.Array) > 0 {
|
||||
s.WriteByte('[')
|
||||
for i, e := range e.Array {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
e.writeTo(s)
|
||||
}
|
||||
s.WriteByte(']')
|
||||
} else if len(e.Object) > 0 {
|
||||
s.WriteByte('{')
|
||||
for i, e := range e.Object {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
e.writeTo(s)
|
||||
}
|
||||
s.WriteByte('}')
|
||||
}
|
||||
}
|
||||
|
||||
// PatternObject ...
|
||||
type PatternObject struct {
|
||||
Key string
|
||||
KeyString *String
|
||||
KeyQuery *Query
|
||||
Val *Pattern
|
||||
}
|
||||
|
||||
func (e *PatternObject) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *PatternObject) writeTo(s *strings.Builder) {
|
||||
if e.Key != "" {
|
||||
s.WriteString(e.Key)
|
||||
} else if e.KeyString != nil {
|
||||
e.KeyString.writeTo(s)
|
||||
} else if e.KeyQuery != nil {
|
||||
s.WriteByte('(')
|
||||
e.KeyQuery.writeTo(s)
|
||||
s.WriteByte(')')
|
||||
}
|
||||
if e.Val != nil {
|
||||
s.WriteString(": ")
|
||||
e.Val.writeTo(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Index ...
|
||||
type Index struct {
|
||||
Name string
|
||||
Str *String
|
||||
Start *Query
|
||||
End *Query
|
||||
IsSlice bool
|
||||
}
|
||||
|
||||
func (e *Index) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Index) writeTo(s *strings.Builder) {
|
||||
if l := s.Len(); l > 0 {
|
||||
// ". .x" != "..x" and "0 .x" != "0.x"
|
||||
if c := s.String()[l-1]; c == '.' || '0' <= c && c <= '9' {
|
||||
s.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
s.WriteByte('.')
|
||||
e.writeSuffixTo(s)
|
||||
}
|
||||
|
||||
func (e *Index) writeSuffixTo(s *strings.Builder) {
|
||||
if e.Name != "" {
|
||||
s.WriteString(e.Name)
|
||||
} else if e.Str != nil {
|
||||
e.Str.writeTo(s)
|
||||
} else {
|
||||
s.WriteByte('[')
|
||||
if e.IsSlice {
|
||||
if e.Start != nil {
|
||||
e.Start.writeTo(s)
|
||||
}
|
||||
s.WriteByte(':')
|
||||
if e.End != nil {
|
||||
e.End.writeTo(s)
|
||||
}
|
||||
} else {
|
||||
e.Start.writeTo(s)
|
||||
}
|
||||
s.WriteByte(']')
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Index) toIndexKey() any {
|
||||
if e.Name != "" {
|
||||
return e.Name
|
||||
} else if e.Str != nil {
|
||||
if e.Str.Queries == nil {
|
||||
return e.Str.Str
|
||||
}
|
||||
} else if !e.IsSlice {
|
||||
return e.Start.toIndexKey()
|
||||
} else {
|
||||
var start, end any
|
||||
ok := true
|
||||
if e.Start != nil {
|
||||
start = e.Start.toIndexKey()
|
||||
ok = start != nil
|
||||
}
|
||||
if e.End != nil && ok {
|
||||
end = e.End.toIndexKey()
|
||||
ok = end != nil
|
||||
}
|
||||
if ok {
|
||||
return map[string]any{"start": start, "end": end}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Index) toIndices(xs []any) []any {
|
||||
if k := e.toIndexKey(); k != nil {
|
||||
return append(xs, k)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Func ...
|
||||
type Func struct {
|
||||
Name string
|
||||
Args []*Query
|
||||
}
|
||||
|
||||
func (e *Func) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Func) writeTo(s *strings.Builder) {
|
||||
s.WriteString(e.Name)
|
||||
if len(e.Args) > 0 {
|
||||
s.WriteByte('(')
|
||||
for i, e := range e.Args {
|
||||
if i > 0 {
|
||||
s.WriteString("; ")
|
||||
}
|
||||
e.writeTo(s)
|
||||
}
|
||||
s.WriteByte(')')
|
||||
}
|
||||
}
|
||||
|
||||
// String ...
|
||||
type String struct {
|
||||
Str string
|
||||
Queries []*Query
|
||||
}
|
||||
|
||||
func (e *String) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *String) writeTo(s *strings.Builder) {
|
||||
if e.Queries == nil {
|
||||
jsonEncodeString(s, e.Str)
|
||||
return
|
||||
}
|
||||
s.WriteByte('"')
|
||||
for _, e := range e.Queries {
|
||||
if e.Term.Str == nil {
|
||||
s.WriteString(`\`)
|
||||
e.writeTo(s)
|
||||
} else {
|
||||
es := e.String()
|
||||
s.WriteString(es[1 : len(es)-1])
|
||||
}
|
||||
}
|
||||
s.WriteByte('"')
|
||||
}
|
||||
|
||||
// Object ...
|
||||
type Object struct {
|
||||
KeyVals []*ObjectKeyVal
|
||||
}
|
||||
|
||||
func (e *Object) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Object) writeTo(s *strings.Builder) {
|
||||
if len(e.KeyVals) == 0 {
|
||||
s.WriteString("{}")
|
||||
return
|
||||
}
|
||||
s.WriteString("{ ")
|
||||
for i, kv := range e.KeyVals {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
kv.writeTo(s)
|
||||
}
|
||||
s.WriteString(" }")
|
||||
}
|
||||
|
||||
// ObjectKeyVal ...
|
||||
type ObjectKeyVal struct {
|
||||
Key string
|
||||
KeyString *String
|
||||
KeyQuery *Query
|
||||
Val *Query
|
||||
}
|
||||
|
||||
func (e *ObjectKeyVal) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *ObjectKeyVal) writeTo(s *strings.Builder) {
|
||||
if e.Key != "" {
|
||||
s.WriteString(e.Key)
|
||||
} else if e.KeyString != nil {
|
||||
e.KeyString.writeTo(s)
|
||||
} else if e.KeyQuery != nil {
|
||||
s.WriteByte('(')
|
||||
e.KeyQuery.writeTo(s)
|
||||
s.WriteByte(')')
|
||||
}
|
||||
if e.Val != nil {
|
||||
s.WriteString(": ")
|
||||
e.Val.writeTo(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Array ...
|
||||
type Array struct {
|
||||
Query *Query
|
||||
}
|
||||
|
||||
func (e *Array) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Array) writeTo(s *strings.Builder) {
|
||||
s.WriteByte('[')
|
||||
if e.Query != nil {
|
||||
e.Query.writeTo(s)
|
||||
}
|
||||
s.WriteByte(']')
|
||||
}
|
||||
|
||||
// Suffix ...
|
||||
type Suffix struct {
|
||||
Index *Index
|
||||
Iter bool
|
||||
Optional bool
|
||||
}
|
||||
|
||||
func (e *Suffix) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Suffix) writeTo(s *strings.Builder) {
|
||||
if e.Index != nil {
|
||||
if e.Index.Name != "" || e.Index.Str != nil {
|
||||
e.Index.writeTo(s)
|
||||
} else {
|
||||
e.Index.writeSuffixTo(s)
|
||||
}
|
||||
} else if e.Iter {
|
||||
s.WriteString("[]")
|
||||
} else if e.Optional {
|
||||
s.WriteByte('?')
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Suffix) toTerm() *Term {
|
||||
if e.Index != nil {
|
||||
return &Term{Type: TermTypeIndex, Index: e.Index}
|
||||
} else if e.Iter {
|
||||
return &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}}}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Suffix) toIndices(xs []any) []any {
|
||||
if e.Index == nil {
|
||||
return nil
|
||||
}
|
||||
return e.Index.toIndices(xs)
|
||||
}
|
||||
|
||||
// If ...
|
||||
type If struct {
|
||||
Cond *Query
|
||||
Then *Query
|
||||
Elif []*IfElif
|
||||
Else *Query
|
||||
}
|
||||
|
||||
func (e *If) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *If) writeTo(s *strings.Builder) {
|
||||
s.WriteString("if ")
|
||||
e.Cond.writeTo(s)
|
||||
s.WriteString(" then ")
|
||||
e.Then.writeTo(s)
|
||||
for _, e := range e.Elif {
|
||||
s.WriteByte(' ')
|
||||
e.writeTo(s)
|
||||
}
|
||||
if e.Else != nil {
|
||||
s.WriteString(" else ")
|
||||
e.Else.writeTo(s)
|
||||
}
|
||||
s.WriteString(" end")
|
||||
}
|
||||
|
||||
// IfElif ...
|
||||
type IfElif struct {
|
||||
Cond *Query
|
||||
Then *Query
|
||||
}
|
||||
|
||||
func (e *IfElif) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *IfElif) writeTo(s *strings.Builder) {
|
||||
s.WriteString("elif ")
|
||||
e.Cond.writeTo(s)
|
||||
s.WriteString(" then ")
|
||||
e.Then.writeTo(s)
|
||||
}
|
||||
|
||||
// Try ...
|
||||
type Try struct {
|
||||
Body *Query
|
||||
Catch *Query
|
||||
}
|
||||
|
||||
func (e *Try) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Try) writeTo(s *strings.Builder) {
|
||||
s.WriteString("try ")
|
||||
e.Body.writeTo(s)
|
||||
if e.Catch != nil {
|
||||
s.WriteString(" catch ")
|
||||
e.Catch.writeTo(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce ...
|
||||
type Reduce struct {
|
||||
Query *Query
|
||||
Pattern *Pattern
|
||||
Start *Query
|
||||
Update *Query
|
||||
}
|
||||
|
||||
func (e *Reduce) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Reduce) writeTo(s *strings.Builder) {
|
||||
s.WriteString("reduce ")
|
||||
e.Query.writeTo(s)
|
||||
s.WriteString(" as ")
|
||||
e.Pattern.writeTo(s)
|
||||
s.WriteString(" (")
|
||||
e.Start.writeTo(s)
|
||||
s.WriteString("; ")
|
||||
e.Update.writeTo(s)
|
||||
s.WriteByte(')')
|
||||
}
|
||||
|
||||
// Foreach ...
|
||||
type Foreach struct {
|
||||
Query *Query
|
||||
Pattern *Pattern
|
||||
Start *Query
|
||||
Update *Query
|
||||
Extract *Query
|
||||
}
|
||||
|
||||
func (e *Foreach) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Foreach) writeTo(s *strings.Builder) {
|
||||
s.WriteString("foreach ")
|
||||
e.Query.writeTo(s)
|
||||
s.WriteString(" as ")
|
||||
e.Pattern.writeTo(s)
|
||||
s.WriteString(" (")
|
||||
e.Start.writeTo(s)
|
||||
s.WriteString("; ")
|
||||
e.Update.writeTo(s)
|
||||
if e.Extract != nil {
|
||||
s.WriteString("; ")
|
||||
e.Extract.writeTo(s)
|
||||
}
|
||||
s.WriteByte(')')
|
||||
}
|
||||
|
||||
// Label ...
|
||||
type Label struct {
|
||||
Ident string
|
||||
Body *Query
|
||||
}
|
||||
|
||||
func (e *Label) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *Label) writeTo(s *strings.Builder) {
|
||||
s.WriteString("label ")
|
||||
s.WriteString(e.Ident)
|
||||
s.WriteString(" | ")
|
||||
e.Body.writeTo(s)
|
||||
}
|
||||
|
||||
// ConstTerm ...
|
||||
type ConstTerm struct {
|
||||
Object *ConstObject
|
||||
Array *ConstArray
|
||||
Number string
|
||||
Str string
|
||||
Null bool
|
||||
True bool
|
||||
False bool
|
||||
}
|
||||
|
||||
func (e *ConstTerm) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *ConstTerm) writeTo(s *strings.Builder) {
|
||||
if e.Object != nil {
|
||||
e.Object.writeTo(s)
|
||||
} else if e.Array != nil {
|
||||
e.Array.writeTo(s)
|
||||
} else if e.Number != "" {
|
||||
s.WriteString(e.Number)
|
||||
} else if e.Null {
|
||||
s.WriteString("null")
|
||||
} else if e.True {
|
||||
s.WriteString("true")
|
||||
} else if e.False {
|
||||
s.WriteString("false")
|
||||
} else {
|
||||
jsonEncodeString(s, e.Str)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ConstTerm) toValue() any {
|
||||
if e.Object != nil {
|
||||
return e.Object.ToValue()
|
||||
} else if e.Array != nil {
|
||||
return e.Array.toValue()
|
||||
} else if e.Number != "" {
|
||||
return toNumber(e.Number)
|
||||
} else if e.Null {
|
||||
return nil
|
||||
} else if e.True {
|
||||
return true
|
||||
} else if e.False {
|
||||
return false
|
||||
} else {
|
||||
return e.Str
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ConstTerm) toString() (string, bool) {
|
||||
if e.Object != nil || e.Array != nil ||
|
||||
e.Number != "" || e.Null || e.True || e.False {
|
||||
return "", false
|
||||
}
|
||||
return e.Str, true
|
||||
}
|
||||
|
||||
// ConstObject ...
|
||||
type ConstObject struct {
|
||||
KeyVals []*ConstObjectKeyVal
|
||||
}
|
||||
|
||||
func (e *ConstObject) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *ConstObject) writeTo(s *strings.Builder) {
|
||||
if len(e.KeyVals) == 0 {
|
||||
s.WriteString("{}")
|
||||
return
|
||||
}
|
||||
s.WriteString("{ ")
|
||||
for i, kv := range e.KeyVals {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
kv.writeTo(s)
|
||||
}
|
||||
s.WriteString(" }")
|
||||
}
|
||||
|
||||
// ToValue converts the object to map[string]any.
|
||||
func (e *ConstObject) ToValue() map[string]any {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
v := make(map[string]any, len(e.KeyVals))
|
||||
for _, e := range e.KeyVals {
|
||||
key := e.Key
|
||||
if key == "" {
|
||||
key = e.KeyString
|
||||
}
|
||||
v[key] = e.Val.toValue()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ConstObjectKeyVal ...
|
||||
type ConstObjectKeyVal struct {
|
||||
Key string
|
||||
KeyString string
|
||||
Val *ConstTerm
|
||||
}
|
||||
|
||||
func (e *ConstObjectKeyVal) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *ConstObjectKeyVal) writeTo(s *strings.Builder) {
|
||||
if e.Key != "" {
|
||||
s.WriteString(e.Key)
|
||||
} else {
|
||||
jsonEncodeString(s, e.KeyString)
|
||||
}
|
||||
s.WriteString(": ")
|
||||
e.Val.writeTo(s)
|
||||
}
|
||||
|
||||
// ConstArray ...
|
||||
type ConstArray struct {
|
||||
Elems []*ConstTerm
|
||||
}
|
||||
|
||||
func (e *ConstArray) String() string {
|
||||
var s strings.Builder
|
||||
e.writeTo(&s)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (e *ConstArray) writeTo(s *strings.Builder) {
|
||||
s.WriteByte('[')
|
||||
for i, e := range e.Elems {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
e.writeTo(s)
|
||||
}
|
||||
s.WriteByte(']')
|
||||
}
|
||||
|
||||
func (e *ConstArray) toValue() []any {
|
||||
v := make([]any, len(e.Elems))
|
||||
for i, e := range e.Elems {
|
||||
v[i] = e.toValue()
|
||||
}
|
||||
return v
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
//go:build !gojq_debug
|
||||
|
||||
package gojq
|
||||
|
||||
type codeinfo struct{}
|
||||
|
||||
func (*compiler) appendCodeInfo(any) {}
|
||||
|
||||
func (*compiler) deleteCodeInfo(string) {}
|
||||
|
||||
func (*env) debugCodes() {}
|
||||
|
||||
func (*env) debugState(int, bool) {}
|
||||
|
||||
func (*env) debugForks(int, string) {}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package gojq
|
||||
|
||||
type scopeStack struct {
|
||||
data []scopeBlock
|
||||
index int
|
||||
limit int
|
||||
}
|
||||
|
||||
type scopeBlock struct {
|
||||
value scope
|
||||
next int
|
||||
}
|
||||
|
||||
func newScopeStack() *scopeStack {
|
||||
return &scopeStack{index: -1, limit: -1}
|
||||
}
|
||||
|
||||
func (s *scopeStack) push(v scope) {
|
||||
b := scopeBlock{v, s.index}
|
||||
s.index = max(s.index, s.limit) + 1
|
||||
if s.index < len(s.data) {
|
||||
s.data[s.index] = b
|
||||
} else {
|
||||
s.data = append(s.data, b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopeStack) pop() scope {
|
||||
b := s.data[s.index]
|
||||
s.index = b.next
|
||||
return b.value
|
||||
}
|
||||
|
||||
func (s *scopeStack) empty() bool {
|
||||
return s.index < 0
|
||||
}
|
||||
|
||||
func (s *scopeStack) save() (index, limit int) {
|
||||
index, limit = s.index, s.limit
|
||||
if s.index > s.limit {
|
||||
s.limit = s.index
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *scopeStack) restore(index, limit int) {
|
||||
s.index, s.limit = index, limit
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package gojq
|
||||
|
||||
type stack struct {
|
||||
data []block
|
||||
index int
|
||||
limit int
|
||||
}
|
||||
|
||||
type block struct {
|
||||
value any
|
||||
next int
|
||||
}
|
||||
|
||||
func newStack() *stack {
|
||||
return &stack{index: -1, limit: -1}
|
||||
}
|
||||
|
||||
func (s *stack) push(v any) {
|
||||
b := block{v, s.index}
|
||||
s.index = max(s.index, s.limit) + 1
|
||||
if s.index < len(s.data) {
|
||||
s.data[s.index] = b
|
||||
} else {
|
||||
s.data = append(s.data, b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) pop() any {
|
||||
b := s.data[s.index]
|
||||
s.index = b.next
|
||||
return b.value
|
||||
}
|
||||
|
||||
func (s *stack) top() any {
|
||||
return s.data[s.index].value
|
||||
}
|
||||
|
||||
func (s *stack) empty() bool {
|
||||
return s.index < 0
|
||||
}
|
||||
|
||||
func (s *stack) save() (index, limit int) {
|
||||
index, limit = s.index, s.limit
|
||||
if s.index > s.limit {
|
||||
s.limit = s.index
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stack) restore(index, limit int) {
|
||||
s.index, s.limit = index, limit
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package gojq
|
||||
|
||||
// TermType represents the type of [Term].
|
||||
type TermType int
|
||||
|
||||
// TermType list.
|
||||
const (
|
||||
TermTypeIdentity TermType = iota + 1
|
||||
TermTypeRecurse
|
||||
TermTypeNull
|
||||
TermTypeTrue
|
||||
TermTypeFalse
|
||||
TermTypeIndex
|
||||
TermTypeFunc
|
||||
TermTypeObject
|
||||
TermTypeArray
|
||||
TermTypeNumber
|
||||
TermTypeUnary
|
||||
TermTypeFormat
|
||||
TermTypeString
|
||||
TermTypeIf
|
||||
TermTypeTry
|
||||
TermTypeReduce
|
||||
TermTypeForeach
|
||||
TermTypeLabel
|
||||
TermTypeBreak
|
||||
TermTypeQuery
|
||||
)
|
||||
|
||||
// GoString implements [fmt.GoStringer].
|
||||
func (termType TermType) GoString() (str string) {
|
||||
defer func() { str = "gojq." + str }()
|
||||
switch termType {
|
||||
case TermTypeIdentity:
|
||||
return "TermTypeIdentity"
|
||||
case TermTypeRecurse:
|
||||
return "TermTypeRecurse"
|
||||
case TermTypeNull:
|
||||
return "TermTypeNull"
|
||||
case TermTypeTrue:
|
||||
return "TermTypeTrue"
|
||||
case TermTypeFalse:
|
||||
return "TermTypeFalse"
|
||||
case TermTypeIndex:
|
||||
return "TermTypeIndex"
|
||||
case TermTypeFunc:
|
||||
return "TermTypeFunc"
|
||||
case TermTypeObject:
|
||||
return "TermTypeObject"
|
||||
case TermTypeArray:
|
||||
return "TermTypeArray"
|
||||
case TermTypeNumber:
|
||||
return "TermTypeNumber"
|
||||
case TermTypeUnary:
|
||||
return "TermTypeUnary"
|
||||
case TermTypeFormat:
|
||||
return "TermTypeFormat"
|
||||
case TermTypeString:
|
||||
return "TermTypeString"
|
||||
case TermTypeIf:
|
||||
return "TermTypeIf"
|
||||
case TermTypeTry:
|
||||
return "TermTypeTry"
|
||||
case TermTypeReduce:
|
||||
return "TermTypeReduce"
|
||||
case TermTypeForeach:
|
||||
return "TermTypeForeach"
|
||||
case TermTypeLabel:
|
||||
return "TermTypeLabel"
|
||||
case TermTypeBreak:
|
||||
return "TermTypeBreak"
|
||||
case TermTypeQuery:
|
||||
return "TermTypeQuery"
|
||||
default:
|
||||
panic(termType)
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package gojq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// TypeOf returns the jq-flavored type name of v.
|
||||
//
|
||||
// This method is used by built-in type/0 function, and accepts only limited
|
||||
// types (nil, bool, int, float64, *big.Int, json.Number, string, []any, and
|
||||
// map[string]any).
|
||||
func TypeOf(v any) string {
|
||||
switch v.(type) {
|
||||
case nil:
|
||||
return "null"
|
||||
case bool:
|
||||
return "boolean"
|
||||
case int, float64, *big.Int, json.Number:
|
||||
return "number"
|
||||
case string:
|
||||
return "string"
|
||||
case []any:
|
||||
return "array"
|
||||
case map[string]any:
|
||||
return "object"
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user