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:
2026-09-22 21:54:01 -07:00
parent 6cb206fb27
commit 9444a35322
311 changed files with 238557 additions and 92 deletions
+16
View File
@@ -0,0 +1,16 @@
/gojq
/goxz
/CREDITS
/y.output
*.exe
*.test
*.out
*.md
*.y
**/*.jq
**/*.json
**/*.yaml
**/*_test.go
.github
_gojq
_tools
+3
View File
@@ -0,0 +1,3 @@
**/testdata/** binary
/builtin.go eol=lf linguist-generated=true
/parser.go eol=lf linguist-generated=true
+7
View File
@@ -0,0 +1,7 @@
/gojq
/goxz
/CREDITS
/y.output
*.exe
*.test
*.out
+418
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,164 @@
# gojq
[![CI Status](https://github.com/itchyny/gojq/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/itchyny/gojq/actions?query=branch:main)
[![Go Report Card](https://goreportcard.com/badge/github.com/itchyny/gojq)](https://goreportcard.com/report/github.com/itchyny/gojq)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/itchyny/gojq/blob/main/LICENSE)
[![release](https://img.shields.io/github/release/itchyny/gojq/all.svg)](https://github.com/itchyny/gojq/releases)
[![pkg.go.dev](https://pkg.go.dev/badge/github.com/itchyny/gojq)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+208
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+669
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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))
}
}