pax_global_header00006660000000000000000000000064144361244200014512gustar00rootroot0000000000000052 comment=c882861ed1727ac715edf14bbcd0786173a42349 gojq-0.12.13/000077500000000000000000000000001443612442000126165ustar00rootroot00000000000000gojq-0.12.13/.dockerignore000066400000000000000000000001761443612442000152760ustar00rootroot00000000000000/gojq /goxz /CREDITS /._* /y.output *.exe *.test *.out *.md *.y **/*.jq **/*.json **/*.yaml **/*_test.go .github _gojq _tools gojq-0.12.13/.gitattributes000066400000000000000000000000541443612442000155100ustar00rootroot00000000000000**/testdata/** binary /builtin.go eol=lf gojq-0.12.13/.github/000077500000000000000000000000001443612442000141565ustar00rootroot00000000000000gojq-0.12.13/.github/workflows/000077500000000000000000000000001443612442000162135ustar00rootroot00000000000000gojq-0.12.13/.github/workflows/ci.yaml000066400000000000000000000030521443612442000174720ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: permissions: contents: read jobs: test: name: Test runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] go: [1.20.x, 1.19.x, 1.18.x] steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - name: Test run: make test - name: Test with GOARCH=386 run: env GOARCH=386 go test -v ./... if: matrix.os != 'macos-latest' - name: Lint run: make lint if: matrix.go >= '1.19.x' - name: Check tools run: make check-tools - name: Check go generate run: go generate && ! git diff | grep ^ shell: bash - name: Check command examples in README.md run: | ./gojq -Rnr 'reduce inputs as $x ( {}; if $x|test("^ [$] .*gojq|^```") then if .command then .results += [{command: .command, output: .output}] | del(.command,.output) end | if $x|test("gojq") then .command = $x[3:] end elif .command then .output += ($x + "\n" | sub(" +#.*"; "")) end ) | .results[] | "if got=$(diff <(printf %s \(.output | @sh)) \\ <(\(.command | gsub("gojq"; "./gojq")) 2>&1)); then echo ok: \(.command | @sh) else echo ng: \(.command | @sh); echo \"$got\"; exit 1 fi" ' README.md | bash shell: bash gojq-0.12.13/.github/workflows/release.yaml000066400000000000000000000034221443612442000205200ustar00rootroot00000000000000name: Release on: push: tags: - 'v*' permissions: contents: write packages: write jobs: release: name: Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v4 with: go-version: 1.x - name: Cross build run: make cross - name: Create Release uses: ncipollo/release-action@v1 with: name: Release ${{ github.ref_name }} artifacts: 'goxz/*' docker: name: Docker runs-on: ubuntu-latest needs: release steps: - name: Checkout code uses: actions/checkout@v3 - name: Docker metadata uses: docker/metadata-action@v4 id: metadata with: images: | ${{ github.repository }} ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and release Docker image uses: docker/build-push-action@v4 with: context: . push: true platforms: linux/amd64, linux/arm64 tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} gojq-0.12.13/.gitignore000066400000000000000000000000671443612442000146110ustar00rootroot00000000000000/gojq /goxz /CREDITS /._* /y.output *.exe *.test *.out gojq-0.12.13/CHANGELOG.md000066400000000000000000000464231443612442000144400ustar00rootroot00000000000000# Changelog ## [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 gojq-0.12.13/Dockerfile000066400000000000000000000002761443612442000146150ustar00rootroot00000000000000FROM golang:1.20 AS builder WORKDIR /app COPY . . ENV CGO_ENABLED 0 RUN make build FROM gcr.io/distroless/static:debug COPY --from=builder /app/gojq / ENTRYPOINT ["/gojq"] CMD ["--help"] gojq-0.12.13/LICENSE000066400000000000000000000020671443612442000136300ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019-2023 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. gojq-0.12.13/Makefile000066400000000000000000000050251443612442000142600ustar00rootroot00000000000000BIN := 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@latest .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 -d ./... && go mod tidy go mod edit -modfile=go.dev.mod -droprequire=github.com/itchyny/{astgen,timefmt}-go go get -u -d -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)" gojq-0.12.13/README.md000066400000000000000000000315601443612442000141020ustar00rootroot00000000000000# gojq [![CI Status](https://github.com/itchyny/gojq/workflows/CI/badge.svg)](https://github.com/itchyny/gojq/actions) [![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: {"foo": { bar: [] } } ^ invalid character 'b' looking for beginning of object key string ``` ## Installation ### Homebrew ```sh brew install gojq ``` ### 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 implements nice error messages for invalid query and JSON input. The error message of jq is sometimes difficult to tell where to fix the query. - 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 fixes various bugs of jq. gojq correctly deletes elements of arrays by `|= empty` ([jq#2051](https://github.com/jqlang/jq/issues/2051)). gojq fixes `try`/`catch` handling ([jq#1859](https://github.com/jqlang/jq/issues/1859), [jq#1885](https://github.com/jqlang/jq/issues/1885), [jq#2140](https://github.com/jqlang/jq/issues/2140)). gojq fixes `nth/2` to output nothing when the count is equal to or larger than the stream size ([jq#1867](https://github.com/jqlang/jq/issues/1867)). gojq consistently counts by characters (not by bytes) in `index`, `rindex`, and `indices` functions; `"12345" | .[index("3"):]` results in `"345"` ([jq#1430](https://github.com/jqlang/jq/issues/1430), [jq#1624](https://github.com/jqlang/jq/issues/1624)). gojq handles overlapping occurrence differently in `rindex` and `indices`; `"ababa" | [rindex("aba"), indices("aba")]` results in `[2,[0,2]]` ([jq#2433](https://github.com/jqlang/jq/issues/2433)). gojq supports string indexing; `"abcde"[2]` ([jq#1520](https://github.com/jqlang/jq/issues/1520)). gojq accepts indexing query `.e0` ([jq#1526](https://github.com/jqlang/jq/issues/1526), [jq#1651](https://github.com/jqlang/jq/issues/1651)), and allows `gsub` to handle patterns including `"^"` ([jq#2148](https://github.com/jqlang/jq/issues/2148)). gojq improves variable lexer to allow using keywords for variable names, especially in binding patterns, also disallows spaces after `$` ([jq#526](https://github.com/jqlang/jq/issues/526)). gojq fixes handling files with no newline characters at the end ([jq#2374](https://github.com/jqlang/jq/issues/2374)). - gojq truncates down floating-point numbers on indexing (`[0] | .[0.5]` results in `0` not `null`), and slicing (`[0,1,2] | .[0.5:1.5]` results in `[0]` not `[0,1]`). gojq parses unary operators with higher precedence than variable binding (`[-1 as $x | 1,$x]` results in `[1,-1]` not `[-1,-1]`). gojq implements `@uri` to escape all the reserved characters defined in RFC 3986, Sec. 2.2 ([jq#1506](https://github.com/jqlang/jq/issues/1506)), and 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)), and looks up correct timezone during daylight saving time on formatting with `%Z` ([jq#1912](https://github.com/jqlang/jq/issues/1912)). 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), `recurse_down` (deprecated in jq). 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 normalizes floating-point numbers to fit to double-precision floating-point numbers. gojq does not support or behaves differently with some regular expression metacharacters and 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`). gojq supports a few filters missing in jq; `scan/2` ([jq#2207](https://github.com/jqlang/jq/pull/2207)), and `@urid` format string ([jq#2261](https://github.com/jqlang/jq/issues/2261)). ### 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 { 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. - 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. But for arguments of `code.Run`, do not give values sharing same data between multiple calls. - 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 iterators can emit multiple errors and you can continue after an error. It is difficult for the iterator to tell the termination in this situation. - 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. gojq-0.12.13/_gojq000066400000000000000000000050161443612442000136420ustar00rootroot00000000000000#compdef gojq _gojq() { _arguments -s -S \ '(-r --raw-output -j --join-output -0 --nul-output)'{-r,--raw-output}'[output raw strings]' \ '(-r --raw-output -j --join-output -0 --nul-output)'{-j,--join-output}'[output without newlines]' \ '(-r --raw-output -j --join-output -0 --nul-output)'{-0,--nul-output}'[output with NUL character]' \ '(-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=[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 } gojq-0.12.13/_tools/000077500000000000000000000000001443612442000141155ustar00rootroot00000000000000gojq-0.12.13/_tools/gen_builtin.go000066400000000000000000000042331443612442000167450ustar00rootroot00000000000000package main import ( "flag" "fmt" "go/ast" "go/printer" "go/token" "os" "regexp" "strings" "github.com/itchyny/astgen-go" "github.com/itchyny/gojq" ) const fileFormat = `// Code generated by _tools/gen_builtin.go; DO NOT EDIT. package gojq func init() {%s} ` func main() { var input, output string flag.StringVar(&input, "i", "", "input file") flag.StringVar(&output, "o", "", "output file") flag.Parse() if err := run(input, output); err != nil { fmt.Fprint(os.Stderr, err) os.Exit(1) } } func run(input, output string) error { cnt, err := os.ReadFile(input) if err != nil { return err } q, err := gojq.Parse(string(cnt)) if err != nil { return err } fds := make(map[string][]*gojq.FuncDef) for _, fd := range q.FuncDefs { fd.Minify() fds[fd.Name] = append(fds[fd.Name], fd) } fds["_assign"] = nil fds["_modify"] = nil t, err := astgen.Build(fds) if err != nil { return err } var sb strings.Builder sb.WriteString("\n\tbuiltinFuncDefs = ") if err := printCompositeLit(&sb, t.(*ast.CompositeLit)); err != nil { return err } out := os.Stdout if output != "" { f, err := os.Create(output) if err != nil { return err } defer f.Close() out = f } _, err = fmt.Fprintf(out, fileFormat, sb.String()) return err } func printCompositeLit(out *strings.Builder, t *ast.CompositeLit) error { err := printer.Fprint(out, token.NewFileSet(), t.Type) if err != nil { return err } out.WriteString("{") for _, kv := range t.Elts { out.WriteString("\n\t\t") var sb strings.Builder err = printer.Fprint(&sb, token.NewFileSet(), kv) if err != nil { return err } str := sb.String() for op := gojq.OpPipe; op <= gojq.OpUpdateAlt; op++ { r := regexp.MustCompile(fmt.Sprintf(`\b((?:Update)?Op): %d\b`, op)) str = r.ReplaceAllString(str, fmt.Sprintf("$1: %#v", op)) } for termType := gojq.TermTypeIdentity; termType <= gojq.TermTypeQuery; termType++ { r := regexp.MustCompile(fmt.Sprintf(`(Term{Type): %d\b`, termType)) str = r.ReplaceAllString(str, fmt.Sprintf("$1: %#v", termType)) } out.WriteString(strings.ReplaceAll(str, "gojq.", "")) out.WriteString(",") } out.WriteString("\n\t}\n") return nil } gojq-0.12.13/_tools/print_builtin.go000066400000000000000000000017501443612442000173310ustar00rootroot00000000000000package main import ( "fmt" "os" "reflect" "sort" "strings" "github.com/itchyny/gojq" ) func main() { cnt, err := os.ReadFile("builtin.jq") if err != nil { panic(err) } q, err := gojq.Parse(string(cnt)) if err != nil { panic(err) } fds := make(map[string][]*gojq.FuncDef) for _, fd := range q.FuncDefs { fd.Minify() fds[fd.Name] = append(fds[fd.Name], fd) } count := len(fds) names, i := make([]string, count), 0 for n := range fds { names[i] = n i++ } sort.Strings(names) for _, n := range names { var sb strings.Builder for _, fd := range fds[n] { fmt.Fprintf(&sb, "%s ", fd) } q, err := gojq.Parse(sb.String()) if err != nil { panic(fmt.Sprintf("%s: %s", err, sb.String())) } for _, fd := range q.FuncDefs { fd.Minify() } if !reflect.DeepEqual(q.FuncDefs, fds[n]) { fmt.Printf("failed: %s: %s %s\n", n, q.FuncDefs, fds[n]) continue } fmt.Printf("ok: %s: %s\n", n, sb.String()) count-- } if count > 0 { os.Exit(1) } } gojq-0.12.13/builtin.go000066400000000000000000001034631443612442000146220ustar00rootroot00000000000000// Code generated by _tools/gen_builtin.go; DO NOT EDIT. package gojq func init() { builtinFuncDefs = map[string][]*FuncDef{ "IN": []*FuncDef{&FuncDef{Name: "IN", Args: []string{"s"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{&Query{Left: &Query{Func: "s"}, Op: OpEq, Right: &Query{Func: "."}}, &Query{Func: "."}}}}}}, &FuncDef{Name: "IN", Args: []string{"src", "s"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{&Query{Left: &Query{Func: "src"}, Op: OpEq, Right: &Query{Func: "s"}}, &Query{Func: "."}}}}}}}, "INDEX": []*FuncDef{&FuncDef{Name: "INDEX", Args: []string{"stream", "idx_expr"}, Body: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{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{Func: "$row"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "idx_expr"}, Op: OpPipe, Right: &Query{Func: "tostring"}}}}}}, Op: OpAssign, Right: &Query{Func: "$row"}}}}}}, &FuncDef{Name: "INDEX", Args: []string{"idx_expr"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "INDEX", Args: []*Query{&Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}}}}, &Query{Func: "idx_expr"}}}}}}}, "JOIN": []*FuncDef{&FuncDef{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{&Suffix{Iter: true}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$idx"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Func: "idx_expr"}}}}}}}}}}}}}}}, &FuncDef{Name: "JOIN", Args: []string{"$idx", "stream", "idx_expr"}, Body: &Query{Left: &Query{Func: "stream"}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$idx"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Func: "idx_expr"}}}}}}}}}}}}, &FuncDef{Name: "JOIN", Args: []string{"$idx", "stream", "idx_expr", "join_expr"}, Body: &Query{Left: &Query{Func: "stream"}, Op: OpPipe, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$idx"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Func: "idx_expr"}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "join_expr"}}}}}, "_assign": []*FuncDef{}, "_modify": []*FuncDef{}, "all": []*FuncDef{&FuncDef{Name: "all", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "all", Args: []*Query{&Query{Func: "."}}}}}}, &FuncDef{Name: "all", Args: []string{"y"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "all", Args: []*Query{&Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}}}}, &Query{Func: "y"}}}}}}, &FuncDef{Name: "all", Args: []string{"g", "y"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "isempty", Args: []*Query{&Query{Left: &Query{Func: "g"}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "y"}, Op: OpPipe, Right: &Query{Func: "not"}}}}}}}}}}}}}, "any": []*FuncDef{&FuncDef{Name: "any", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{&Query{Func: "."}}}}}}, &FuncDef{Name: "any", Args: []string{"y"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "any", Args: []*Query{&Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}}}}, &Query{Func: "y"}}}}}}, &FuncDef{Name: "any", Args: []string{"g", "y"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "isempty", Args: []*Query{&Query{Left: &Query{Func: "g"}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Func: "y"}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "not"}}}}, "arrays": []*FuncDef{&FuncDef{Name: "arrays", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}}}}}}}}, "booleans": []*FuncDef{&FuncDef{Name: "booleans", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "boolean"}}}}}}}}}}, "capture": []*FuncDef{&FuncDef{Name: "capture", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "capture", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "null"}}}}}}, &FuncDef{Name: "capture", Args: []string{"$re", "$flags"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "$flags"}}}}}, Op: OpPipe, Right: &Query{Func: "_capture"}}}}, "combinations": []*FuncDef{&FuncDef{Name: "combinations", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "length"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, Then: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}, Else: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, SuffixList: []*Suffix{&Suffix{Iter: true}, &Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$x"}}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "$x"}}}}, Op: OpAdd, 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}}}, Op: OpPipe, Right: &Query{Func: "combinations"}}}}}}}}}}}}}}, &FuncDef{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{&Query{Func: "n"}, &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "repeat", Args: []*Query{&Query{Func: "."}}}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "combinations"}}}}, "del": []*FuncDef{&FuncDef{Name: "del", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "delpaths", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{&Query{Func: "f"}}}}}}}}}}}}}}, "finites": []*FuncDef{&FuncDef{Name: "finites", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Func: "isfinite"}}}}}}}, "first": []*FuncDef{&FuncDef{Name: "first", Body: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}}, &FuncDef{Name: "first", Args: []string{"g"}, Body: &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{Ident: "$out", Body: &Query{Left: &Query{Func: "g"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}}}}}}}}, "fromdate": []*FuncDef{&FuncDef{Name: "fromdate", Body: &Query{Func: "fromdateiso8601"}}}, "fromdateiso8601": []*FuncDef{&FuncDef{Name: "fromdateiso8601", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "strptime", Args: []*Query{&Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "%Y-%m-%dT%H:%M:%S%z"}}}}}}}, Op: OpPipe, Right: &Query{Func: "mktime"}}}}, "fromstream": []*FuncDef{&FuncDef{Name: "fromstream", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{&ObjectKeyVal{Key: "x", Val: &ObjectVal{Queries: []*Query{&Query{Func: "null"}}}}, &ObjectKeyVal{Key: "e", Val: &ObjectVal{Queries: []*Query{&Query{Func: "false"}}}}}}, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$init"}}, Body: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "f"}}, Pattern: &Pattern{Name: "$i"}, Start: &Query{Func: "$init"}, Update: &Query{Left: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "e"}}}, Then: &Query{Func: "$init"}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "$i"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "length"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "2"}}}}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "e"}}}}}}, &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$i"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}}}, Op: OpPipe, Right: &Query{Left: &Query{Func: "length"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{&Query{Left: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "x"}}}}}}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$i"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}}}}, &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$i"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}}}}}}, Else: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "setpath", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "e"}}}}}}, &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$i"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}}}, Op: OpPipe, Right: &Query{Left: &Query{Func: "length"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}}}}}}, 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: "x"}}}, Else: &Query{Func: "empty"}}}}}}}}}}}}}}, "group_by": []*FuncDef{&FuncDef{Name: "group_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_group_by", Args: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, "gsub": []*FuncDef{&FuncDef{Name: "gsub", Args: []string{"$re", "str"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "sub", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "str"}, &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}}}}}}, &FuncDef{Name: "gsub", Args: []string{"$re", "str", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "sub", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "str"}, &Query{Left: &Query{Func: "$flags"}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}}}}}}}}, "in": []*FuncDef{&FuncDef{Name: "in", Args: []string{"xs"}, Body: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$x"}}, Body: &Query{Left: &Query{Func: "xs"}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "has", Args: []*Query{&Query{Func: "$x"}}}}}}}}}}}}}, "inputs": []*FuncDef{&FuncDef{Name: "inputs", Body: &Query{Term: &Term{Type: TermTypeTry, Try: &Try{Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "repeat", Args: []*Query{&Query{Func: "input"}}}}}, Catch: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "."}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "break"}}}}, Then: &Query{Func: "empty"}, Else: &Query{Func: "error"}}}}}}}}}, "inside": []*FuncDef{&FuncDef{Name: "inside", Args: []string{"xs"}, Body: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$x"}}, Body: &Query{Left: &Query{Func: "xs"}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "contains", Args: []*Query{&Query{Func: "$x"}}}}}}}}}}}}}, "isempty": []*FuncDef{&FuncDef{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{Func: "g"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "false"}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}}}}}, Op: OpComma, Right: &Query{Func: "true"}}}}}}}, "iterables": []*FuncDef{&FuncDef{Name: "iterables", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpPipe, Right: &Query{Left: &Query{Left: &Query{Func: "."}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}}, Op: OpOr, Right: &Query{Left: &Query{Func: "."}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}}}}}}}}}}, "last": []*FuncDef{&FuncDef{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"}}}}}}}}, &FuncDef{Name: "last", Args: []string{"g"}, Body: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}, Pattern: &Pattern{Name: "$item"}, Start: &Query{Func: "null"}, Update: &Query{Func: "$item"}}}}}}, "leaf_paths": []*FuncDef{&FuncDef{Name: "leaf_paths", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "paths", Args: []*Query{&Query{Func: "scalars"}}}}}}}, "limit": []*FuncDef{&FuncDef{Name: "limit", Args: []string{"$n", "g"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "$n"}, Op: OpGt, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, Then: &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{Ident: "$out", Body: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}, Pattern: &Pattern{Name: "$item"}, Start: &Query{Func: "$n"}, Update: &Query{Left: &Query{Func: "."}, Op: OpSub, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}, Extract: &Query{Left: &Query{Func: "$item"}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "."}, Op: OpLe, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, Then: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}, Else: &Query{Func: "empty"}}}}}}}}}}}, Elif: []*IfElif{&IfElif{Cond: &Query{Left: &Query{Func: "$n"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, Then: &Query{Func: "empty"}}}, Else: &Query{Func: "g"}}}}}}, "map": []*FuncDef{&FuncDef{Name: "map", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}}}}, Op: OpPipe, Right: &Query{Func: "f"}}}}}}}, "map_values": []*FuncDef{&FuncDef{Name: "map_values", Args: []string{"f"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}}}}, Op: OpModify, Right: &Query{Func: "f"}}}}, "match": []*FuncDef{&FuncDef{Name: "match", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "null"}}}}}}, &FuncDef{Name: "match", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_match", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "$flags"}, &Query{Func: "false"}}}, SuffixList: []*Suffix{&Suffix{Iter: true}}}}}}, "max_by": []*FuncDef{&FuncDef{Name: "max_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_max_by", Args: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, "min_by": []*FuncDef{&FuncDef{Name: "min_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_min_by", Args: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, "normals": []*FuncDef{&FuncDef{Name: "normals", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Func: "isnormal"}}}}}}}, "not": []*FuncDef{&FuncDef{Name: "not", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "."}, Then: &Query{Func: "false"}, Else: &Query{Func: "true"}}}}}}, "nth": []*FuncDef{&FuncDef{Name: "nth", Args: []string{"$n"}, Body: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Func: "$n"}}}}}, &FuncDef{Name: "nth", Args: []string{"$n", "g"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "$n"}, Op: OpLt, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "error", Args: []*Query{&Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "nth doesn't support negative indices"}}}}}}}, Else: &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{Ident: "$out", Body: &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "g"}}, Pattern: &Pattern{Name: "$item"}, Start: &Query{Left: &Query{Func: "$n"}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}, Update: &Query{Left: &Query{Func: "."}, Op: OpSub, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}, Extract: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "."}, Op: OpLe, Right: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}, Then: &Query{Left: &Query{Func: "$item"}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeBreak, Break: "$out"}}}, Else: &Query{Func: "empty"}}}}}}}}}}}}}}}, "nulls": []*FuncDef{&FuncDef{Name: "nulls", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "."}, Op: OpEq, Right: &Query{Func: "null"}}}}}}}}, "numbers": []*FuncDef{&FuncDef{Name: "numbers", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "number"}}}}}}}}}}, "objects": []*FuncDef{&FuncDef{Name: "objects", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}}}}}}}}, "paths": []*FuncDef{&FuncDef{Name: "paths", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{&Query{Func: ".."}}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "."}, Op: OpNe, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}}}}}}}}, &FuncDef{Name: "paths", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "paths"}, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$p"}}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "getpath", Args: []*Query{&Query{Func: "$p"}}}}}, Op: OpPipe, Right: &Query{Func: "f"}}}}}}, Op: OpPipe, Right: &Query{Func: "$p"}}}}}}}}}, "range": []*FuncDef{&FuncDef{Name: "range", Args: []string{"$end"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_range", Args: []*Query{&Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}, &Query{Func: "$end"}, &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}, &FuncDef{Name: "range", Args: []string{"$start", "$end"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_range", Args: []*Query{&Query{Func: "$start"}, &Query{Func: "$end"}, &Query{Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}}, &FuncDef{Name: "range", Args: []string{"$start", "$end", "$step"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_range", Args: []*Query{&Query{Func: "$start"}, &Query{Func: "$end"}, &Query{Func: "$step"}}}}}}}, "recurse": []*FuncDef{&FuncDef{Name: "recurse", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "recurse", Args: []*Query{&Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}, &Suffix{Optional: true}}}}}}}}}, &FuncDef{Name: "recurse", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "r", Body: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Func: "f"}, Op: OpPipe, Right: &Query{Func: "r"}}}}}}}, Func: "r"}}, &FuncDef{Name: "recurse", Args: []string{"f", "cond"}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "r", Body: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Func: "f"}, Op: OpPipe, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Func: "cond"}}}}}, Op: OpPipe, Right: &Query{Func: "r"}}}}}}}}, Func: "r"}}}, "repeat": []*FuncDef{&FuncDef{Name: "repeat", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "_repeat", Body: &Query{Left: &Query{Func: "f"}, Op: OpComma, Right: &Query{Func: "_repeat"}}}}, Func: "_repeat"}}}, "scalars": []*FuncDef{&FuncDef{Name: "scalars", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpPipe, Right: &Query{Left: &Query{Left: &Query{Func: "."}, Op: OpNe, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}}, Op: OpAnd, Right: &Query{Left: &Query{Func: "."}, Op: OpNe, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}}}}}}}}}}, "scan": []*FuncDef{&FuncDef{Name: "scan", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "scan", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "null"}}}}}}, &FuncDef{Name: "scan", Args: []string{"$re", "$flags"}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{&Query{Func: "$re"}, &Query{Left: &Query{Func: "$flags"}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "g"}}}}}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "captures"}}}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}}, 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{&Suffix{Iter: true}, &Suffix{Index: &Index{Name: "string"}}}}}}}}}}}}}}, "select": []*FuncDef{&FuncDef{Name: "select", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "f"}, Then: &Query{Func: "."}, Else: &Query{Func: "empty"}}}}}}, "sort_by": []*FuncDef{&FuncDef{Name: "sort_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_sort_by", Args: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, "splits": []*FuncDef{&FuncDef{Name: "splits", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "splits", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "null"}}}}}}, &FuncDef{Name: "splits", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "split", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "$flags"}}}, SuffixList: []*Suffix{&Suffix{Iter: true}}}}}}, "strings": []*FuncDef{&FuncDef{Name: "strings", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "string"}}}}}}}}}}, "sub": []*FuncDef{&FuncDef{Name: "sub", Args: []string{"$re", "str"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "sub", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "str"}, &Query{Func: "null"}}}}}}, &FuncDef{Name: "sub", Args: []string{"$re", "str", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$str"}}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "_sub", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "matches"}}}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{}}}}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$str"}, SuffixList: []*Suffix{&Suffix{Index: &Index{End: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "offset"}}}, IsSlice: true}}}}}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "string"}}}}, Else: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "matches"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Term: &Term{Type: TermTypeUnary, Unary: &Unary{Op: OpSub, Term: &Term{Type: TermTypeNumber, Number: "1"}}}}}}, &Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$r"}}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{&ObjectKeyVal{Key: "string", Val: &ObjectVal{Queries: []*Query{&Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Left: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Func: "$r"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "_capture"}, Op: OpPipe, Right: &Query{Func: "str"}}}}}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$str"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Start: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$r"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Name: "offset"}}}}}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$r"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Name: "length"}}}}}}, End: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "offset"}}}, IsSlice: true}}}}}}, Op: OpAdd, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "string"}}}}}}}}}, &ObjectKeyVal{Key: "offset", Val: &ObjectVal{Queries: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "$r"}, SuffixList: []*Suffix{&Suffix{Index: &Index{Name: "offset"}}}}}}}}, &ObjectKeyVal{Key: "matches", Val: &ObjectVal{Queries: []*Query{&Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Name: "matches"}, SuffixList: []*Suffix{&Suffix{Index: &Index{End: &Query{Term: &Term{Type: TermTypeUnary, Unary: &Unary{Op: OpSub, Term: &Term{Type: TermTypeNumber, Number: "1"}}}}, IsSlice: true}}}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "_sub"}}}}}}}}}}}}, Left: &Query{Term: &Term{Type: TermTypeObject, Object: &Object{KeyVals: []*ObjectKeyVal{&ObjectKeyVal{Key: "string", Val: &ObjectVal{Queries: []*Query{&Query{Term: &Term{Type: TermTypeString, Str: &String{}}}}}}, &ObjectKeyVal{Key: "matches", Val: &ObjectVal{Queries: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "match", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "$flags"}}}}}}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "_sub"}}}}}}}}}, "test": []*FuncDef{&FuncDef{Name: "test", Args: []string{"$re"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "test", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "null"}}}}}}, &FuncDef{Name: "test", Args: []string{"$re", "$flags"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_match", Args: []*Query{&Query{Func: "$re"}, &Query{Func: "$flags"}, &Query{Func: "true"}}}}}}}, "todate": []*FuncDef{&FuncDef{Name: "todate", Body: &Query{Func: "todateiso8601"}}}, "todateiso8601": []*FuncDef{&FuncDef{Name: "todateiso8601", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "strftime", Args: []*Query{&Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "%Y-%m-%dT%H:%M:%SZ"}}}}}}}}}, "tostream": []*FuncDef{&FuncDef{Name: "tostream", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{&Query{FuncDefs: []*FuncDef{&FuncDef{Name: "r", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}, &Suffix{Optional: true}}}}, Op: OpPipe, Right: &Query{Func: "r"}}}}, Op: OpComma, Right: &Query{Func: "."}}}}, Func: "r"}}}, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$p"}}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "getpath", Args: []*Query{&Query{Func: "$p"}}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{&Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Iter: true}, &Suffix{Optional: true}}}}}}}, Pattern: &Pattern{Name: "$q"}, Start: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "$p"}, Op: OpComma, Right: &Query{Func: "."}}}}}, Update: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "$p"}, Op: OpAdd, Right: &Query{Func: "$q"}}}}}}}}}}}}}}}}, "truncate_stream": []*FuncDef{&FuncDef{Name: "truncate_stream", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{&Suffix{Bind: &Bind{Patterns: []*Pattern{&Pattern{Name: "$n"}}, Body: &Query{Left: &Query{Func: "null"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "f"}, Op: OpPipe, 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"}}}}}, Op: OpPipe, Right: &Query{Left: &Query{Func: "length"}, Op: OpGt, Right: &Query{Func: "$n"}}}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}, Op: OpModify, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Func: "$n"}, IsSlice: true}}}}, Else: &Query{Func: "empty"}}}}}}}}}}}}}, "unique_by": []*FuncDef{&FuncDef{Name: "unique_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_unique_by", Args: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, "until": []*FuncDef{&FuncDef{Name: "until", Args: []string{"cond", "next"}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "_until", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "cond"}, Then: &Query{Func: "."}, Else: &Query{Left: &Query{Func: "next"}, Op: OpPipe, Right: &Query{Func: "_until"}}}}}}}, Func: "_until"}}}, "values": []*FuncDef{&FuncDef{Name: "values", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{&Query{Left: &Query{Func: "."}, Op: OpNe, Right: &Query{Func: "null"}}}}}}}}, "walk": []*FuncDef{&FuncDef{Name: "walk", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "_walk", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Func: "_walk"}}}}}, Elif: []*IfElif{&IfElif{Cond: &Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map_values", Args: []*Query{&Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "last", Args: []*Query{&Query{Func: "_walk"}}}}}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "f"}}}}, Func: "_walk"}}}, "while": []*FuncDef{&FuncDef{Name: "while", Args: []string{"cond", "update"}, Body: &Query{FuncDefs: []*FuncDef{&FuncDef{Name: "_while", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "cond"}, Then: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Func: "update"}, Op: OpPipe, Right: &Query{Func: "_while"}}}}}, Else: &Query{Func: "empty"}}}}}}, Func: "_while"}}}, "with_entries": []*FuncDef{&FuncDef{Name: "with_entries", Args: []string{"f"}, Body: &Query{Left: &Query{Func: "to_entries"}, Op: OpPipe, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{&Query{Func: "f"}}}}}, Op: OpPipe, Right: &Query{Func: "from_entries"}}}}}, } } gojq-0.12.13/builtin.jq000066400000000000000000000115541443612442000146260ustar00rootroot00000000000000def not: if . then false else true end; def in(xs): . as $x | xs | has($x); def map(f): [.[] | f]; def with_entries(f): to_entries | map(f) | from_entries; 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 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 leaf_paths: paths(scalars); def inside(xs): . as $x | xs | contains($x); 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(last(_walk)) end | f; _walk; def first: .[0]; def first(g): label $out | g | ., break $out; def last: .[-1]; def last(g): reduce g as $item (null; $item); 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 g end; def nth($n): .[$n]; def nth($n; g): if $n < 0 then error("nth doesn't support negative indices") else label $out | foreach g as $item ( $n + 1; . - 1; if . <= 0 then $item, break $out else empty end ) end; def truncate_stream(f): . as $n | null | f | if .[0] | length > $n then .[0] |= .[$n:] else empty end; def fromstream(f): { x: null, e: false } as $init | foreach f as $i ( $init; if .e then $init end | if $i | length == 2 then setpath(["e"]; $i[0] | length == 0) | setpath(["x"] + $i[0]; $i[1]) else setpath(["e"]; $i[0] | length == 1) end; if .e then .x else empty end ); def tostream: path(def r: (.[]? | r), .; r) as $p | getpath($p) | reduce path(.[]?) as $q ([$p, .]; [$p + $q]); def map_values(f): .[] |= f; def del(f): delpaths([path(f)]); def paths: path(..) | select(. != []); def paths(f): paths as $p | select(getpath($p) | f) | $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) | _capture; 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): splits($re; null); def splits($re; $flags): split($re; $flags)[]; def sub($re; str): sub($re; str; null); def sub($re; str; $flags): . as $str | def _sub: if .matches == [] then $str[:.offset] + .string else .matches[-1] as $r | { string: (($r | _capture | str) + $str[$r.offset+$r.length:.offset] + .string), offset: $r.offset, matches: .matches[:-1], } | _sub end; { string: "", matches: [match($re; $flags)] } | _sub; 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; .); gojq-0.12.13/cli/000077500000000000000000000000001443612442000133655ustar00rootroot00000000000000gojq-0.12.13/cli/cli.go000066400000000000000000000304221443612442000144640ustar00rootroot00000000000000// Package cli implements the gojq command. package cli import ( "errors" "fmt" "io" "os" "path/filepath" "runtime" "strings" "github.com/mattn/go-isatty" "github.com/itchyny/gojq" ) const name = "gojq" const version = "0.12.13" var revision = "HEAD" const ( exitCodeOK = iota exitCodeFalsyErr exitCodeFlagParseErr exitCodeCompileErr exitCodeNoValueErr exitCodeDefaultErr ) type cli struct { inStream io.Reader outStream io.Writer errStream io.Writer outputRaw bool outputJoin bool outputNul bool outputCompact bool outputIndent *int outputTab bool outputYAML bool inputRaw bool inputStream bool inputYAML bool inputSlurp bool argnames []string argvalues []any outputYAMLSeparator bool exitCodeError error } type flagopts struct { OutputRaw bool `short:"r" long:"raw-output" description:"output raw strings"` OutputJoin bool `short:"j" long:"join-output" description:"output without newlines"` OutputNul bool `short:"0" long:"nul-output" description:"output with NUL character"` OutputCompact bool `short:"c" long:"compact-output" description:"output without pretty-printing"` OutputIndent *int `long:"indent" description:"number of spaces for indentation"` OutputTab bool `long:"tab" description:"use tabs for indentation"` OutputYAML bool `long:"yaml-output" description:"output in YAML format"` OutputColor bool `short:"C" long:"color-output" description:"output with colors even if piped"` OutputMono bool `short:"M" long:"monochrome-output" description:"output without colors"` InputNull bool `short:"n" long:"null-input" description:"use null as input value"` InputRaw bool `short:"R" long:"raw-input" description:"read input as raw strings"` InputStream bool `long:"stream" description:"parse input in stream fashion"` InputYAML bool `long:"yaml-input" description:"read input as YAML format"` InputSlurp bool `short:"s" long:"slurp" description:"read all inputs into an array"` FromFile string `short:"f" long:"from-file" description:"load query from file"` ModulePaths []string `short:"L" description:"directory to search modules from"` Arg map[string]string `long:"arg" description:"set a string value to a variable"` ArgJSON map[string]string `long:"argjson" description:"set a JSON value to a variable"` SlurpFile map[string]string `long:"slurpfile" description:"set the JSON contents of a file to a variable"` RawFile map[string]string `long:"rawfile" description:"set the contents of a file to a variable"` Args []any `long:"args" positional:"" description:"consume remaining arguments as positional string values"` JSONArgs []any `long:"jsonargs" positional:"" description:"consume remaining arguments as positional JSON values"` ExitStatus bool `short:"e" long:"exit-status" description:"exit 1 when the last value is false or null"` Version bool `short:"v" long:"version" description:"display version information"` Help bool `short:"h" long:"help" description:"display this help information"` } var addDefaultModulePaths = true func (cli *cli) run(args []string) int { if err := cli.runInternal(args); err != nil { cli.printError(err) if err, ok := err.(interface{ ExitCode() int }); ok { return err.ExitCode() } return exitCodeDefaultErr } return exitCodeOK } func (cli *cli) runInternal(args []string) (err error) { var opts flagopts args, err = parseFlags(args, &opts) if err != nil { return &flagParseError{err} } if opts.Help { fmt.Fprintf(cli.outStream, `%[1]s - Go implementation of jq Version: %s (rev: %s/%s) Synopsis: %% echo '{"foo": 128}' | %[1]s '.foo' Usage: %[1]s [OPTIONS] `, name, version, revision, runtime.Version()) fmt.Fprintln(cli.outStream, formatFlags(&opts)) return nil } if opts.Version { fmt.Fprintf(cli.outStream, "%s %s (rev: %s/%s)\n", name, version, revision, runtime.Version()) return nil } cli.outputRaw, cli.outputJoin, cli.outputNul, cli.outputCompact, cli.outputIndent, cli.outputTab, cli.outputYAML = opts.OutputRaw, opts.OutputJoin, opts.OutputNul, opts.OutputCompact, opts.OutputIndent, opts.OutputTab, opts.OutputYAML defer func(x bool) { noColor = x }(noColor) if opts.OutputColor || opts.OutputMono { noColor = opts.OutputMono } else if os.Getenv("NO_COLOR") != "" || os.Getenv("TERM") == "dumb" { noColor = true } else { f, ok := cli.outStream.(interface{ Fd() uintptr }) noColor = !(ok && (isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd()))) } if !noColor { if colors := os.Getenv("GOJQ_COLORS"); colors != "" { if err := setColors(colors); err != nil { return err } } } if i := cli.outputIndent; i != nil { if *i > 9 { return fmt.Errorf("too many indentation count: %d", *i) } else if *i < 0 { return fmt.Errorf("negative indentation count: %d", *i) } } if opts.OutputYAML && opts.OutputTab { return errors.New("cannot use tabs for YAML output") } cli.inputRaw, cli.inputStream, cli.inputYAML, cli.inputSlurp = opts.InputRaw, opts.InputStream, opts.InputYAML, opts.InputSlurp for k, v := range opts.Arg { cli.argnames = append(cli.argnames, "$"+k) cli.argvalues = append(cli.argvalues, v) } for k, v := range opts.ArgJSON { val, _ := newJSONInputIter(strings.NewReader(v), "$"+k).Next() if err, ok := val.(error); ok { return err } cli.argnames = append(cli.argnames, "$"+k) cli.argvalues = append(cli.argvalues, val) } for k, v := range opts.SlurpFile { val, err := slurpFile(v) if err != nil { return err } cli.argnames = append(cli.argnames, "$"+k) cli.argvalues = append(cli.argvalues, val) } for k, v := range opts.RawFile { val, err := os.ReadFile(v) if err != nil { return err } cli.argnames = append(cli.argnames, "$"+k) cli.argvalues = append(cli.argvalues, string(val)) } named := make(map[string]any, len(cli.argnames)) for i, name := range cli.argnames { named[name[1:]] = cli.argvalues[i] } positional := opts.Args for i, v := range opts.JSONArgs { if v != nil { val, _ := newJSONInputIter(strings.NewReader(v.(string)), "--jsonargs").Next() if err, ok := val.(error); ok { return err } if i < len(positional) { positional[i] = val } else { positional = append(positional, val) } } } cli.argnames = append(cli.argnames, "$ARGS") cli.argvalues = append(cli.argvalues, map[string]any{ "named": named, "positional": positional, }) var arg, fname string if opts.FromFile != "" { src, err := os.ReadFile(opts.FromFile) if err != nil { return err } arg, fname = string(src), opts.FromFile } else if len(args) == 0 { arg = "." } else { arg, fname = strings.TrimSpace(args[0]), "" args = args[1:] } if opts.ExitStatus { cli.exitCodeError = &exitCodeError{exitCodeNoValueErr} defer func() { if _, ok := err.(interface{ ExitCode() int }); !ok { err = cli.exitCodeError } }() } query, err := gojq.Parse(arg) if err != nil { return &queryParseError{fname, arg, err} } modulePaths := opts.ModulePaths if len(modulePaths) == 0 && addDefaultModulePaths { modulePaths = listDefaultModulePaths() } iter := cli.createInputIter(args) defer iter.Close() code, err := gojq.Compile(query, gojq.WithModuleLoader(gojq.NewModuleLoader(modulePaths)), gojq.WithEnvironLoader(os.Environ), gojq.WithVariables(cli.argnames), gojq.WithFunction("debug", 0, 0, cli.funcDebug), gojq.WithFunction("stderr", 0, 0, cli.funcStderr), gojq.WithFunction("input_filename", 0, 0, func(iter inputIter) func(any, []any) any { return func(any, []any) any { if fname := iter.Name(); fname != "" && (len(args) > 0 || !opts.InputNull) { return fname } return nil } }(iter), ), gojq.WithInputIter(iter), ) if err != nil { if err, ok := err.(interface { QueryParseError() (string, string, error) }); ok { name, query, err := err.QueryParseError() return &queryParseError{name, query, err} } if err, ok := err.(interface { JSONParseError() (string, string, error) }); ok { fname, contents, err := err.JSONParseError() return &compileError{&jsonParseError{fname, contents, 0, err}} } return &compileError{err} } if opts.InputNull { iter = newNullInputIter() } return cli.process(iter, code) } func listDefaultModulePaths() []string { modulePaths := []string{"", "../lib/gojq", "lib"} if executable, err := os.Executable(); err == nil { if executable, err := filepath.EvalSymlinks(executable); err == nil { origin := filepath.Dir(executable) modulePaths[1] = filepath.Join(origin, modulePaths[1]) modulePaths[2] = filepath.Join(origin, modulePaths[2]) } } if homeDir, err := os.UserHomeDir(); err == nil { modulePaths[0] = filepath.Join(homeDir, ".jq") } else { modulePaths = modulePaths[1:] } return modulePaths } func slurpFile(name string) (any, error) { iter := newSlurpInputIter( newFilesInputIter(newJSONInputIter, []string{name}, nil), ) defer iter.Close() val, _ := iter.Next() if err, ok := val.(error); ok { return nil, err } return val, nil } func (cli *cli) createInputIter(args []string) (iter inputIter) { var newIter func(io.Reader, string) inputIter switch { case cli.inputRaw: if cli.inputSlurp { newIter = newReadAllIter } else { newIter = newRawInputIter } case cli.inputStream: newIter = newStreamInputIter case cli.inputYAML: newIter = newYAMLInputIter default: newIter = newJSONInputIter } if cli.inputSlurp { defer func() { if cli.inputRaw { iter = newSlurpRawInputIter(iter) } else { iter = newSlurpInputIter(iter) } }() } if len(args) == 0 { return newIter(cli.inStream, "") } return newFilesInputIter(newIter, args, cli.inStream) } func (cli *cli) process(iter inputIter, code *gojq.Code) error { var err error for { v, ok := iter.Next() if !ok { return err } if er, ok := v.(error); ok { cli.printError(er) err = &emptyError{er} continue } if er := cli.printValues(code.Run(v, cli.argvalues...)); er != nil { cli.printError(er) err = &emptyError{er} } } } func (cli *cli) printValues(iter gojq.Iter) error { m := cli.createMarshaler() for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { return err } if cli.outputYAMLSeparator { cli.outStream.Write([]byte("---\n")) } else { cli.outputYAMLSeparator = cli.outputYAML } if err := m.marshal(v, cli.outStream); err != nil { return err } if cli.exitCodeError != nil { if v == nil || v == false { cli.exitCodeError = &exitCodeError{exitCodeFalsyErr} } else { cli.exitCodeError = &exitCodeError{exitCodeOK} } } if !cli.outputJoin && !cli.outputYAML { if cli.outputNul { cli.outStream.Write([]byte{'\x00'}) } else { cli.outStream.Write([]byte{'\n'}) } } } return nil } func (cli *cli) createMarshaler() marshaler { if cli.outputYAML { return yamlFormatter(cli.outputIndent) } indent := 2 if cli.outputCompact { indent = 0 } else if cli.outputTab { indent = 1 } else if i := cli.outputIndent; i != nil { indent = *i } f := newEncoder(cli.outputTab, indent) if cli.outputRaw || cli.outputJoin || cli.outputNul { return &rawMarshaler{f} } return f } func (cli *cli) funcDebug(v any, _ []any) any { if err := newEncoder(false, 0).marshal([]any{"DEBUG:", v}, cli.errStream); err != nil { return err } if _, err := cli.errStream.Write([]byte{'\n'}); err != nil { return err } return v } func (cli *cli) funcStderr(v any, _ []any) any { if err := newEncoder(false, 0).marshal(v, cli.errStream); err != nil { return err } return v } func (cli *cli) printError(err error) { if er, ok := err.(interface{ IsEmptyError() bool }); !ok || !er.IsEmptyError() { if er, ok := err.(interface{ IsHaltError() bool }); !ok || !er.IsHaltError() { fmt.Fprintf(cli.errStream, "%s: %s\n", name, err) } else if er, ok := err.(gojq.ValueError); ok { v := er.Value() if str, ok := v.(string); ok { cli.errStream.Write([]byte(str)) } else { bs, _ := gojq.Marshal(v) cli.errStream.Write(bs) cli.errStream.Write([]byte{'\n'}) } } } } gojq-0.12.13/cli/cli_test.go000066400000000000000000000063451443612442000155320ustar00rootroot00000000000000package cli import ( "os" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "gopkg.in/yaml.v3" ) func init() { addDefaultModulePaths = false os.Setenv("NO_COLOR", "") os.Setenv("GOJQ_COLORS", "") } func setLocation(loc *time.Location) func() { orig := time.Local time.Local = loc return func() { time.Local = orig } } func TestCliRun(t *testing.T) { f, err := os.Open("test.yaml") if err != nil { t.Fatal(err) } defer f.Close() defer setLocation(time.FixedZone("UTC-7", -7*60*60))() errorReplacer := strings.NewReplacer( name+": ", "", "testdata\\", "testdata/", "flag `/", "flag `--", ) var testCases []struct { Name string Args []string Input string Env []string Expected string Error string ExitCode int `yaml:"exit_code"` } if err = yaml.NewDecoder(f).Decode(&testCases); err != nil { t.Fatal(err) } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { defer func() { if err := recover(); err != nil { t.Fatal(err) } }() var outStream, errStream strings.Builder cli := cli{ inStream: strings.NewReader(tc.Input), outStream: &outStream, errStream: &errStream, } for _, env := range tc.Env { xs := strings.SplitN(env, "=", 2) k, v := xs[0], xs[1] defer func(v string) { os.Setenv(k, v) }(os.Getenv(k)) if k == "GOJQ_COLORS" { defer func(colors [][]byte) { nullColor, falseColor, trueColor, numberColor, stringColor, objectKeyColor, arrayColor, objectColor = colors[0], colors[1], colors[2], colors[3], colors[4], colors[5], colors[6], colors[7] }([][]byte{ nullColor, falseColor, trueColor, numberColor, stringColor, objectKeyColor, arrayColor, objectColor, }) } os.Setenv(k, v) } code := cli.run(tc.Args) if tc.Error == "" { if code != tc.ExitCode { t.Errorf("exit code: got: %v, expected: %v", code, tc.ExitCode) } if diff := cmp.Diff(tc.Expected, outStream.String()); diff != "" { t.Error("standard output:\n" + diff) } if diff := cmp.Diff("", errStream.String()); diff != "" { t.Error("standard error output:\n" + diff) } } else { errStr := errStream.String() if strings.Contains(errStr, "DEBUG:") { if code != exitCodeOK { t.Errorf("exit code: got: %v, expected: %v", code, exitCodeOK) } } else if tc.ExitCode != 0 { if code != tc.ExitCode { t.Errorf("exit code: got: %v, expected: %v", code, tc.ExitCode) } } else { if code != exitCodeDefaultErr { t.Errorf("exit code: got: %v, expected: %v", code, exitCodeDefaultErr) } } if diff := cmp.Diff(tc.Expected, outStream.String()); diff != "" { t.Error("standard output:\n" + diff) } if got, expected := errorReplacer.Replace(errStr), strings.TrimSpace(tc.Error); !strings.Contains(got, expected) { t.Error("standard error output:\n" + cmp.Diff(expected, got)) } if !strings.HasSuffix(errStr, "\n") && !strings.Contains(tc.Name, "stderr") && !strings.Contains(tc.Name, "halt_error") { t.Error(`standard error output should end with "\n"`) } if strings.HasSuffix(errStr, "\n\n") { t.Error(`standard error output should not end with "\n\n"`) } } }) } } gojq-0.12.13/cli/color.go000066400000000000000000000027171443612442000150410ustar00rootroot00000000000000package cli import ( "bytes" "fmt" "strings" ) var noColor bool func newColor(c string) []byte { return []byte("\x1b[" + c + "m") } func setColor(buf *bytes.Buffer, color []byte) { if !noColor { buf.Write(color) } } var ( resetColor = newColor("0") // Reset nullColor = newColor("90") // Bright black falseColor = newColor("33") // Yellow trueColor = newColor("33") // Yellow numberColor = newColor("36") // Cyan stringColor = newColor("32") // Green objectKeyColor = newColor("34;1") // Bold Blue arrayColor = []byte(nil) // No color objectColor = []byte(nil) // No color ) func validColor(x string) bool { var num bool for _, c := range x { if '0' <= c && c <= '9' { num = true } else if c == ';' && num { num = false } else { return false } } return num || x == "" } func setColors(colors string) error { var i int var color string for _, target := range []*[]byte{ &nullColor, &falseColor, &trueColor, &numberColor, &stringColor, &objectKeyColor, &arrayColor, &objectColor, } { if i < len(colors) { if j := strings.IndexByte(colors[i:], ':'); j >= 0 { color = colors[i : i+j] i += j + 1 } else { color = colors[i:] i = len(colors) } if !validColor(color) { return fmt.Errorf("invalid color: %q", color) } if color == "" { *target = nil } else { *target = newColor(color) } } else { *target = nil } } return nil } gojq-0.12.13/cli/encoder.go000066400000000000000000000121631443612442000153360ustar00rootroot00000000000000package cli import ( "bytes" "fmt" "io" "math" "math/big" "sort" "strconv" "unicode/utf8" ) type encoder struct { out io.Writer w *bytes.Buffer tab bool indent int depth int buf [64]byte } func newEncoder(tab bool, indent int) *encoder { // reuse the buffer in multiple calls of marshal return &encoder{w: new(bytes.Buffer), tab: tab, indent: indent} } func (e *encoder) flush() error { _, err := e.out.Write(e.w.Bytes()) e.w.Reset() return err } func (e *encoder) marshal(v any, w io.Writer) error { e.out = w err := e.encode(v) if ferr := e.flush(); ferr != nil && err == nil { err = ferr } return err } func (e *encoder) encode(v any) error { switch v := v.(type) { case nil: e.write([]byte("null"), nullColor) case bool: if v { e.write([]byte("true"), trueColor) } else { e.write([]byte("false"), falseColor) } case int: e.write(strconv.AppendInt(e.buf[:0], int64(v), 10), numberColor) case float64: e.encodeFloat64(v) case *big.Int: e.write(v.Append(e.buf[:0], 10), numberColor) case string: e.encodeString(v, stringColor) case []any: if err := e.encodeArray(v); err != nil { return err } case map[string]any: if err := e.encodeObject(v); err != nil { return err } default: panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v)) } if e.w.Len() > 8*1024 { return e.flush() } return nil } // ref: floatEncoder in encoding/json func (e *encoder) encodeFloat64(f float64) { if math.IsNaN(f) { e.write([]byte("null"), nullColor) return } if f >= math.MaxFloat64 { f = math.MaxFloat64 } else if f <= -math.MaxFloat64 { f = -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.write(buf, numberColor) } // ref: encodeState#string in encoding/json func (e *encoder) encodeString(s string, color []byte) { if color != nil { setColor(e.w, color) } 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('"') if color != nil { setColor(e.w, resetColor) } } func (e *encoder) encodeArray(vs []any) error { e.writeByte('[', arrayColor) e.depth += e.indent for i, v := range vs { if i > 0 { e.writeByte(',', arrayColor) } if e.indent != 0 { e.writeIndent() } if err := e.encode(v); err != nil { return err } } e.depth -= e.indent if len(vs) > 0 && e.indent != 0 { e.writeIndent() } e.writeByte(']', arrayColor) return nil } func (e *encoder) encodeObject(vs map[string]any) error { e.writeByte('{', objectColor) e.depth += e.indent 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.writeByte(',', objectColor) } if e.indent != 0 { e.writeIndent() } e.encodeString(kv.key, objectKeyColor) e.writeByte(':', objectColor) if e.indent != 0 { e.w.WriteByte(' ') } if err := e.encode(kv.val); err != nil { return err } } e.depth -= e.indent if len(vs) > 0 && e.indent != 0 { e.writeIndent() } e.writeByte('}', objectColor) return nil } func (e *encoder) writeIndent() { e.w.WriteByte('\n') if n := e.depth; n > 0 { if e.tab { e.writeIndentInternal(n, "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t") } else { e.writeIndentInternal(n, " ") } } } func (e *encoder) writeIndentInternal(n int, spaces string) { if l := len(spaces); n <= l { e.w.WriteString(spaces[:n]) } else { e.w.WriteString(spaces) for n -= l; n > 0; n, l = n-l, l*2 { if n < l { l = n } e.w.Write(e.w.Bytes()[e.w.Len()-l:]) } } } func (e *encoder) writeByte(b byte, color []byte) { if color == nil { e.w.WriteByte(b) } else { setColor(e.w, color) e.w.WriteByte(b) setColor(e.w, resetColor) } } func (e *encoder) write(bs []byte, color []byte) { if color == nil { e.w.Write(bs) } else { setColor(e.w, color) e.w.Write(bs) setColor(e.w, resetColor) } } gojq-0.12.13/cli/error.go000066400000000000000000000125011443612442000150440ustar00rootroot00000000000000package cli import ( "encoding/json" "fmt" "io" "strconv" "strings" "unicode/utf8" "github.com/mattn/go-runewidth" ) type emptyError struct { err error } func (*emptyError) Error() string { return "" } func (*emptyError) IsEmptyError() bool { return true } func (err *emptyError) ExitCode() int { if err, ok := err.err.(interface{ ExitCode() int }); ok { return err.ExitCode() } return exitCodeDefaultErr } type exitCodeError struct { code int } func (err *exitCodeError) Error() string { return "exit code: " + strconv.Itoa(err.code) } func (err *exitCodeError) IsEmptyError() bool { return true } func (err *exitCodeError) ExitCode() int { return err.code } type flagParseError struct { err error } func (err *flagParseError) Error() string { return err.err.Error() } func (err *flagParseError) ExitCode() int { return exitCodeFlagParseErr } type compileError struct { err error } func (err *compileError) Error() string { return "compile error: " + err.err.Error() } func (err *compileError) ExitCode() int { return exitCodeCompileErr } type queryParseError struct { fname, contents string err error } func (err *queryParseError) Error() string { var offset int if e, ok := err.err.(interface{ Token() (string, int) }); ok { var token string token, offset = e.Token() offset -= len(token) - 1 } linestr, line, column := getLineByOffset(err.contents, offset) if err.fname != "" || containsNewline(err.contents) { return fmt.Sprintf("invalid query: %s:%d\n%s %s", err.fname, line, formatLineInfo(linestr, line, column), err.err) } return fmt.Sprintf("invalid query: %s\n %s\n%s ^ %s", err.contents, linestr, strings.Repeat(" ", column), err.err) } func (err *queryParseError) ExitCode() int { return exitCodeCompileErr } type jsonParseError struct { fname, contents string line int err error } func (err *jsonParseError) Error() string { var offset int if err.err == io.ErrUnexpectedEOF { offset = len(err.contents) + 1 } else if e, ok := err.err.(*json.SyntaxError); ok { offset = int(e.Offset) } linestr, line, column := getLineByOffset(err.contents, offset) if line += err.line; line > 1 { return fmt.Sprintf("invalid json: %s:%d\n%s %s", err.fname, line, formatLineInfo(linestr, line, column), err.err) } return fmt.Sprintf("invalid json: %s\n %s\n%s ^ %s", err.fname, linestr, strings.Repeat(" ", column), err.err) } type yamlParseError struct { fname, contents string err error } func (err *yamlParseError) Error() string { var line int msg := strings.TrimPrefix( strings.TrimPrefix(err.err.Error(), "yaml: "), "unmarshal errors:\n ") if fmt.Sscanf(msg, "line %d: ", &line); line == 0 { return "invalid yaml: " + err.fname } msg = msg[strings.Index(msg, ": ")+2:] if i := strings.IndexByte(msg, '\n'); i >= 0 { msg = msg[:i] } linestr := getLineByLine(err.contents, line) return fmt.Sprintf("invalid yaml: %s:%d\n%s %s", err.fname, line, formatLineInfo(linestr, line, 0), msg) } func getLineByOffset(str string, offset int) (linestr string, line, column int) { ss := &stringScanner{str, 0} for { str, start, ok := ss.next() if !ok { offset -= start break } line++ linestr = str if ss.offset >= offset { offset -= start break } } if offset > len(linestr) { offset = len(linestr) } else if offset > 0 { offset-- } else { offset = 0 } if offset > 48 { skip := len(trimLastInvalidRune(linestr[:offset-48])) linestr = linestr[skip:] offset -= skip } if len(linestr) > 64 { linestr = linestr[:64] } linestr = trimLastInvalidRune(linestr) if offset >= len(linestr) { offset = len(linestr) } else { offset = len(trimLastInvalidRune(linestr[:offset])) } column = runewidth.StringWidth(linestr[:offset]) return } func getLineByLine(str string, line int) (linestr string) { ss := &stringScanner{str, 0} for { str, _, ok := ss.next() if !ok { break } if line--; line == 0 { linestr = str break } } if len(linestr) > 64 { linestr = trimLastInvalidRune(linestr[:64]) } return } func trimLastInvalidRune(s string) string { for i := len(s) - 1; i >= 0 && i > len(s)-utf8.UTFMax; i-- { if b := s[i]; b < utf8.RuneSelf { return s[:i+1] } else if utf8.RuneStart(b) { if r, _ := utf8.DecodeRuneInString(s[i:]); r == utf8.RuneError { return s[:i] } break } } return s } func formatLineInfo(linestr string, line, column int) string { l := strconv.Itoa(line) return " " + l + " | " + linestr + "\n" + strings.Repeat(" ", len(l)+column) + " ^" } type stringScanner struct { str string offset int } func (ss *stringScanner) next() (line string, start int, ok bool) { if ss.offset == len(ss.str) { return } start, ok = ss.offset, true line = ss.str[start:] i := indexNewline(line) if i < 0 { ss.offset = len(ss.str) return } line = line[:i] if strings.HasPrefix(ss.str[start+i:], "\r\n") { i++ } ss.offset += i + 1 return } // Faster than strings.ContainsAny(str, "\r\n"). func containsNewline(str string) bool { return strings.IndexByte(str, '\n') >= 0 || strings.IndexByte(str, '\r') >= 0 } // Faster than strings.IndexAny(str, "\r\n"). func indexNewline(str string) (i int) { if i = strings.IndexByte(str, '\n'); i >= 0 { str = str[:i] } if j := strings.IndexByte(str, '\r'); j >= 0 { i = j } return } gojq-0.12.13/cli/error_test.go000066400000000000000000000065721443612442000161160ustar00rootroot00000000000000package cli import ( "fmt" "strings" "testing" ) func generateString(size int) string { var sb strings.Builder sb.Grow(size) for i, j := 0, 0; i < size; i, j = i+1, (i+j)%256 { sb.WriteByte(byte(j%10 | '0')) } return sb.String() } func TestGetLineByOffset(t *testing.T) { numbers := generateString(500) testCases := []struct { str string offset int linestr string line, column int }{ { "", 0, "", 0, 0, }, { "abc", -1, "abc", 1, 0, }, { "abc", 0, "abc", 1, 0, }, { "abc", 1, "abc", 1, 0, }, { "abc", 2, "abc", 1, 1, }, { "abc", 3, "abc", 1, 2, }, { "abc", 4, "abc", 1, 3, }, { "abc\ndef\nghi", 4, "abc", 1, 3, }, { "abc\rdef\rghi", 4, "abc", 1, 3, }, { "abc\r\ndef\r\nghi", 4, "abc", 1, 3, }, { "abc\ndef\nghi", 5, "def", 2, 0, }, { "abc\rdef\rghi", 5, "def", 2, 0, }, { "abc\r\ndef\r\nghi", 6, "def", 2, 0, }, { "abc\ndef\nghi", 7, "def", 2, 2, }, { "abc\ndef\nghi", 8, "def", 2, 3, }, { "abc\ndef\nghi", 9, "ghi", 3, 0, }, { "abc\ndef\nghi", 12, "ghi", 3, 3, }, { "abc\ndef\nghi", 13, "ghi", 3, 3, }, { "abc\n012\nghi", 5, "012", 2, 0, }, { "abc\n012\nghi", 6, "012", 2, 0, }, { "abc\n012\nghi", 7, "012", 2, 0, }, { "abc\n012\nghi", 8, "012", 2, 2, }, { "abc\n012\nghi", 9, "012", 2, 2, }, { "abc\n012\nghi", 10, "012", 2, 2, }, { "abc\n012\nghi", 11, "012", 2, 4, }, { "abc\ndef\xef\xbc\nghi", 10, "def", 2, 3, }, { numbers, 0, numbers[:64], 1, 0, }, { numbers, 30, numbers[:64], 1, 29, }, { numbers, 100, numbers[51:115], 1, 48, }, { numbers, 400, numbers[351:415], 1, 48, }, { numbers, 450, numbers[401:465], 1, 48, }, { numbers, 500, numbers[451:], 1, 48, }, } for _, tc := range testCases { var name string if len(tc.str) > 20 { name = tc.str[:20] + "..." } else { name = tc.str } t.Run(fmt.Sprintf("%q,%d", name, tc.offset), func(t *testing.T) { linestr, line, column := getLineByOffset(tc.str, tc.offset) if linestr != tc.linestr || line != tc.line || column != tc.column { t.Errorf("getLineByOffset(%q, %d):\n"+ " got: %q, %d, %d\n"+ "expected: %q, %d, %d", tc.str, tc.offset, linestr, line, column, tc.linestr, tc.line, tc.column) } }) } } func TestGetLineByLine(t *testing.T) { testCases := []struct { str string line int linestr string }{ { "", 0, "", }, { "abc", -1, "", }, { "abc", 0, "", }, { "abc", 1, "abc", }, { "abc\n", 1, "abc", }, { "abc", 2, "", }, { "abc\n", 2, "", }, { "abc\ndef\nghi", 1, "abc", }, { "abc\ndef\nghi", 2, "def", }, { "abc\rdef\rghi", 2, "def", }, { "abc\r\ndef\r\nghi", 2, "def", }, { "abc\ndef\nghi", 3, "ghi", }, { "abc\ndef\nghi", 4, "", }, } for _, tc := range testCases { t.Run(fmt.Sprintf("%q,%d", tc.str, tc.line), func(t *testing.T) { linestr := getLineByLine(tc.str, tc.line) if linestr != tc.linestr { t.Errorf("getLineByLine(%q, %d):\n"+ " got: %q\n"+ "expected: %q", tc.str, tc.line, linestr, tc.linestr) } }) } } gojq-0.12.13/cli/flags.go000066400000000000000000000121101443612442000150030ustar00rootroot00000000000000package cli import ( "fmt" "reflect" "strconv" "strings" ) func parseFlags(args []string, opts any) ([]string, error) { rest := make([]string, 0, len(args)) val := reflect.ValueOf(opts).Elem() typ := val.Type() longToValue := map[string]reflect.Value{} longToPositional := map[string]struct{}{} shortToValue := map[string]reflect.Value{} for i, l := 0, val.NumField(); i < l; i++ { if flag, ok := typ.Field(i).Tag.Lookup("long"); ok { longToValue[flag] = val.Field(i) if _, ok := typ.Field(i).Tag.Lookup("positional"); ok { longToPositional[flag] = struct{}{} } } if flag, ok := typ.Field(i).Tag.Lookup("short"); ok { shortToValue[flag] = val.Field(i) } } mapKeys := map[string]struct{}{} var positionalVal reflect.Value for i := 0; i < len(args); i++ { arg := args[i] var ( val reflect.Value ok bool shortopts string ) if arg == "--" { if positionalVal.IsValid() { for _, arg := range args[i+1:] { positionalVal.Set(reflect.Append(positionalVal, reflect.ValueOf(arg))) } } else { rest = append(rest, args[i+1:]...) } break } if strings.HasPrefix(arg, "--") { if val, ok = longToValue[arg[2:]]; !ok { if j := strings.IndexByte(arg, '='); j >= 0 { if val, ok = longToValue[arg[2:j]]; ok { if val.Kind() == reflect.Bool { return nil, fmt.Errorf("boolean flag `%s' cannot have an argument", arg[:j]) } args[i] = arg[j+1:] arg = arg[:j] i-- } } if !ok { return nil, fmt.Errorf("unknown flag `%s'", arg) } } } else if len(arg) > 1 && arg[0] == '-' { var skip bool for i := 1; i < len(arg); i++ { opt := arg[i : i+1] if val, ok = shortToValue[opt]; ok { if val.Kind() != reflect.Bool { break } } else if !("A" <= opt && opt <= "Z" || "a" <= opt && opt <= "z") { skip = true break } } if !skip && (len(arg) > 2 || !ok) { shortopts = arg[1:] goto L } } if !ok { if positionalVal.IsValid() && len(rest) > 0 { positionalVal.Set(reflect.Append(positionalVal, reflect.ValueOf(arg))) } else { rest = append(rest, arg) } continue } S: switch val.Kind() { case reflect.Bool: val.SetBool(true) case reflect.String: if i++; i >= len(args) { return nil, fmt.Errorf("expected argument for flag `%s'", arg) } val.SetString(args[i]) case reflect.Ptr: if val.Type().Elem().Kind() == reflect.Int { if i++; i >= len(args) { return nil, fmt.Errorf("expected argument for flag `%s'", arg) } v, err := strconv.Atoi(args[i]) if err != nil { return nil, fmt.Errorf("invalid argument for flag `%s': %w", arg, err) } val.Set(reflect.New(val.Type().Elem())) val.Elem().SetInt(int64(v)) } case reflect.Slice: if _, ok := longToPositional[arg[2:]]; ok { if positionalVal.IsValid() { for positionalVal.Len() > val.Len() { val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem()))) } } positionalVal = val } else { if i++; i >= len(args) { return nil, fmt.Errorf("expected argument for flag `%s'", arg) } val.Set(reflect.Append(val, reflect.ValueOf(args[i]))) } case reflect.Map: if i += 2; i >= len(args) { return nil, fmt.Errorf("expected 2 arguments for flag `%s'", arg) } if val.IsNil() { val.Set(reflect.MakeMap(val.Type())) } name := args[i-1] if _, ok := mapKeys[name]; !ok { mapKeys[name] = struct{}{} val.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(args[i])) } } L: if shortopts != "" { opt := shortopts[:1] if val, ok = shortToValue[opt]; !ok { return nil, fmt.Errorf("unknown flag `%s'", opt) } if val.Kind() != reflect.Bool && len(shortopts) > 1 { if shortopts[1] == '=' { args[i] = shortopts[2:] } else { args[i] = shortopts[1:] } i-- shortopts = "" } else { shortopts = shortopts[1:] } arg = "-" + opt goto S } } return rest, nil } func formatFlags(opts any) string { val := reflect.ValueOf(opts).Elem() typ := val.Type() var sb strings.Builder sb.WriteString("Command Options:\n") for i, l := 0, typ.NumField(); i < l; i++ { tag := typ.Field(i).Tag if i == l-1 { sb.WriteString("\nHelp Option:\n") } sb.WriteString(" ") var short bool if flag, ok := tag.Lookup("short"); ok { sb.WriteString("-") sb.WriteString(flag) short = true } else { sb.WriteString(" ") } m := sb.Len() if flag, ok := tag.Lookup("long"); ok { if short { sb.WriteString(", ") } else { sb.WriteString(" ") } sb.WriteString("--") sb.WriteString(flag) switch val.Field(i).Kind() { case reflect.Bool: sb.WriteString(" ") case reflect.Map: if strings.HasSuffix(flag, "file") { sb.WriteString(" name file") } else { sb.WriteString(" name value") } default: if _, ok = tag.Lookup("positional"); !ok { sb.WriteString("=") } } } else { sb.WriteString("=") } sb.WriteString(" "[:24-sb.Len()+m]) sb.WriteString(tag.Get("description")) sb.WriteString("\n") } return sb.String() } gojq-0.12.13/cli/inputs.go000066400000000000000000000204621443612442000152420ustar00rootroot00000000000000package cli import ( "bufio" "bytes" "encoding/json" "io" "os" "strings" "gopkg.in/yaml.v3" "github.com/itchyny/gojq" ) type inputReader struct { io.Reader file *os.File buf *bytes.Buffer } func newInputReader(r io.Reader) *inputReader { if r, ok := r.(*os.File); ok { if _, err := r.Seek(0, io.SeekCurrent); err == nil { return &inputReader{r, r, nil} } } var buf bytes.Buffer // do not use strings.Builder because we need to Reset return &inputReader{io.TeeReader(r, &buf), nil, &buf} } func (ir *inputReader) getContents(offset *int64, line *int) string { if buf := ir.buf; buf != nil { return buf.String() } if current, err := ir.file.Seek(0, io.SeekCurrent); err == nil { defer func() { ir.file.Seek(current, io.SeekStart) }() } ir.file.Seek(0, io.SeekStart) const bufSize = 16 * 1024 var buf bytes.Buffer // do not use strings.Builder because we need to Reset if offset != nil && *offset > bufSize { buf.Grow(bufSize) for *offset > bufSize { n, err := io.Copy(&buf, io.LimitReader(ir.file, bufSize)) *offset -= int64(n) *line += bytes.Count(buf.Bytes(), []byte{'\n'}) buf.Reset() if err != nil || n == 0 { break } } } var r io.Reader if offset == nil { r = ir.file } else { r = io.LimitReader(ir.file, bufSize*2) } io.Copy(&buf, r) return buf.String() } type inputIter interface { gojq.Iter io.Closer Name() string } type jsonInputIter struct { dec *json.Decoder ir *inputReader fname string offset int64 line int err error } func newJSONInputIter(r io.Reader, fname string) inputIter { ir := newInputReader(r) dec := json.NewDecoder(ir) dec.UseNumber() return &jsonInputIter{dec: dec, ir: ir, fname: fname} } func (i *jsonInputIter) Next() (any, bool) { if i.err != nil { return nil, false } var v any if err := i.dec.Decode(&v); err != nil { if err == io.EOF { i.err = err return nil, false } var offset *int64 var line *int if err, ok := err.(*json.SyntaxError); ok { err.Offset -= i.offset offset, line = &err.Offset, &i.line } i.err = &jsonParseError{i.fname, i.ir.getContents(offset, line), i.line, err} return i.err, true } if buf := i.ir.buf; buf != nil && buf.Len() >= 16*1024 { i.offset += int64(buf.Len()) i.line += bytes.Count(buf.Bytes(), []byte{'\n'}) buf.Reset() } return v, true } func (i *jsonInputIter) Close() error { i.err = io.EOF return nil } func (i *jsonInputIter) Name() string { return i.fname } type nullInputIter struct { err error } func newNullInputIter() inputIter { return &nullInputIter{} } func (i *nullInputIter) Next() (any, bool) { if i.err != nil { return nil, false } i.err = io.EOF return nil, true } func (i *nullInputIter) Close() error { i.err = io.EOF return nil } func (i *nullInputIter) Name() string { return "" } type filesInputIter struct { newIter func(io.Reader, string) inputIter fnames []string stdin io.Reader iter inputIter file io.Reader err error } func newFilesInputIter( newIter func(io.Reader, string) inputIter, fnames []string, stdin io.Reader, ) inputIter { return &filesInputIter{newIter: newIter, fnames: fnames, stdin: stdin} } func (i *filesInputIter) Next() (any, bool) { if i.err != nil { return nil, false } for { if i.file == nil { if len(i.fnames) == 0 { i.err = io.EOF if i.iter != nil { i.iter.Close() i.iter = nil } return nil, false } fname := i.fnames[0] i.fnames = i.fnames[1:] if fname == "-" && i.stdin != nil { i.file, fname = i.stdin, "" } else { file, err := os.Open(fname) if err != nil { return err, true } i.file = file } if i.iter != nil { i.iter.Close() } i.iter = i.newIter(i.file, fname) } if v, ok := i.iter.Next(); ok { return v, ok } if r, ok := i.file.(io.Closer); ok && i.file != i.stdin { r.Close() } i.file = nil } } func (i *filesInputIter) Close() error { if i.file != nil { if r, ok := i.file.(io.Closer); ok && i.file != i.stdin { r.Close() } i.file = nil i.err = io.EOF } return nil } func (i *filesInputIter) Name() string { if i.iter != nil { return i.iter.Name() } return "" } type rawInputIter struct { r *bufio.Reader fname string err error } func newRawInputIter(r io.Reader, fname string) inputIter { return &rawInputIter{r: bufio.NewReader(r), fname: fname} } func (i *rawInputIter) Next() (any, bool) { if i.err != nil { return nil, false } line, err := i.r.ReadString('\n') if err != nil { i.err = err if err != io.EOF { return err, true } if line == "" { return nil, false } } return strings.TrimSuffix(line, "\n"), true } func (i *rawInputIter) Close() error { i.err = io.EOF return nil } func (i *rawInputIter) Name() string { return i.fname } type streamInputIter struct { stream *jsonStream ir *inputReader fname string offset int64 line int err error } func newStreamInputIter(r io.Reader, fname string) inputIter { ir := newInputReader(r) dec := json.NewDecoder(ir) dec.UseNumber() return &streamInputIter{stream: newJSONStream(dec), ir: ir, fname: fname} } func (i *streamInputIter) Next() (any, bool) { if i.err != nil { return nil, false } v, err := i.stream.next() if err != nil { if err == io.EOF { i.err = err return nil, false } var offset *int64 var line *int if err, ok := err.(*json.SyntaxError); ok { err.Offset -= i.offset offset, line = &err.Offset, &i.line } i.err = &jsonParseError{i.fname, i.ir.getContents(offset, line), i.line, err} return i.err, true } if buf := i.ir.buf; buf != nil && buf.Len() >= 16*1024 { i.offset += int64(buf.Len()) i.line += bytes.Count(buf.Bytes(), []byte{'\n'}) buf.Reset() } return v, true } func (i *streamInputIter) Close() error { i.err = io.EOF return nil } func (i *streamInputIter) Name() string { return i.fname } type yamlInputIter struct { dec *yaml.Decoder ir *inputReader fname string err error } func newYAMLInputIter(r io.Reader, fname string) inputIter { ir := newInputReader(r) dec := yaml.NewDecoder(ir) return &yamlInputIter{dec: dec, ir: ir, fname: fname} } func (i *yamlInputIter) Next() (any, bool) { if i.err != nil { return nil, false } var v any if err := i.dec.Decode(&v); err != nil { if err == io.EOF { i.err = err return nil, false } i.err = &yamlParseError{i.fname, i.ir.getContents(nil, nil), err} return i.err, true } return normalizeYAML(v), true } func (i *yamlInputIter) Close() error { i.err = io.EOF return nil } func (i *yamlInputIter) Name() string { return i.fname } type slurpInputIter struct { iter inputIter err error } func newSlurpInputIter(iter inputIter) inputIter { return &slurpInputIter{iter: iter} } func (i *slurpInputIter) Next() (any, bool) { if i.err != nil { return nil, false } var vs []any var v any var ok bool for { v, ok = i.iter.Next() if !ok { i.err = io.EOF return vs, true } if i.err, ok = v.(error); ok { return i.err, true } vs = append(vs, v) } } func (i *slurpInputIter) Close() error { if i.iter != nil { i.iter.Close() i.iter = nil i.err = io.EOF } return nil } func (i *slurpInputIter) Name() string { return i.iter.Name() } type readAllIter struct { r io.Reader fname string err error } func newReadAllIter(r io.Reader, fname string) inputIter { return &readAllIter{r: r, fname: fname} } func (i *readAllIter) Next() (any, bool) { if i.err != nil { return nil, false } i.err = io.EOF cnt, err := io.ReadAll(i.r) if err != nil { return err, true } return string(cnt), true } func (i *readAllIter) Close() error { i.err = io.EOF return nil } func (i *readAllIter) Name() string { return i.fname } type slurpRawInputIter struct { iter inputIter err error } func newSlurpRawInputIter(iter inputIter) inputIter { return &slurpRawInputIter{iter: iter} } func (i *slurpRawInputIter) Next() (any, bool) { if i.err != nil { return nil, false } var vs []string var v any var ok bool for { v, ok = i.iter.Next() if !ok { i.err = io.EOF return strings.Join(vs, ""), true } if i.err, ok = v.(error); ok { return i.err, true } vs = append(vs, v.(string)) } } func (i *slurpRawInputIter) Close() error { if i.iter != nil { i.iter.Close() i.iter = nil i.err = io.EOF } return nil } func (i *slurpRawInputIter) Name() string { return i.iter.Name() } gojq-0.12.13/cli/marshaler.go000066400000000000000000000012661443612442000156770ustar00rootroot00000000000000package cli import ( "io" "gopkg.in/yaml.v3" ) type marshaler interface { marshal(any, io.Writer) error } type rawMarshaler struct { m marshaler } func (m *rawMarshaler) marshal(v any, w io.Writer) error { if s, ok := v.(string); ok { _, err := w.Write([]byte(s)) return err } return m.m.marshal(v, w) } func yamlFormatter(indent *int) *yamlMarshaler { return &yamlMarshaler{indent} } type yamlMarshaler struct { indent *int } func (m *yamlMarshaler) marshal(v any, w io.Writer) error { enc := yaml.NewEncoder(w) if i := m.indent; i != nil { enc.SetIndent(*i) } else { enc.SetIndent(2) } if err := enc.Encode(v); err != nil { return err } return enc.Close() } gojq-0.12.13/cli/run.go000066400000000000000000000002451443612442000145210ustar00rootroot00000000000000package cli import "os" // Run gojq. func Run() int { return (&cli{ inStream: os.Stdin, outStream: os.Stdout, errStream: os.Stderr, }).run(os.Args[1:]) } gojq-0.12.13/cli/stream.go000066400000000000000000000055721443612442000152200ustar00rootroot00000000000000package cli import ( "encoding/json" "io" ) type jsonStream struct { dec *json.Decoder path []any states []int } func newJSONStream(dec *json.Decoder) *jsonStream { return &jsonStream{dec: dec, states: []int{jsonStateTopValue}, path: []any{}} } const ( jsonStateTopValue = iota jsonStateArrayStart jsonStateArrayValue jsonStateArrayEnd jsonStateArrayEmptyEnd jsonStateObjectStart jsonStateObjectKey jsonStateObjectValue jsonStateObjectEnd jsonStateObjectEmptyEnd ) func (s *jsonStream) next() (any, error) { switch s.states[len(s.states)-1] { case jsonStateArrayEnd, jsonStateObjectEnd: s.path = s.path[:len(s.path)-1] fallthrough case jsonStateArrayEmptyEnd, jsonStateObjectEmptyEnd: s.states = s.states[:len(s.states)-1] } if s.dec.More() { switch s.states[len(s.states)-1] { case jsonStateArrayValue: s.path[len(s.path)-1] = s.path[len(s.path)-1].(int) + 1 case jsonStateObjectValue: s.path = s.path[:len(s.path)-1] } } for { token, err := s.dec.Token() if err != nil { if err == io.EOF && s.states[len(s.states)-1] != jsonStateTopValue { err = io.ErrUnexpectedEOF } return nil, err } if d, ok := token.(json.Delim); ok { switch d { case '[', '{': switch s.states[len(s.states)-1] { case jsonStateArrayStart: s.states[len(s.states)-1] = jsonStateArrayValue case jsonStateObjectKey: s.states[len(s.states)-1] = jsonStateObjectValue } if d == '[' { s.states = append(s.states, jsonStateArrayStart) s.path = append(s.path, 0) } else { s.states = append(s.states, jsonStateObjectStart) } case ']': if s.states[len(s.states)-1] == jsonStateArrayStart { s.states[len(s.states)-1] = jsonStateArrayEmptyEnd s.path = s.path[:len(s.path)-1] return []any{s.copyPath(), []any{}}, nil } s.states[len(s.states)-1] = jsonStateArrayEnd return []any{s.copyPath()}, nil case '}': if s.states[len(s.states)-1] == jsonStateObjectStart { s.states[len(s.states)-1] = jsonStateObjectEmptyEnd return []any{s.copyPath(), map[string]any{}}, nil } s.states[len(s.states)-1] = jsonStateObjectEnd return []any{s.copyPath()}, nil default: panic(d) } } else { switch s.states[len(s.states)-1] { case jsonStateArrayStart: s.states[len(s.states)-1] = jsonStateArrayValue fallthrough case jsonStateArrayValue: return []any{s.copyPath(), token}, nil case jsonStateObjectStart, jsonStateObjectValue: s.states[len(s.states)-1] = jsonStateObjectKey s.path = append(s.path, token) case jsonStateObjectKey: s.states[len(s.states)-1] = jsonStateObjectValue return []any{s.copyPath(), token}, nil default: s.states[len(s.states)-1] = jsonStateTopValue return []any{s.copyPath(), token}, nil } } } } func (s *jsonStream) copyPath() []any { path := make([]any, len(s.path)) copy(path, s.path) return path } gojq-0.12.13/cli/test.yaml000066400000000000000000005353511443612442000152440ustar00rootroot00000000000000- name: empty input input: '' expected: '' - name: empty query args: - '' input: '128' expected: | 128 - name: comment only query args: - '# comment' input: '128' expected: | 128 - name: number input args: - '.' input: '128' expected: | 128 - name: number query args: - '0, 128, 01000, 3.14, 1.2e3, 1E+3, 1E-9' input: '0' expected: | 0 128 1000 3.14 1200 1000 1e-9 - name: number literal error args: - '0..[empty]' input: '0' error: | invalid query: 0..[empty] 0..[empty] ^ invalid token "0.." exit_code: 3 - name: string input args: - '.' input: '"Hello, world! \r\n\t\f\b\"\\\/\uff06"' expected: | "Hello, world! \r\n\t\f\b\"\\/&" - name: string query args: - '" Hello, world! \r\n\t\f\b\"\\\/\uff10 "' input: 'null' expected: | " Hello, world! \r\n\t\f\b\"\\/0 " - name: string query including newlines args: - "\"a\n\\(\"\nb\nc\n\")\nd\n\"" input: 'null' expected: | "a\n\nb\nc\n\nd\n" - name: string query including control characters args: - "\"\x01\x07\x0f\\n\", \"\x7f\\n\"" input: 'null' expected: | "\u0001\u0007\u000f\n" "\u007f\n" - name: string query including surrogate pairs args: - '"\ud83d\ude04" | length, utf8bytelength, explode[]' input: 'null' expected: | 1 4 128516 - name: string query error args: - '. + "foo' input: 'null' error: | invalid query: . + "foo . + "foo ^ unterminated string literal exit_code: 3 - name: string query error args: - '"foo\,bar"' input: 'null' error: | invalid query: "foo\,bar" "foo\,bar" ^ invalid escape sequence "\," in string literal exit_code: 3 - name: string query error args: - '"\u"' input: 'null' error: | invalid query: "\u" "\u" ^ invalid escape sequence "\u" in string literal exit_code: 3 - name: string query error args: - "\"\n\\u\n\"" input: 'null' error: | invalid query: :2 2 | \u ^ invalid escape sequence "\u" in string literal exit_code: 3 - name: string query error args: - '"\u' input: 'null' error: | invalid query: "\u "\u ^ invalid escape sequence "\u" in string literal exit_code: 3 - name: string query error args: - '"\' input: 'null' error: | invalid query: "\ "\ ^ unterminated string literal exit_code: 3 - name: string query error args: - '"\"' input: 'null' error: | invalid query: "\" "\" ^ unterminated string literal exit_code: 3 - name: string query error args: - '"\u````"' input: 'null' error: | invalid query: "\u````" "\u````" ^ invalid escape sequence "\u" in string literal exit_code: 3 - name: string query error args: - '"\uAA\uAA"' input: 'null' error: | invalid query: "\uAA\uAA" "\uAA\uAA" ^ invalid escape sequence "\uAA" in string literal exit_code: 3 - name: string query error args: - '"\uAA@AA"' input: 'null' error: | invalid query: "\uAA@AA" "\uAA@AA" ^ invalid escape sequence "\uAA" in string literal exit_code: 3 - name: string query error args: - '"\uAAGA"' input: 'null' error: | invalid query: "\uAAGA" "\uAAGA" ^ invalid escape sequence "\uAA" in string literal exit_code: 3 - name: string query error args: - '"\ufffg"' input: 'null' error: | invalid query: "\ufffg" "\ufffg" ^ invalid escape sequence "\ufff" in string literal exit_code: 3 - name: string query error args: - '"0\u00:00"' input: 'null' error: | invalid query: "0\u00:00" "0\u00:00" ^ invalid escape sequence "\u00" in string literal exit_code: 3 - name: string query error args: - '"0\u0000\u000/0"' input: 'null' error: | invalid query: "0\u0000\u000/0" "0\u0000\u000/0" ^ invalid escape sequence "\u000" in string literal exit_code: 3 - name: string query error args: - '"\(1) \x "' input: 'null' error: | invalid query: "\(1) \x " "\(1) \x " ^ invalid escape sequence "\x" in string literal exit_code: 3 - name: string query error args: - '"\a"' input: 'null' error: | invalid query: "\a" "\a" ^ invalid escape sequence "\a" in string literal exit_code: 3 - name: string query error args: - '"\u\(' input: 'null' error: | invalid query: "\u\( "\u\( ^ invalid escape sequence "\u" in string literal exit_code: 3 - name: object input input: '{"foo": 128}' expected: | { "foo": 128 } - name: object input and string escape input: '{"foo\\u0000\\t\\n\\r\\u001f !\"#$/09:;<>?@AZ[\\\\]^_`az{|}~\\u007f": "<\\t\\n\\r>"}' expected: | { "foo\\u0000\\t\\n\\r\\u001f !\"#$/09:;<>?@AZ[\\\\]^_`az{|}~\\u007f": "<\\t\\n\\r>" } - name: object indexing args: - '.foo' input: '{"foo": 128}' expected: | 128 - name: object indexing with large number value args: - '.foo' input: '{"foo": 4722366482869645213696}' expected: | 4722366482869645213696 - name: object indexing by keywords args: - '.and,.or,.try' input: '{"and":1,"or":2,"try":3}' expected: | 1 2 3 - name: object indexing by brackets args: - '.["foo"]' input: '{"foo": 128}' expected: | 128 - name: object indexing by string args: - '."foo", ."2\(.foo[])3"' input: '{"foo":[1,2],"213":3,"223":6}' expected: | [ 1, 2 ] 3 6 - name: object indexing by query args: - '.c[.a + .b]' input: '{ "a": "b", "b": "c", "c": {"bc": 128} }' expected: | 128 - name: object nested indexing by query args: - '.d[.a][.b][.c]' input: '{ "a": "b", "b": "c", "c": "d", "d": { "b": { "c": { "d": 128 } } } }' expected: | 128 - name: object indexing by iterator after iterator args: - '.c[][.a[] + .b[]]' input: '{ "a": ["a", "b"], "b": ["c", "d"], "c": [{"ac": 1, "bc": 2, "ad": 3, "bd": 4}, {"ac": 5, "bc": 6, "ad": 7, "bd": 8}] }' expected: | 1 5 2 6 3 7 4 8 - name: object indexing by string iterator after iterator args: - '.c[]."\(.a[])"' input: '{ "a": [0,1], "b": [0,2], "c": [{"0":1,"1":2},{"0":3,"1":4}] }' expected: | 1 3 2 4 - name: object indexing in object value args: - '{ message: .[].message }' input: '[ {"message": "Hello, world" } ]' expected: | { "message": "Hello, world" } - name: object indexing error args: - '.[.a + .b]' input: '{"a": 10, "b": 20}' error: | expected an array but got: object ({"a":10,"b":20}) - name: object indexing value error args: - '.["foo"]' input: '[]' error: | expected an object but got: array ([]) - name: object indexing key error args: - '.[null]' input: '{}' error: | expected a string for object key but got: null - name: object indexing syntax error args: - '. a' input: '{}' error: | invalid query: . a . a ^ unexpected token "a" exit_code: 3 - name: expected object but got number error args: - '.foo.bar' input: '{"foo": 128}' error: | expected an object but got: number (128) - name: expected object but got string error args: - '"a"+"☆"*100 | .foo' input: '0' error: | expected an object but got: string ("a☆☆☆☆☆☆☆ ...") - name: expected object error with pipe operator args: - '.foo|.bar' input: '{"foo": 128}' error: | expected an object but got: number (128) - name: expected object error after iterator args: - '.[]|.foo|.bar' input: '[128]' error: | expected an object but got: number (128) - name: object indexing against null args: - '.foo.bar.baz' input: 'null' expected: | null - name: object optional indexing args: - '.foo?.bar?.baz?' input: '{"foo": 128}' - name: object optional indexing error args: - '.foo.bar.baz?' input: '{"foo": 128}' error: | expected an object but got: number (128) - name: object optional indexing after iterator error args: - '.[].foo?' input: '128' error: | cannot iterate over: number (128) - name: object optional indexing with optional operator args: - '.foo??, .' input: '0' expected: | 0 - name: error after optional operator args: - '1? | .foo' input: 'null' error: | expected an object but got: number (1) - name: error after optional operator with fork args: - '(1,2)? | .foo' input: 'null' error: | expected an object but got: number (1) - name: array indexing args: - '.[-4,-3,-2,-1,0,1,2,3,nan]' input: '[0, 1, 2]' expected: | null 0 1 2 0 1 2 null null - name: array slicing args: - -c - '[.[-4,-3,-2,-1,0,1,2,3:]], [.[:-4,-3,-2,-1,0,1,2,3]], [.[-1,0,1,2,3:-1,0,1,2,3]]' input: '[0, 1, 2]' expected: | [[0,1,2],[0,1,2],[1,2],[2],[0,1,2],[1,2],[2],[]] [[],[],[0],[0,1],[],[0],[0,1],[0,1,2]] [[],[],[],[],[2],[0,1],[],[0],[0,1],[0,1,2],[1],[],[],[1],[1,2],[],[],[],[],[2],[],[],[],[],[]] - name: array indexing and slicing by large number args: - -c - '.[1e300,-1e300], .[-1e300,1e300:], .[:-1e300,1e300], .[-1e300:1e300], .[1e300:-1e300]' input: '[0, 1, 2]' expected: | null null [0,1,2] [] [] [0,1,2] [0,1,2] [] - name: array indexing and slicing by large integer args: - -c - '. as $x | 4722366482869645213696 as $y | $x[$y,-$y], $x[-$y,$y:], $x[:-$y,$y], $x[-$y:$y], $x[$y-$y], $x[$y/$y]' input: '[0, 1, 2]' expected: | null null [0,1,2] [] [] [0,1,2] [0,1,2] 0 1 - name: array indexing and slicing by infinite args: - -c - '.[infinite,-infinite], .[-infinite,infinite:], .[:-infinite,infinite], .[-infinite:infinite], .[infinite:-infinite]' input: '[0, 1, 2]' expected: | null null [0,1,2] [] [] [0,1,2] [0,1,2] [] - name: array and string slicing by object args: - -c - '[.[{"start": (null,range(-3;4)), "end": (null,range(-3;4))}]]' input: '[0, 1, 2] "abcde"' expected: | [[0,1,2],[],[0],[0,1],[],[0],[0,1],[0,1,2],[0,1,2],[],[0],[0,1],[],[0],[0,1],[0,1,2],[1,2],[],[],[1],[],[],[1],[1,2],[2],[],[],[],[],[],[],[2],[0,1,2],[],[0],[0,1],[],[0],[0,1],[0,1,2],[1,2],[],[],[1],[],[],[1],[1,2],[2],[],[],[],[],[],[],[2],[],[],[],[],[],[],[],[]] ["abcde","ab","abc","abcd","","a","ab","abc","cde","","c","cd","","","","c","de","","","d","","","","","e","","","","","","","","abcde","ab","abc","abcd","","a","ab","abc","bcde","b","bc","bcd","","","b","bc","cde","","c","cd","","","","c","de","","","d","","","",""] - name: array slicing by object against null args: - '.[{"start": 1, "end": 2}]' input: 'null' expected: | null - name: array slicing by object error args: - '.[{"start": 1}]' input: '[]' error: | expected "start" and "end" for slicing but got: object ({"start":1}) - name: array nested indexing by iterator args: - -c - '[.[.[]],.[.[]:],.[:.[]],.[.[]:.[]],.[-.[]],.[--.[]]]' input: '[0, 1, 2]' expected: | [0,1,2,[0,1,2],[1,2],[2],[],[0],[0,1],[],[0],[0,1],[],[],[1],[],[],[],0,2,1,0,1,2] - name: array indexing by iterator args: - '.c[][.a[] + .b[]]' input: '{ "a": [0,1], "b": [0,2], "c": [[1,2,3,4],[5,6,7,8]] }' expected: | 1 5 2 6 3 7 4 8 - name: array slicing by iterator args: - -c - '.c[][.a[] : .b[]]' input: '{ "a": [0,1], "b": [0,2], "c": [[1,2,3,4],[5,6,7,8]] }' expected: | [] [] [1,2] [5,6] [] [] [2] [6] - name: expected array but got object error args: - '.[0:1]' input: '{"foo": 128}' error: | expected an array but got: object ({"foo":128}) - name: array indexing against null args: - '.[0]' input: 'null' expected: | null - name: array slicing against null args: - '.[0:1]' input: 'null' expected: | null - name: array indexing by array args: - -c - '.[[1],[5,7,9],[],5,[3]]' input: '[1,3,5,7,9,8,6,5,4,3,2,1]' expected: | [0,11] [2] [] 8 [1,9] - name: array indexing not number error args: - '.[true]' input: '[]' error: | expected a number for indexing an array but got: boolean (true) - name: array slicing start error args: - '.[[]:0]' input: '[]' error: | expected a number for indexing an array but got: array ([]) - name: array slicing end error args: - '.[0:{}]' input: '[]' error: | expected a number for indexing an array but got: object ({}) - name: string indexing and slicing args: - -c - '[.[-5],.[-1],.[0],.[3],.[4],.[6],.[2:4],.[-4:-2]]' input: '"12345"' expected: | ["1","5","1","4","5",null,"34","23"] - name: string indexing not number error args: - '.[null]' input: '""' error: | expected a number for indexing a string but got: null - name: string slicing start error args: - '.[[]:]' input: '""' error: | expected a number for indexing a string but got: array ([]) - name: string slicing end error args: - '.[:{}]' input: '""' error: | expected a number for indexing a string but got: object ({}) - name: array construction args: - -c - '.,[3,[[2]]],[1],[[.],3]|[.,.]' input: 'null' expected: | [null,null] [[3,[[2]]],[3,[[2]]]] [[1],[1]] [[[null],3],[[null],3]] - name: array construction with error function args: - -c - '[error], [0, error, 1]' input: 'null' expected: | [] [0,1] - name: array construction with halt_error function args: - -c - '[halt_error], [0, halt_error, 1]' input: 'null' exit_code: 5 - name: array iterator args: - '.foo | .[] | .' input: '{"foo": [1,2,{"bar":[]},[3,4,5]]}' expected: | 1 2 { "bar": [] } [ 3, 4, 5 ] - name: iterator with optional operator args: - '.[]?' input: '0' - name: iterator with optional operator after indexing error args: - '.x[]?' input: '0' error: | expected an object but got: number (0) - name: nested iterator args: - '.[][]' input: '[[10]]' expected: | 10 - name: nested iterator error args: - '.[][]' input: '[10]' error: | cannot iterate over: number (10) - name: nested iterator with optional operator args: - '.[][]?' input: '[10]' - name: object iterator args: - '.[]' input: '{"foo": 128}' expected: | 128 - name: object optional indexing after iterator args: - '[.[].name?]' input: '[ 1, { "name": 2 }, 3, { "name": 4 } ]' expected: | [ 2, 4 ] - name: pipe args: - '.foo | .bar' input: '{"foo": {"bar": {"baz": 128}}}' expected: | { "baz": 128 } - name: null value args: - '.[] | null' input: '["a", 1, [], {}]' expected: | null null null null - name: boolean values args: - '.[] | true, false' input: '["a", 1]' expected: | true false true false - name: empty function args: - '.[] | empty, empty, . + [empty,.][], [empty,.][] / 1, empty < ., . and empty, empty or .' input: '[0, 1]' expected: | 0 0 2 1 - name: empty function in array construction args: - '[empty], [0, empty, 1]' input: 'null' expected: | [] [ 0, 1 ] - name: empty function in function declaration args: - 'def f: 1,empty,empty,2; f,f' input: '[0, 1]' expected: | 1 2 1 2 - name: unary operators args: - -c - 'map(-.[]), map(+.[])' input: '[[1,2,3,4722366482869645213696]]' expected: | [-1,-2,-3,-4722366482869645213696] [1,2,3,4722366482869645213696] - name: unary operator against string args: - '(-.)' input: '"abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde"' error: | cannot negate: string ("abcdeabcdeabcdeabcdeabcd ...") - name: unary operator with variable binding args: - '-1 as $x | 1, $x' input: 'null' expected: | 1 -1 - name: object construction args: - -c - '{ foo: .foo.bar, "bar": .foo, "": false }' input: '{"foo":{"bar":["a","b"]}}' expected: | {"":false,"bar":{"bar":["a","b"]},"foo":["a","b"]} - name: object construction shortcuts args: - '{foo,"bar",qux,quxx:.foo}' input: '{"foo": 1, "bar": 2, "baz": 3}' expected: | { "bar": 2, "foo": 1, "qux": null, "quxx": 1 } - name: object construction with trailing comma args: - '{foo,bar,}' input: '{"foo": 1, "bar": 2, "baz": 3}' expected: | { "bar": 2, "foo": 1 } - name: object construction by keywords args: - '{as, and, null, module}' input: '{"and": 1, "as": 2, "module": 3, "null": 4}' expected: | { "and": 1, "as": 2, "module": 3, "null": 4 } - name: object construction with pipe in value args: - '{ "bar": { "baz": . } | . | select(true) }' input: '{ "foo": 100 }' expected: | { "bar": { "baz": { "foo": 100 } } } - name: empty object args: - '{}' input: '{"foo":{"bar":["a","b"]}}' expected: | {} - name: unary operators in object values args: - '{ x: -128, y: +128 }' input: '0' expected: | { "x": -128, "y": 128 } - name: iterator in object values args: - -c - '{ foo: .foo[], bar: .bar[] }' input: '{"foo":[1,2],"bar":["a","b"]}' expected: | {"bar":"a","foo":1} {"bar":"b","foo":1} {"bar":"a","foo":2} {"bar":"b","foo":2} - name: iterator in object key args: - -c - '{ (.[]): .. }' input: '["a", "b"]' expected: | {"a":["a","b"]} {"a":"a"} {"a":"b"} {"b":["a","b"]} {"b":"a"} {"b":"b"} - name: binary operators in object key and value args: - -c - '{ (.[] + .[]): (.[] + .[]) }' input: '["foo", "bar"]' expected: | {"foofoo":"foofoo"} {"foofoo":"barfoo"} {"foofoo":"foobar"} {"foofoo":"barbar"} {"barfoo":"foofoo"} {"barfoo":"barfoo"} {"barfoo":"foobar"} {"barfoo":"barbar"} {"foobar":"foofoo"} {"foobar":"barfoo"} {"foobar":"foobar"} {"foobar":"barbar"} {"barbar":"foofoo"} {"barbar":"barfoo"} {"barbar":"foobar"} {"barbar":"barbar"} - name: error in object key args: - -c - '{ (.[]): .. }' input: '["a", 1]' expected: | {"a":["a",1]} {"a":"a"} {"a":1} error: | expected a string for object key but got: number (1) - name: error in object value args: - -c - '.[] | { (.k): .a.b }' input: '[{"a":{"b":10},"k":"a"}, {"a":10,"k":"b"}]' expected: | {"a":10} error: | expected an object but got: number (10) - name: variable in object keys args: - -c - '. as [$x, $y, $z] | {$x: 1, $y: 2, $z: 3}' input: | ["a","b","c"] expected: | {"a":1,"b":2,"c":3} - name: pipe in object key args: - '{ (.foo|.bar): .foo.bar }' input: '{"foo":{"bar":"baz"}}' expected: | { "baz": "baz" } - name: number in object key args: - '{ (.foo): .foo }' input: '{"foo":10}' error: | expected a string for object key but got: number (10) - name: null in object key args: - '{ (.foo): .foo }' input: '{}' error: | expected a string for object key but got: null - name: keyword in object keys args: - -c - '{if:0,and:1,or:2,then:3,else:4,elif:5,end:6,as:7,def:8,reduce:9,foreach:10,try:11,catch:12,label:13,import:14,include:15,module:16}' input: 'null' expected: | {"and":1,"as":7,"catch":12,"def":8,"elif":5,"else":4,"end":6,"foreach":10,"if":0,"import":14,"include":15,"label":13,"module":16,"or":2,"reduce":9,"then":3,"try":11} - name: array args: - '[.foo, ., false]' input: '{"foo": {"bar": 128}}' expected: | [ { "bar": 128 }, { "foo": { "bar": 128 } }, false ] - name: empty array args: - '[]' input: '{"foo": {"bar": 128}}' expected: | [] - name: pipe in array args: - '[.foo|.bar]' input: '{"foo": {"bar": 128}}' expected: | [ 128 ] - name: iterator in array args: - '[.foo|.bar[][]]' input: '{"foo": {"bar": [[1],[2],[3]]}}' expected: | [ 1, 2, 3 ] - name: error after iterator args: - '[ .[] | .foo ]' input: '[ [1, 2, 3], 4, 5]' error: | expected an object but got: array ([1,2,3]) - name: function after iterator error args: - '.[] | .foo | null' input: '[1, 2, 3]' error: | expected an object but got: number (1) - name: multiple iterators in array args: - -c - '[.[],..,.[]]' input: '[1, 2, 3]' expected: | [1,2,3,[1,2,3],1,2,3,1,2,3] - name: recurse args: - -c - '..' input: '{"foo":{"bar":["a","b"]}}' expected: | {"foo":{"bar":["a","b"]}} {"bar":["a","b"]} ["a","b"] "a" "b" - name: recurse after iterator args: - '.[] | ..' input: '{"foo":[{"bar":128}]}' expected: | [ { "bar": 128 } ] { "bar": 128 } 128 - name: recurse and pipe args: - '.. | .foo?' input: '{"foo":{"bar":["a","b"]}}' expected: | { "bar": [ "a", "b" ] } null - name: recurse in object value args: - -c - '{ foo: .. }' input: '{"foo":[1,2,3]}' expected: | {"foo":{"foo":[1,2,3]}} {"foo":[1,2,3]} {"foo":1} {"foo":2} {"foo":3} - name: length function args: - 'length' input: | 42 -42 {"a":1,"b":2} [3,4,5] "Hello, world" "12345" null -4722366482869645213696 expected: | 42 42 2 3 12 5 0 4722366482869645213696 - name: length function error args: - 'length' input: 'false' error: | length cannot be applied to: boolean (false) - name: keys function args: - 'keys' input: | {"a":1,"b":2} [3,4,5] expected: | [ "a", "b" ] [ 0, 1, 2 ] - name: utf8bytelength function args: - 'utf8bytelength' input: '"" "☆12345☆"' expected: | 0 21 - name: has function on objects args: - 'has(.c[])' input: | {"a":{"b":1},"c":["a","b"]} {"c":["b","c"]} expected: | true false false true - name: has function on arrays args: - 'has(2, nan, infinite, -infinite)' input: | [1, 2, 3] [1, 3] expected: | true false false false false false false false - name: has function against null args: - 'has(2, nan, infinite, -infinite)' input: 'null' expected: | false false false false - name: has/0 is not defined args: - 'has' input: '[0]' error: | function not defined: has/0 exit_code: 3 - name: has/2 is not defined args: - 'has(1; 2)' input: '[0]' error: | function not defined: has/2 exit_code: 3 - name: has function type error args: - 'has(0)' input: '{}' error: | has(0) cannot be applied to: object ({}) - name: in function for object args: - 'in({"foo": 42})' input: '"foo" "bar"' expected: | true false - name: in function for array args: - 'in([3, 4])' input: '-1 0 1 2' expected: | false true true false - name: in function for null args: - 'in(null)' input: '0' expected: | false - name: in function type error args: - 'in([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])' input: '"x"' error: | has("x") cannot be applied to: array ([0,0,0,0,0,0,0,0,0,0,0,0, ...]) - name: map function args: - -c - 'map(..)' input: '[{"a":{"b":1}}, [2,3,4]]' expected: | [{"a":{"b":1}},{"b":1},1,[2,3,4],2,3,4] - name: to_entries function args: - -c - 'to_entries' input: | [10, 20] {"a": 10, "b": []} expected: | [{"key":0,"value":10},{"key":1,"value":20}] [{"key":"a","value":10},{"key":"b","value":[]}] - name: from_entries function args: - -c - 'from_entries' input: '[{"key":"a","Key":"b","value":1},{"Key":"b","Value":2},{"key":null,"name":"c","Name":"d","value":3},{"name":false,"Name":"d","value":null,"Value":4},{"key":"e"}]' expected: | {"a":1,"b":2,"c":3,"d":null,"e":null} - name: from_entries function missing key args: - 'from_entries' input: '[{"value":1}]' error: | from_entries cannot be applied to [{"value":1}]: expected a string for object key but got: null - name: from_entries function key type error args: - 'from_entries' input: '[{"key":{},"value":1}]' error: | from_entries cannot be applied to [{"key":{},"value":1}]: expected a string for object key but got: object ({}) - name: with_entries function args: - -c - 'with_entries(.key |= "x" + . | .value += 1)' input: '{"a": 1, "b": 2}' expected: | {"xa":2,"xb":3} - name: add function args: - -c - 'add' input: | [] [1,2,3] ["a","b","c"] [[3],[4,5],[6]] [{"a":1}, {"b":2}, {"a":3}] {} {"a":1,"b":9} {"a":"a","b":"b","c":"c"} {"a":[1],"b":[2],"c":[3],"d":[4]} expected: | null 6 "abc" [3,4,5,6] {"a":3,"b":2} null 10 "abc" [1,2,3,4] - name: add function with a variable args: - '. as $x | [$x, {y:1}] | add, $x' input: '{"x":0}' expected: | { "x": 0, "y": 1 } { "x": 0 } - name: add function error args: - 'add' input: '[{}, [], {}]' error: | cannot add: object ({}) and array ([]) - name: add function error args: - 'add' input: '["x", null, "y", {}, "z"]' error: | cannot add: string ("xy") and object ({}) - name: flatten/0 function args: - -c - 'flatten' input: | [] [0, [[[[[1]]]]]] [0, [1, [2]], [1, [[3], 2]]] {"x": [1], "y": [[2]], "z": [[[3]]]} expected: | [] [0,1] [0,1,2,1,3,2] [1,2,3] - name: flatten/0 function error args: - 'flatten, flatten("")' input: '0 [[]]' expected: | [] error: | flatten cannot be applied to: number (0) flatten cannot be applied to: string ("") - name: flatten/1 function args: - -c - 'flatten(0, 1.5, 2)' input: | [] [0, [[[[[1]]]]]] [0, [1, [2]], [1, [[3], 2]]] {"x": [1], "y": [[2]], "z": [[[3]]]} expected: | [] [] [] [0,[[[[[1]]]]]] [0,1] [0,[[[1]]]] [0,[1,[2]],[1,[[3],2]]] [0,1,2,1,3,2] [0,1,2,1,[3],2] [[1],[[2]],[[[3]]]] [1,2,3] [1,2,[3]] - name: flatten/1 function depth error args: - 'flatten(-1)' input: '[]' error: | flatten depth should not be negative: -1 - name: min, min_by, max, max_by functions args: - -c - '[min, min_by(.[1]), min_by(.[2]), max, max_by(.[1]), max_by(.[2])]' input: | [] [[4,2,"a"],[3,1,"a"],[2,4,"a"],[1,3,"a"]] expected: | [null,null,null,null,null,null] [[1,3,"a"],[3,1,"a"],[4,2,"a"],[4,2,"a"],[2,4,"a"],[1,3,"a"]] - name: sort, sort_by, group_by functions args: - -c - 'sort, sort_by(.a), sort_by(.b), sort_by(.b, .c), group_by(.b), group_by(.c, .a), group_by(.a + .b - .c == 2)' input: '[{"a": 1, "b": 4, "c": 5}, {"a": 4, "b": 1, "c": 2}, {"a": 1, "b": 4, "c": 3}, {"a": 3, "b": 2, "c": 1}]' expected: | [{"a":1,"b":4,"c":3},{"a":1,"b":4,"c":5},{"a":3,"b":2,"c":1},{"a":4,"b":1,"c":2}] [{"a":1,"b":4,"c":5},{"a":1,"b":4,"c":3},{"a":3,"b":2,"c":1},{"a":4,"b":1,"c":2}] [{"a":4,"b":1,"c":2},{"a":3,"b":2,"c":1},{"a":1,"b":4,"c":5},{"a":1,"b":4,"c":3}] [{"a":4,"b":1,"c":2},{"a":3,"b":2,"c":1},{"a":1,"b":4,"c":3},{"a":1,"b":4,"c":5}] [[{"a":4,"b":1,"c":2}],[{"a":3,"b":2,"c":1}],[{"a":1,"b":4,"c":5},{"a":1,"b":4,"c":3}]] [[{"a":3,"b":2,"c":1}],[{"a":4,"b":1,"c":2}],[{"a":1,"b":4,"c":3}],[{"a":1,"b":4,"c":5}]] [[{"a":1,"b":4,"c":5},{"a":4,"b":1,"c":2},{"a":3,"b":2,"c":1}],[{"a":1,"b":4,"c":3}]] - name: unique, unique_by functions args: - -c - 'unique, unique_by(length)' input: | [7,3,5,1,4,2,5,3,6,1,7,4,3] ["cat", "dog", "hello", "world", "banana", "apple"] expected: | [1,2,3,4,5,6,7] [1,2,3,4,5,6,7] ["apple","banana","cat","dog","hello","world"] ["cat","hello","banana"] - name: min, max, sort, unique functions error args: - 'try min catch ., try max catch ., try sort catch ., try unique catch .' input: '0' expected: | "min cannot be applied to: number (0)" "max cannot be applied to: number (0)" "sort cannot be applied to: number (0)" "unique cannot be applied to: number (0)" - name: arrays, objects, iterables, booleans, numbers, strings, nulls, values, scalars functions args: - -c - 'map(arrays), map(objects), map(iterables), map(booleans), map(numbers), map(strings), map(nulls), map(values), map(scalars)' input: '[null, true, false, 0, 1, "", "foo", [], [2, []], {}, { "foo": [] }, 4722366482869645213696]' expected: | [[],[2,[]]] [{},{"foo":[]}] [[],[2,[]],{},{"foo":[]}] [true,false] [0,1,4722366482869645213696] ["","foo"] [null] [true,false,0,1,"","foo",[],[2,[]],{},{"foo":[]},4722366482869645213696] [null,true,false,0,1,"","foo",4722366482869645213696] - name: leaf_paths function args: - -c - 'leaf_paths' input: '[null,[],{},{"a":[1,[],{},true]},[3,[],1]]' expected: | [3,"a",0] [3,"a",3] [4,0] [4,2] - name: recurse/0 function args: - -c - 'recurse' input: '{"a":{"b":[1]}}' expected: | {"a":{"b":[1]}} {"b":[1]} [1] 1 - name: recurse/1 function args: - -c - 'recurse(.a[])' input: '{"a":[{"a": []}, {"a":[{"a":[]}]}]}' expected: | {"a":[{"a":[]},{"a":[{"a":[]}]}]} {"a":[]} {"a":[{"a":[]}]} {"a":[]} - name: recurse/2 function args: - -c - '[recurse(. + .; . < 1000)]' input: '1' expected: | [1,2,4,8,16,32,64,128,256,512] - name: while function args: - -c - '[while(.[0]<100; [.[1], .[0]+.[1]]) | .[0]]' input: '[0, 1]' expected: | [0,1,1,2,3,5,8,13,21,34,55,89] - name: until function args: - '[.,1] | until(.[0]==0; [.[0]-1, .[0]*.[1]]) | .[1]' input: '20' expected: | 2432902008176640000 - name: repeat function args: - -c - '[limit(.; repeat(1,2,3))]' input: '10' expected: | [1,2,3,1,2,3,1,2,3,1] - name: range function args: - -c - '[range(.)], [range(range(.))], [range(3.5)], [range(3; 5)], [range(1.5; 4)], [range(-5; -1.5)], [range(3.5; 5.5)], [range(-5.5; -3.5)], [range(0; 10; 3)], [range(0; 10; -1)], [range(5; -5; -3)], [range(0.25; 9.5; 2.25)], [range(-5; -9.5; -1)]' input: '5' expected: | [0,1,2,3,4] [0,0,1,0,1,2,0,1,2,3] [0,1,2,3] [3,4] [1.5,2.5,3.5] [-5,-4,-3,-2] [3.5,4.5] [-5.5,-4.5] [0,3,6,9] [] [5,2,-1,-4] [0.25,2.5,4.75,7,9.25] [-5,-6,-7,-8,-9] - name: range function overflow args: - 'range(9223372036854775700; 9223372036854775805; 10)' input: 'null' expected: | 9223372036854775700 9223372036854775710 9223372036854775720 9223372036854775730 9223372036854775740 9223372036854775750 9223372036854775760 9223372036854775770 9223372036854775780 9223372036854775790 9223372036854775800 - name: range function with large numbers args: - 'range(0; 20000000000000000000; 2000000000000000000)' input: 'null' expected: | 0 2000000000000000000 4000000000000000000 6000000000000000000 8000000000000000000 10000000000000000000 12000000000000000000 14000000000000000000 16000000000000000000 18000000000000000000 - name: range function with iterators in arguments args: - -c - '[range(-.; .+1)] | [range(.[]; .[]; .[])] | recurse(.[26:];.[0])[:26] | map(.+3)' input: '3' expected: | [0,0,0,0,1,0,0,0,1,2,0,2,0,0,1,2,3,0,2,0,3,0,1,2,3,4] [0,2,4,0,3,0,1,2,3,4,5,0,2,4,0,3,1,1,1,1,1,1,1,2,1,1] [1,2,3,1,3,1,1,2,3,4,1,3,1,4,1,2,3,4,5,1,3,5,1,4,2,2] [2,1,2,2,2,2,2,2,2,3,2,2,2,3,4,2,4,2,2,3,4,5,2,4,2,5] [3,3,1,3,2,1,3,3,3,2,3,3,3,3,3,3,3,4,3,3,3,4,5,3,5,3] [4,1,4,2,4,3,2,1,4,4,2,4,3,2,4,4,4,3,4,4,4,4,4,4,4,5] [4,4,5,2,5,3,1,5,4,3,2,1,5,2,5,3,5,4,3,2,5,5,3,5,4,3] [5,5,5,4,5,5,5,5,5,5,6,3,6,4,2,6,5,4,3,2,1,6,3,6,4,2] [6,5,4,3,2,6,3,6,4,6,5,4,3,6,6,4,6,5,4,6,6,6,5,6,6,6] - name: range function error args: - '(null, "", [], {} | try range(.) catch .), try range(""; 0) catch ., try range(0; 1; []) catch .' input: 'null' expected: | "range cannot be applied to: null" "range cannot be applied to: string (\"\")" "range cannot be applied to: array ([])" "range cannot be applied to: object ({})" "range cannot be applied to: string (\"\")" "range cannot be applied to: array ([])" - name: tonumber function args: - 'tonumber' input: | "0" "12345" "-43210" "10000000000000000000" "-10000000000000000000" "+4.2" "05" "9." "+.123" "-.1" "1e-2" "2.e-2" "1e300" "-1e300" "1e1000" "-1e1000" 3.14 expected: | 0 12345 -43210 10000000000000000000 -10000000000000000000 4.2 5 9 0.123 -0.1 0.01 0.02 1e+300 -1e+300 1.7976931348623157e+308 -1.7976931348623157e+308 3.14 - name: tonumber function error args: - 'try tonumber catch .' input: | "" "." " " "1a" ".e1" "-.e" expected: | "tonumber cannot be applied to \"\": invalid number" "tonumber cannot be applied to \".\": invalid number" "tonumber cannot be applied to \" \": invalid number" "tonumber cannot be applied to \"1a\": invalid number" "tonumber cannot be applied to \".e1\": invalid number" "tonumber cannot be applied to \"-.e\": invalid number" - name: tostring function args: - 'tostring' input: | null false true 3.14 "foo bar" {"foo": "-43210"} [1,2,3] ["<&><>"] expected: | "null" "false" "true" "3.14" "foo bar" "{\"foo\":\"-43210\"}" "[1,2,3]" "[\"<&><>\"]" - name: type function args: - 'type' input: | null false true 0 3.14 4722366482869645213696 "foo" [] {} expected: | "null" "boolean" "boolean" "number" "number" "number" "string" "array" "object" - name: reverse function args: - 'reverse' input: '[0, 1, 2, 3, 4]' expected: | [ 4, 3, 2, 1, 0 ] - name: contains function args: - -c - '.[] as $x | (null,false,true,[],[3,4],[3,1],"foo","ll",{},{"a":1},{"a":""},{"b":[5]},{"a":3,"b":[5]}) as $y | $x | [$x,$y,contains($y)]?' input: '[null,false,true,[],[1,2,3],"","hello",{"a":3,"b":[4,5,6]}]' expected: | [null,null,true] [false,false,true] [true,true,true] [[],[],true] [[],[3,4],false] [[],[3,1],false] [[1,2,3],[],true] [[1,2,3],[3,4],false] [[1,2,3],[3,1],true] ["","foo",false] ["","ll",false] ["hello","foo",false] ["hello","ll",true] [{"a":3,"b":[4,5,6]},{},true] [{"a":3,"b":[4,5,6]},{"a":1},false] [{"a":3,"b":[4,5,6]},{"a":""},false] [{"a":3,"b":[4,5,6]},{"b":[5]},true] [{"a":3,"b":[4,5,6]},{"a":3,"b":[5]},true] - name: contains function against unicode strings args: - -c - '. as $x | ("@", "\u0000@", "\u0000dc", "b\u0000c", "b\u0000cd", "b\u0000cd") as $y | $x | [$x,$y,contains($y)]' input: '"ab\u0000cd"' expected: | ["ab\u0000cd","@",false] ["ab\u0000cd","\u0000@",false] ["ab\u0000cd","\u0000dc",false] ["ab\u0000cd","b\u0000c",true] ["ab\u0000cd","b\u0000cd",true] ["ab\u0000cd","b\u0000cd",true] - name: contains function error args: - 'nan | contains([nan])' input: '0' error: | contains([null]) cannot be applied to: number (null) - name: indices, index, rindex functions against null args: - 'indices(0), index(0), rindex(0)' input: 'null' expected: | null null null - name: indices, index, rindex functions against array args: - -c - '[indices(0, 1, [1, 2, 1])], [index(0, 1, [1, 2])], [rindex(0, 1, [1, 2])]' input: | [] [0] [0, 1, 2, 1, 2, 1, 0, 1, 0] expected: | [[],[],[]] [null,null,null] [null,null,null] [[0],[],[]] [0,null,null] [0,null,null] [[0,6,8],[1,3,5,7],[1,3]] [0,1,1] [8,7,3] - name: indices, index, rindex functions against string args: - -c - '[indices("", ",", "x")], [index("", ",", "x")], [rindex("", ",", "x")]' input: '"a,bc,def,ghij,klmno"' expected: | [[],[1,4,8,13],[]] [null,1,null] [null,13,null] - name: indices, index, rindex functions against multibyte string args: - -c - '[indices("0", "1", "34")], [index("0", "1", "34")], [rindex("0", "1", "34")]' input: '"012340"' expected: | [[0,5],[1],[3]] [0,1,3] [5,1,3] - name: indices, index, rindex functions type error args: - '(try indices([]) catch .), (try index([]) catch .), (try rindex([]) catch .)' input: '{}' expected: | "indices([]) cannot be applied to: object ({})" "index([]) cannot be applied to: object ({})" "rindex([]) cannot be applied to: object ({})" - name: inside function args: - -c - '.[] as $x | (false,"abc",[],["foobaz","foobar"],{},{"a":10,"b":20,"c":30}) as $y | $x | [$x,$y,inside($y)]?' input: '["a",["bar","baz"],{"a":10,"b":20}]' expected: | ["a","abc",true] [["bar","baz"],[],false] [["bar","baz"],["foobaz","foobar"],true] [{"a":10,"b":20},{},false] [{"a":10,"b":20},{"a":10,"b":20,"c":30},true] - name: startswith function args: - 'startswith("foo")' input: | "" "fo" "foo" "fooo" "barfoo" expected: | false false true true false - name: startswith function error args: - '("x" | try startswith({}) catch .), (10 | try startswith("x") catch .)' input: '0' expected: | "startswith({}) cannot be applied to: string (\"x\")" "startswith(\"x\") cannot be applied to: number (10)" - name: endswith function args: - 'endswith("foo")' input: | "" "fo" "foo" "fooo" "barfoo" expected: | false false true false true - name: endswith function error args: - '("x" | try endswith({}) catch .), (10 | try endswith("x") catch .)' input: '0' expected: | "endswith({}) cannot be applied to: string (\"x\")" "endswith(\"x\") cannot be applied to: number (10)" - name: combinations/0 function args: - -c - '[combinations]' input: '[] [[0]] [[1, 2, 3], [4, 5, 6]]' expected: | [[]] [[0]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] - name: combinations/1 function args: - -c - '[combinations(3)]' input: '[] [0] [1, 2]' expected: | [] [[0,0,0]] [[1,1,1],[1,1,2],[1,2,1],[1,2,2],[2,1,1],[2,1,2],[2,2,1],[2,2,2]] - name: ltrimstr function args: - 'map(ltrimstr("foo")), ("x"|ltrimstr(0, null, {}))' input: '["", "fo", "foo", "fooo", "barfoo", 0, null, true, {}, []]' expected: | [ "", "fo", "", "o", "barfoo", 0, null, true, {}, [] ] "x" "x" "x" - name: rtrimstr function args: - 'map(rtrimstr("foo")), ("x"|rtrimstr(0, null, {}))' input: '["", "fo", "foo", "fooo", "barfoo", 0, null, true, {}, []]' expected: | [ "", "fo", "", "fooo", "bar", 0, null, true, {}, [] ] "x" "x" "x" - name: explode function args: - -c - 'explode' input: | "" "foo bar" "12345" "\n\t" expected: | [] [102,111,111,32,98,97,114] [65297,65298,65299,65300,65301] [10,9] - name: implode function args: - 'implode' input: | [] [102,111,111,32,98,97,114] [65297,65298,65299,65300,65301] [10,9] expected: | "" "foo bar" "12345" "\n\t" - name: implode function error args: - 'try implode catch .' input: | [0] [-1] [1114112] [[]] [{}] expected: | "\u0000" "implode cannot be applied to: array ([-1])" "implode cannot be applied to: array ([1114112])" "implode cannot be applied to: array ([[]])" "implode cannot be applied to: array ([{}])" - name: split/1 function args: - 'split(", ","") | join("/")' input: | "a,b, c, d, e,f" ", a,b, c, d, e,f, " expected: | "a,b/c/d/e,f" "a/,/b/,/ /c/,/ /d/,/ /e/,/f" "/a,b/c/d/e,f/" ",/ /a/,/b/,/ /c/,/ /d/,/ /e/,/f/,/ " - name: join function args: - 'join(",")' input: '[null, false, true, 0, 1, "", "a", "b", "abc"]' expected: | ",false,true,0,1,,a,b,abc" - name: join function with iterator argument args: - '. / "" | join(.[])' input: '"abcde"' expected: | "aabacadae" "abbbcbdbe" "acbcccdce" "adbdcddde" "aebecedee" - name: join object args: - 'join(",")' input: '{"a": null, "b": false, "c": true, "d": 0, "e": 1, "f": "", "g": "a", "h": "b", "i": "abc"}' expected: | ",false,true,0,1,,a,b,abc" - name: join object order args: - 'join(",")' input: '{"d":4, "c":3, "b":2, "a":1}' expected: | "1,2,3,4" - name: join function error item args: - 'try join(",") catch .' input: '["1","2",[3,4,5]]' expected: | "join cannot be applied to an array including: array ([3,4,5])" - name: join function error input args: - '.[] | try join(",") catch .' input: '[null, false, true, 1, "a"]' expected: | "join(\",\") cannot be applied to: null" "join(\",\") cannot be applied to: boolean (false)" "join(\",\") cannot be applied to: boolean (true)" "join(\",\") cannot be applied to: number (1)" "join(\",\") cannot be applied to: string (\"a\")" - name: join function non-string separator args: - '.[] | try join(1) catch .' input: '[[],[1],[1,2]]' expected: | "" "1" "join(1) cannot be applied to: array ([1,2])" - name: ascii_downcase function args: - 'ascii_downcase' input: '"@ABC XYZ[] `abc xyz{} Αα"' expected: | "@abc xyz[] `abc xyz{} Αα" - name: ascii_upcase function args: - 'ascii_upcase' input: '"@ABC XYZ[] `abc xyz{} Αα"' expected: | "@ABC XYZ[] `ABC XYZ{} Αα" - name: walk function args: - -c - 'walk(if type == "boolean" then not else . + . end) | .[]' input: '[1, ["a", {"b": []}], {"c": [null, false, true]}]' expected: | 2 ["aa",{"b":[]},"aa",{"b":[]}] {"c":[null,true,false,null,true,false]} 2 ["aa",{"b":[]},"aa",{"b":[]}] {"c":[null,true,false,null,true,false]} - name: walk function with multiple values args: - -c - 'walk(.,0)' input: '[1,2,3] {"x":1,"y":2,"z":3}' expected: | [1,0,2,0,3,0] 0 {"x":0,"y":0,"z":0} 0 - name: transpose function args: - -c - 'transpose' input: | [] [[1,2,3,[]],[4,"foo",{}],[5,6]] expected: | [] [[1,4,5],[2,"foo",6],[3,{},null],[[],null,null]] - name: function not defined args: - 'abc' input: '{}' error: | function not defined: abc/0 exit_code: 3 - name: variable not defined args: - '$abc' input: '{}' error: | variable not defined: $abc exit_code: 3 - name: argument count error args: - 'map(.;.)' input: '{}' error: | function not defined: map/2 exit_code: 3 - name: function declaration with an argument args: - 'def f(g): g | g; f(..)' input: '[0, 1]' expected: | [ 0, 1 ] 0 1 0 1 - name: function nested declaration args: - -c - 'def f: def g: .; g; f' input: '[0, 1]' expected: | [0,1] - name: function declaration with call of another declared function args: - -c - 'def f: .,.; def g: [f,f]; f,g' input: '[0, 1]' expected: | [0,1] [0,1] [[0,1],[0,1],[0,1],[0,1]] - name: function declaration with update operator args: - -c - 'def f: def f($x): .; f(.x) | .x; f += f' input: '{"x": 1}' expected: | {"x":2} - name: function declaration and recursive call args: - 'def f(g): if type == "array" then map(f(g * 2)) else g end; f(.)' input: '[[[1,2]]]' expected: | [ [ [ 8, 16 ] ] ] - name: function declaration and recursive call args: - 'def f(g): if . < 1000 then .,(g|f(g|g)) else g end; f(.*2)' input: '1' expected: | 1 2 8 128 2147483648 - name: function declaration and recursive call args: - 'def f: . as $x | $x, ($x + 1 | f); last(limit(100000; f))' input: '0' expected: | 99999 - name: function declaration and recursive call args: - 'def f: . as $x | $x, (if $x < 3 then $x + 1 | f else empty end), $x; f' input: '0' expected: | 0 1 2 3 3 2 1 0 - name: function declaration and recursive call args: - 'def f: ., if . < 2 then . + (1,2) | f else empty end; f' input: '0' expected: | 0 1 2 3 2 - name: function declaration and recursive call args: - '100000 as $x | def f: if . == $x then . else . + 1 | f end; f' input: '0' expected: | 100000 - name: function declaration and recursive call args: - 'def f: if .a then .a | f end; {}, {a:{}} | f | .,0' input: '0' expected: | {} 0 {} 0 - name: function declaration with duplicate function names args: - 'def f(g): def f: def g: 3; g * 2; g * f; def f: . * 5; f(f)' input: '1' expected: | 30 - name: function declaration with duplicate function names args: - '(def f(g): def g: 1; g; f(2)), (def g: 3; def f(g): g; f(4))' input: '0' expected: | 1 4 - name: function declaration with duplicate argument names args: - 'def f(g;g): g; f(1;2)' input: 'null' expected: | 2 - name: function declaration with duplicate variable names args: - 'def f($g;$g): $g; f(1;2,3)' input: 'null' expected: | 2 3 - name: function declaration with builtin function name conflict args: - 'def empty: . % 2; range(5) | select(empty > 0)' input: 'null' expected: | 1 3 - name: function declaration with builtin function name conflict args: - 'def select(f): f; null, 0 | numbers, select(.)' input: 'null' expected: | null 0 0 - name: function declaration with argument name conflict args: - 'def update: .[1:]; [range(3)] | while(length>0; update)' input: 'null' expected: | [ 0, 1, 2 ] [ 1, 2 ] [ 2 ] - name: function declaration with argument name conflict args: - 'def f($keys): with_entries(.key *= $keys); {x: 1} | f(3)' input: 'null' expected: | { "xxx": 1 } - name: function declaration with mixed argument types args: - -c - 'def f0( a; b; c): a*100 + b*10 + c; def f1( a;$b; c): a*100 + $b*10 + c; def f2($a; b;$c): $a*100 + b*10 + $c; def f3($a;$b;$c): $a*100 + $b*10 + $c; [f0(.[];.[];.[])], [f1(.[];.[];.[])], [f2(.[];.[];.[])], [f3(.[];.[];.[])]' input: '[1,2]' expected: | [111,211,121,221,112,212,122,222] [111,211,112,212,121,221,122,222] [111,121,112,122,211,221,212,222] [111,112,121,122,211,212,221,222] - name: function declaration with function reference to value argument args: - -c - 'def f($x; $y): [$x, x, $y, y]; f(1,2; 3,4)' input: 'null' expected: | [1,1,2,3,3,4] [1,1,2,4,3,4] [2,1,2,3,3,4] [2,1,2,4,3,4] - name: function declaration inside query args: - -c - '[0, def f: 1, def g: 2; g, 3 as $x | def h: $x + 1; h * g; f, f + 1, 5]' input: 'null' expected: | [0,1,2,8,2,3,9,5] - name: argument count error for custom function args: - 'def f(g): g | g; f' input: '{}' error: | function not defined: f/0 exit_code: 3 - name: function declaration name error args: - 'def $f: .; $f' input: 'null' error: | invalid query: def $f: .; $f def $f: .; $f ^ unexpected token "$f" exit_code: 3 - name: add, subtract, multiply, divide, modulo numbers args: - -c - '[.[] + .[]], [.[] - .[]], [.[] * .[]], [.[] / .[]], [.[] % .[]]' input: '[-1, 5, 16]' expected: | [-2,4,15,4,10,21,15,21,32] [0,6,17,-6,0,11,-17,-11,0] [1,-5,-16,-5,25,80,-16,80,256] [1,-5,-16,-0.2,1,3.2,-0.0625,0.3125,1] [0,0,0,-1,0,1,-1,5,0] - name: add, subtract, multiply, divide, modulo large numbers args: - -c - '[.[] + .[]], [.[] - .[]], [.[] * .[]], [.[] / .[]], [.[] % .[]]' input: '[-1, 47, 9223372036854775807, 557702036893939360447070208]' expected: | [-2,46,9223372036854775806,557702036893939360447070207,46,94,9223372036854775854,557702036893939360447070255,9223372036854775806,9223372036854775854,18446744073709551614,557702046117311397301846015,557702036893939360447070207,557702036893939360447070255,557702046117311397301846015,1115404073787878720894140416] [0,48,9223372036854775808,557702036893939360447070209,-48,0,9223372036854775760,557702036893939360447070161,-9223372036854775808,-9223372036854775760,0,557702027670567323592294401,-557702036893939360447070209,-557702036893939360447070161,-557702027670567323592294401,0] [1,-47,-9223372036854775807,-557702036893939360447070208,-47,2209,433498485732174462929,26211995734015149941012299776,-9223372036854775807,433498485732174462929,85070591730234615847396907784232501249,5143893371984510803678792604831111805828857856,-557702036893939360447070208,26211995734015149941012299776,5143893371984510803678792604831111805828857856,311031561955648899562865430629603887429045530881163264] [1,-47,-9223372036854775807,-557702036893939360447070208,-0.02127659574468085,1,196241958230952670,1.1866000784977433e+25,-1.0842021724855044e-19,5.095750210681871e-18,1,60466176,-1.7930721672981344e-27,8.427439186301232e-26,1.65381716879202e-8,1] [0,0,0,0,-1,0,35,37,-1,47,0,60466176,-1,47,9223372036854775807,0] - name: add, subtract, multiply, divide, modulo operators precedence args: - -c - '[.[] + .[] % .[] * .[] - .[] / .[] % .[] + .[]] | while(length>0; .[32:])[:32]' input: '[3, 7]' expected: | [5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9,4,8,7,11,13,17,4,8,4,8,11,15,25,29,4,8] [6,10,9,13,15,19,6,10,6,10,13,17,27,31,6,10,5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9] [5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9,4,8,7,11,13,17,4,8,4,8,11,15,25,29,4,8] [6,10,9,13,15,19,6,10,6,10,13,17,27,31,6,10,5,9,8,12,14,18,5,9,5,9,12,16,26,30,5,9] [9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13,8,12,11,15,17,21,8,12,8,12,15,19,29,33,8,12] [10,14,13,17,19,23,10,14,10,14,17,21,31,35,10,14,9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13] [9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13,8,12,11,15,17,21,8,12,8,12,15,19,29,33,8,12] [10,14,13,17,19,23,10,14,10,14,17,21,31,35,10,14,9,13,12,16,18,22,9,13,9,13,16,20,30,34,9,13] - name: integer division precision args: - '72 / (1, -2, 3, -4, 6, -8) * 100000000000000000000' input: '0' expected: | 7200000000000000000000 -3600000000000000000000 2400000000000000000000 -1800000000000000000000 1200000000000000000000 -900000000000000000000 - name: optional and division operator args: - '1?/1' input: '0' expected: | 1 - name: optional and division operator parse error args: - '.?/' input: '0' error: | invalid query: .?/ .?/ ^ unexpected EOF exit_code: 3 - name: factorial calculation args: - -n - -r - 'def fact($n): if $n < 1 then 1 else $n * fact($n - 1) end; (range(30;51) | "\(.)! = \(fact(.))"), fact(50) / fact(30), fact(50) % (fact(30) + 1), fact(100), fact(1000)*1.0, 1/fact(1000), -1/fact(1000), 1.0/fact(1000)' expected: | 30! = 265252859812191058636308480000000 31! = 8222838654177922817725562880000000 32! = 263130836933693530167218012160000000 33! = 8683317618811886495518194401280000000 34! = 295232799039604140847618609643520000000 35! = 10333147966386144929666651337523200000000 36! = 371993326789901217467999448150835200000000 37! = 13763753091226345046315979581580902400000000 38! = 523022617466601111760007224100074291200000000 39! = 20397882081197443358640281739902897356800000000 40! = 815915283247897734345611269596115894272000000000 41! = 33452526613163807108170062053440751665152000000000 42! = 1405006117752879898543142606244511569936384000000000 43! = 60415263063373835637355132068513997507264512000000000 44! = 2658271574788448768043625811014615890319638528000000000 45! = 119622220865480194561963161495657715064383733760000000000 46! = 5502622159812088949850305428800254892961651752960000000000 47! = 258623241511168180642964355153611979969197632389120000000000 48! = 12413915592536072670862289047373375038521486354677760000000000 49! = 608281864034267560872252163321295376887552831379210240000000000 50! = 30414093201713378043612608166064768844377641568960512000000000000 114660755112113373922453094400000 150592104700077684713855385600001 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 1.7976931348623157e+308 0 -0 0 - name: negative large numbers args: - -n - 'def f($n): if $n < 1 then 1 else (-$n) * (-f($n - 1)) end; -f(range(30;40))' expected: | -265252859812191058636308480000000 -8222838654177922817725562880000000 -263130836933693530167218012160000000 -8683317618811886495518194401280000000 -295232799039604140847618609643520000000 -10333147966386144929666651337523200000000 -371993326789901217467999448150835200000000 -13763753091226345046315979581580902400000000 -523022617466601111760007224100074291200000000 -20397882081197443358640281739902897356800000000 - name: large number literal args: - -n - | 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0, -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 expected: | 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 1e+308 -1e+308 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 1.7976931348623157e+308 -1.7976931348623157e+308 - name: zero division args: - '.[] / .[]' input: '[1, 0]' expected: | 1 0 error: | cannot divide number (1) by: number (0) - name: zero modulo args: - '.[] % .[]' input: '[1, 0]' expected: | 0 0 error: | cannot modulo number (1) by: number (0) - name: modulo with near 0.0 args: - '.[] % .[]' input: '[1, 0.9]' expected: | 0 0 error: | cannot modulo number (1) by: number (0.9) - name: zero division and modulo for large number args: - -c - '[try (.[] / .[]) catch .], [try (.[] % .[]) catch .]' input: '[1267650600228229401496703205376, 0]' expected: | [1,0,"cannot divide number (12676506002282294014967032 ...) by: number (0)"] [0,0,"cannot modulo number (12676506002282294014967032 ...) by: number (0)"] - name: add strings args: - '.[] + .[] + "c"' input: '["a", "b"]' expected: | "aac" "bac" "abc" "bbc" - name: multiply constant args: - '3 * .[], 2.5 * .[], .054e2 * .[]' input: '[1, 2.5, "abc"]' expected: | 3 7.5 "abcabcabc" 2.5 6.25 "abcabc" 5.4 13.5 "abcabcabcabcabc" - name: multiply strings args: - '[-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 3.7, 10, 9444732965739290427392 / 4722366482869645213696, 100000000, infinite, -infinite, nan, -1e300, 1e300][] * "abc"' input: 'null' expected: | null null null "abc" "abc" "abc" "abcabcabc" "abcabcabcabcabcabcabcabcabcabc" "abcabc" null null null null null null - name: multiply empty string args: - '(-9223372036854775808, -1, 0, 0.001, 0.999, 1, 7, 9223372036854775807) * ""' input: 'null' expected: | null null null "" "" "" "" "" - name: multiply objects args: - -c - '.[] * .[]' input: '[{"foo": 10, "bar": {"qux": 20}}, {"foo": 20, "bar": {"baz": "quux"}}]' expected: | {"bar":{"qux":20},"foo":10} {"bar":{"baz":"quux","qux":20},"foo":10} {"bar":{"baz":"quux","qux":20},"foo":20} {"bar":{"baz":"quux"},"foo":20} - name: divide strings args: - -c - '.[] / .[]' input: '["abc", "a", "b"]' expected: | ["",""] ["a"] ["b"] ["","bc"] ["",""] ["b"] ["a","c"] ["a"] ["",""] - name: add and subtract arrays args: - -c - '.[] + .[] - .[]' input: '[[1, 2], [3, 4]]' expected: | [] [3,4] [3,4] [3,4,3,4] [1,2,1,2] [1,2] [1,2] [] - name: ensure arrays are immutable during addition args: - -c - '. as $first | .[:0] + [6, 7, 8] | ., $first' input: | [1,2,3,4,5] expected: | [6,7,8] [1,2,3,4,5] - name: subtract arrays args: - -c - '[1,2,3,4,1,4722366482869645213696] - [.,3,4722366482869645213696]' input: '1' expected: | [2,4] - name: subtract arrays with large numbers args: - -c - '. as $x | [$x-$x] - [0]' input: '10000000000000' expected: | [] - name: subtract arrays with nan args: - -c - '[nan,{x:nan}] - [nan,{x:nan}]' input: '1' expected: | [null,{"x":null}] - name: add object args: - -c - '.[] + .[]' input: '[{"a":1},{"b":2}]' expected: | {"a":1} {"a":1,"b":2} {"a":1,"b":2} {"b":2} - name: add null args: - '.[] + .[]' input: '[1, 2, null]' expected: | 2 3 1 3 4 2 1 2 null - name: subtract null args: - '.[] - .[]' input: '[1, 2, null]' expected: | 0 1 error: | cannot subtract: null and number (1) - name: subtract object args: - '.[] - .[]' input: '[{"a":1},{"b":2}]' error: | cannot subtract: object ({"a":1}) and object ({"a":1}) - name: comparison operators args: - '[.[] > .[], .[] < .[], .[] >= .[], .[] <= .[], .[] == .[], .[] != .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' input: '[null, false, true, 0, 1, "", "a", "b", "abc", [], [1], [2], [3, 4], ["a", "b"], [[]], [[], []], [{}], {}, {"a": ""}, {"a":[1]}, {"b":[1]}, {"b": []}, {"b": [[]]}, {"a": 0, "b": 1}]' expected: | "011111111111111111111111001111111111111111111111000111111111111111111111000011111111111111111111000001111111111111111111" "000000111111111111111111000000011111111111111111000000000111111111111111000000010111111111111111000000000011111111111111" "000000000001111111111111000000000000111111111111000000000000011111111111000000000000001111111111000000000000000111111111" "000000000000000011111111000000000000000001111111000000000000000000111111000000000000000000011111000000000000000000001111" "000000000000000000000010000000000000000000001010000000000000000000000000000000000000000000001110000000000000000000000000" "100000000000000000000000110000000000000000000000111000000000000000000000111100000000000000000000111110000000000000000000" "111111000000000000000000111111101000000000000000111111100000000000000000111111111000000000000000111111111100000000000000" "111111111110000000000000111111111111000000000000111111111111100000000000111111111111110000000000111111111111111000000000" "111111111111111100000000111111111111111110000000111111111111111111000000111111111111111111100000111111111111111111110101" "111111111111111111110001111111111111111111111101111111111111111111110000111111111111111111111111011111111111111111111111" "001111111111111111111111000111111111111111111111000011111111111111111111000001111111111111111111000000111111111111111111" "000000010111111111111111000000011111111111111111000000000111111111111111000000000011111111111111000000000001111111111111" "000000000000111111111111000000000000011111111111000000000000001111111111000000000000000111111111000000000000000011111111" "000000000000000001111111000000000000000000111111000000000000000000011111000000000000000000001010000000000000000000001110" "000000000000000000000010000000000000000000001111100000000000000000000000110000000000000000000000111000000000000000000000" "111100000000000000000000111110000000000000000000111111000000000000000000111111100000000000000000111111111000000000000000" "111111101000000000000000111111111100000000000000111111111110000000000000111111111111000000000000111111111111100000000000" "111111111111110000000000111111111111111000000000111111111111111100000000111111111111111110000000111111111111111111000000" "111111111111111111100000111111111111111111110000111111111111111111111101111111111111111111110101111111111111111111111111" "111111111111111111110001100000000000000000000000010000000000000000000000001000000000000000000000000100000000000000000000" "000010000000000000000000000001000000000000000000000000100000000000000000000000010000000000000000000000001000000000000000" "000000000100000000000000000000000010000000000000000000000001000000000000000000000000100000000000000000000000010000000000" "000000000000001000000000000000000000000100000000000000000000000010000000000000000000000001000000000000000000000000100000" "000000000000000000010000000000000000000000001000000000000000000000000100000000000000000000000010000000000000000000000001" "011111111111111111111111101111111111111111111111110111111111111111111111111011111111111111111111111101111111111111111111" "111110111111111111111111111111011111111111111111111111101111111111111111111111110111111111111111111111111011111111111111" "111111111101111111111111111111111110111111111111111111111111011111111111111111111111101111111111111111111111110111111111" "111111111111111011111111111111111111111101111111111111111111111110111111111111111111111111011111111111111111111111101111" "111111111111111111110111111111111111111111111011111111111111111111111101111111111111111111111110" - name: comparison and arithmetic operators args: - '[.[] + .[] > .[] - .[], .[] * .[] < .[] / .[] + .[] / .[], .[] == .[] * .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' input: '[1, 2, 3]' expected: | "111111111111111111011111111111111111111111111111111111111111111111111111111111111100000000110100000111100100100000000100" "000000110100000100000000100000000100000000110100000111100100111110100110100000110100000111100100110100000110100000110100" "000111100100111110100111110100111100100111100100111110100111100100111100100111100100100000000110100000111100100000000000" "100000000100000000000000000100000000100000000100000000110100000111100100100000000100000000110100000100000000100000000100" "000000110100000111100100111110100100000000110100000110100000100000000110100000110100000100000000110100000111100100000000" "000100000000100000000000000000000000000100000000100000000110100000111100100100000000100000000110100000000000000100000000" "100000000100000000110100000111100100100000000100000000110100000100000000100000000100000000100010001010000000001000000" - name: comparison operators with large numbers args: - '[.[] > .[], .[] < .[], .[] >= .[], .[] <= .[], .[] == .[], .[] != .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' input: '[-9999999999999999999999999999999999, -4722366482869645213697, -2, 0, 47, 4722366482869645213696, 4722366482869645213697, 99999999999999999999999999999999999]' expected: | "011111110011111100011111000011110000011100000011000000010000000000000000100000001100000011100000111100001111100011111100" "111111101111111101111111001111110001111100001111000001110000001100000001100000001100000011100000111100001111100011111100" "111111101111111110000000010000000010000000010000000010000000010000000010000000010111111110111111110111111110111111110111" "111110111111110111111110" - name: comparison operators associativity error args: - '. == . == .' input: '1' error: | invalid query: . == . == . . == . == . ^ unexpected token "==" exit_code: 3 - name: logical operators args: - '[.[] > .[] and .[] or .[] < .[]] | while(length>0; .[120:]) | .[:120] | map(if . then 1 else 0 end) | join("")' input: '[null, false, true, 0, "", [], {}]' expected: | "000000010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000011000001" "110000111100011111001111110111110000000100000011000001110000111100011111001111110000000010000001100000111000011110001111" "100111111011111000000010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111101111100" "000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110111110000000100000011000" "001110000111100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011110001" "111100111111000000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111100000" "000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000000001000000110000011100" "001111000111110011111101111100000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001" "111110111110000000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111011111000000" "010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111101111100000001000000110000011" "100001111000111110011111100000000100000011000001110000111100011111001111110111110000000100000011000001110000111100011111" "001111110000000010000001100000111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000" "011000001110000111100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011" "110001111100111111000000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111" "100000000100000011000001110000111100011111001111110111110000000100000011000001110000111100011111001111110000000010000001" "100000111000011110001111100111111011111000000010000001100000111000011110001111100111111000000001000000110000011100001111" "000111110011111100000000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000000" "001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110111110000000100000011000001" "110000111100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011110001111" "100111111000000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111100000000" "100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000000001000000110000011100001" "111000111110011111100000000100000011000001110000111100011111001111110000000010000001100000111000011110001111100111111000" "000001000000110000011100001111000111110011111101111100000001000000110000011100001111000111110011111100000000100000011000" "001110000111100011111001111110111110000000100000011000001110000111100011111001111110000000010000001100000111000011110001" "111100111111000000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110000000010" "000001100000111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000011000001110000111" "100011111001111110000000010000001100000111000011110001111100111111011111000000010000001100000111000011110001111100111111" "000000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001111110000000010000001100000" "111000011110001111100111111000000001000000110000011100001111000111110011111100000000100000011000001110000111100011111001" "1111100000000100000011000001110000111100011111001111110" - name: alternative operator args: - -c - '[.a // .b, .b // null // .a, .c[] // .d[], .d[] // .c, .c // .a, .e.a // .c[]]' input: '{ "a": 5, "b": false, "c": [null,false], "d": [false,3], "e": null }' expected: | [5,5,false,3,3,[null,false],null,false] - name: condition args: - 'if . then . else [.] end' input: 'null false true 0' expected: | [ null ] [ false ] true 0 - name: condition with elif clause args: - 'if . == null then . elif not then [.] elif . == true then [[.]] else [[[.]]] end' input: 'null false true 0' expected: | null [ false ] [ [ true ] ] [ [ [ 0 ] ] ] - name: condition without else clause args: - 'map(if . > 2 then . / 2 end)' input: '[1, 2, 3, 4, 5]' expected: | [ 1, 2, 1.5, 2, 2.5 ] - name: condition precedence args: - '. + if 1 then 2 else 3 end + 4' input: '1' expected: | 7 - name: condition with optional operator args: - 'if . then tonumber end ?' input: '1' expected: | 1 - name: condition with identity in then clause args: - 'if 0 then . else 0|0 end' input: 'null' expected: | null - name: condition with identity in else clause args: - 'if 0 then 0|0 else . end' input: 'null' expected: | 0 - name: condition with suffix args: - 'if . then 1 end []' input: '1' error: | invalid query: if . then 1 end [] if . then 1 end [] ^ unexpected token "[" exit_code: 3 - name: not function args: - 'not' input: | true false expected: | false true - name: iterator in condition args: - 'if .. then [] else {} end' input: '{ "foo": false }' expected: | [] {} - name: try args: - 'map(try utf8bytelength)' input: '[[], {}, [1,2], "hello", 55, true, false]' expected: | [ 5 ] - name: try catch args: - 'map(try utf8bytelength catch .)' input: '[[], {}, [1,2], "hello", 55, true, false]' expected: | [ "utf8bytelength cannot be applied to: array ([])", "utf8bytelength cannot be applied to: object ({})", "utf8bytelength cannot be applied to: array ([1,2])", 5, "utf8bytelength cannot be applied to: number (55)", "utf8bytelength cannot be applied to: boolean (true)", "utf8bytelength cannot be applied to: boolean (false)" ] - name: try catch with function declaration args: - 'def f: fromjson; try ({} | f) catch ., 1' input: 'null' expected: | "fromjson cannot be applied to: object ({})" 1 - name: try and comma operator precedence args: - '1, try error(2), 3' input: 'null' expected: | 1 3 - name: try and alternative operator precedence args: - 'try error(0) // 1' input: 'null' expected: | 1 - name: try and update operator precedence args: - 'try error(0) = 1' input: 'null' expected: | null - name: try and arithmetic operator precedence args: - 'try 0 * error(0)' input: 'null' error: | error: 0 exit_code: 5 - name: try and indexing precedence args: - 'try 0 .[error(0)]' input: 'null' - name: try catch precedence args: - '1 + try 2 catch 3 + 4' input: 'null' expected: | 7 - name: reduce iterator args: - '[range(.)] | reduce .[] as $i (0; . + $i)' input: '10' expected: | 45 - name: reduce arrays args: - 'reduce .[] as [$i,$j] (0; . + $i * $j)' input: '[[1,2,10], [3,4,10]]' expected: | 14 - name: reduce objects args: - 'reduce .[] as {$i, j:$j} (0; . + $i - $j)' input: '[{"i":2, "j":1}, {"i":5, "j":3}, {"i":6, "j":4}]' expected: | 5 - name: reduce null args: - 'reduce . as $n (.; .)' input: 'null' expected: | null - name: reduce precedence args: - '. + reduce . as $n (. + [2]; [.]) + .' input: '[1]' expected: | [ 1, [ 1, 2 ], 1 ] - name: reduce variable scope args: - -c - '. as $x | reduce .[] as $x (.; . + .) | [., $x]' input: '[1]' expected: | [[1,1],[1]] - name: foreach iterator args: - 'foreach range(5) as $item (0; $item)' input: 'null' expected: | 0 1 2 3 4 - name: foreach arrays args: - 'foreach .[] as [$i, $j] (0; . + $i - $j)' input: '[[2,1], [5,3], [6,4]]' expected: | 1 3 5 - name: foreach objects args: - 'foreach .[] as {a:$a} (0; . + $a; -.)' input: '[{"a":1}, {"b":2}, {"a":3, "b":4}]' expected: | -1 -1 -4 - name: foreach with iterator in update args: - 'foreach .[] as $i (1; ., . + $i, range(.))' input: '[1, 2, 3]' expected: | 1 2 0 0 2 2 5 0 1 - name: foreach variable scope args: - -c - '. as $x | foreach .[] as $x (.; . + .) | [., $x]' input: '[1]' expected: | [[1,1],[1]] - name: label and break syntax args: - '[(label $x | .[] | if . > 2 then break $x else . end), 0]' input: '[1,2,3,2,1]' expected: | [ 1, 2, 0 ] - name: label and break in foreach syntax args: - '[range(.)] | label $x | foreach .[] as $i (0; if . > 5 then break $x else . + $i end)' input: '10' expected: | 0 1 3 6 - name: label and break in try catch syntax args: - 'label $x | (label $y | try break $y catch .), .' input: '10' expected: | 10 - name: label and break in function argument args: - 'range(.) | label $out | limit(3; 1, if . > 1 then break $out end, 2)' input: '3' expected: | 1 0 2 1 1 2 1 - name: label and break in function argument with foreach args: - 'label $out | last(foreach range(0; 10) as $i ({x: 1}; if .x > 10 then break $out else .x += 1 end))' input: '0' expected: | { "x": 11 } - name: label in recursive call args: - 'def f: label $x | if . < 5 then ., (. + 1 | f, break $x, .) else . end; f' input: '0' expected: | 0 1 2 3 4 5 - name: duplicate label names args: - 'label $x | (label $x | break $x), .' input: '1' expected: | 1 - name: invalid label name args: - 'label foo | .' input: '1' error: | invalid query: label foo | . label foo | . ^ unexpected token "foo" exit_code: 3 - name: label not defined error for break without label args: - 'break $test' input: '1' error: | label not defined: $test exit_code: 3 - name: label not defined error for break with label in parenthesis args: - '(label $x | 1) | break $x' input: '1' error: | label not defined: $x exit_code: 3 - name: invalid break name args: - 'label $x | break x' input: '1' error: | invalid query: label $x | break x label $x | break x ^ unexpected token "x" exit_code: 3 - name: invalid break syntax args: - 'label $x | break' input: '0' error: | invalid query: label $x | break label $x | break ^ unexpected EOF exit_code: 3 - name: variable and label name conflict args: - '. as $x | (label $x | break $x), $x' input: '0' expected: | 0 - name: string interpolation args: - -c - '["\(.foo[])_\(.bar[])_\(.baz[])"]' input: '{ "foo": [0, 1], "bar": ["x", "y"], "baz": [{"a": 1}, [1], null] }' expected: | ["0_x_{\"a\":1}","1_x_{\"a\":1}","0_y_{\"a\":1}","1_y_{\"a\":1}","0_x_[1]","1_x_[1]","0_y_[1]","1_y_[1]","0_x_null","1_x_null","0_y_null","1_y_null"] - name: string interpolation in object args: - -c - '[{a:"\(.foo[])"}, {"\(.foo[])": 1}, {"\(.bar[])"}]' input: '{ "foo": [0, 1], "bar": ["x", "y"], "x": 10, "y": 20 }' expected: | [{"a":"0"},{"a":"1"},{"0":1},{"1":1},{"x":10},{"y":20}] - name: string interpolation nested args: - -c - '" \("(") \("x" + "\(1 + 2)") \("\\") \("\("\\")") \(")") "' input: '0' expected: | " ( x3 \\ \\ ) " - name: string interpolation nested deeply args: - -c - '"\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\("\(1+2)")")")")")")")")")")")")")")")")")")")")")")")"' input: '0' expected: | "3" - name: string interpolation multiple lines args: - -c - | "\( # " "\( "\(2 * (4 # " +# 6))" )" # " # " + (def f: "\("\("(" + "\\\((1 + 2) * 3)\"" + ")")")"; f) # " # " # " )" input: '0' expected: | "20(\\9\")" - name: string interpolation parse error args: - '"\(1 +)"' input: '0' error: | invalid query: "\(1 +)" "\(1 +)" ^ unexpected token ")" exit_code: 3 - name: tojson function args: - 'tojson' input: | null false true 4722366482869645213696 "foo" ["foo"] {"a": [1,2,3]} expected: | "null" "false" "true" "4722366482869645213696" "\"foo\"" "[\"foo\"]" "{\"a\":[1,2,3]}" - name: fromjson function args: - -c - 'fromjson' input: | "null" "false" "true" "4722366482869645213696" "\"foo\"" "[\"foo\"]" "{\"a\":[1,2,3]}" expected: | null false true 4722366482869645213696 "foo" ["foo"] {"a":[1,2,3]} - name: fromjson function error args: - 'fromjson' input: '"[0"' error: | fromjson cannot be applied to "[0": unexpected EOF - name: variable definition args: - -c - '.foo.bar as $foo | .foo.bar as $bar | {$foo} + {bar: ($bar / 2)} * { baz: 20 }' input: | {"foo":{"bar":10}} expected: | {"bar":5,"baz":20,"foo":10} - name: variable definition args: - -c - '.ys as $ys | .xs[] | {a, b: $ys[.b]}' input: | {"xs":[{"a":"a0","b":"b0"},{"a":"a1","b":"b1"}],"ys":{"b0":"bb0","b1":"bb1"}} expected: | {"a":"a0","b":"bb0"} {"a":"a1","b":"bb1"} - name: variable name error args: - '.foo as foo | {$bar}' input: 'null' error: | invalid query: .foo as foo | {$bar} .foo as foo | {$bar} ^ unexpected token "foo" exit_code: 3 - name: variable name not found error args: - '.foo as $foo | {$bar}' input: | {"foo":10} error: | variable not defined: $bar exit_code: 3 - name: binding an array args: - -c - '. as [$i, $j] | [$j, $i]' input: | [] [10] [20,30] expected: | [null,null] [null,10] [30,20] - name: binding an array deeply args: - -c - '. as [$i, [$j], [[$k]]] | [$i, $j, $k]' input: | [] [10] [20,[30],[[40]]] expected: | [null,null,null] [10,null,null] [20,30,40] - name: binding an array error args: - '. as [$i, $j] | [$j, $i]' input: | {"foo":10} error: | expected an array but got: object ({"foo":10}) - name: binding an object args: - -c - '. as {$foo, bar: $bar} | {$bar, $foo}' input: | {} {"foo":10} {"foo":20,"bar":30} expected: | {"bar":null,"foo":null} {"bar":null,"foo":10} {"bar":30,"foo":20} - name: binding an object deeply args: - -c - '. as {$foo, bar: {"baz": {$qux}, "": $quy}} | {$foo, $qux, $quy}' input: | {} {"foo":10} {"foo":20,"bar":{"baz":{"qux":30},"":-1}} expected: | {"foo":null,"qux":null,"quy":null} {"foo":10,"qux":null,"quy":null} {"foo":20,"qux":30,"quy":-1} - name: binding an object with expression args: - -c - '. as {as: $foo, ("e"+"x"+"p"): $bar, (.foo): $baz, "\(.qux)": $qux} | [$foo, $bar, $baz, $qux]' input: | {"as":1,"exp":2,"foo":"bar","bar":3,"qux":"quux","quux":4} expected: | [1,2,3,4] - name: binding an object error args: - '. as {$i} | [$i]' input: | [10] error: | expected an object but got: array ([10]) - name: binding object and array deeply args: - -c - '. as {$a, $b: [$c, $d]} | [$a, $b, $c, $d]' input: | {"a":1,"b":[2,{"d":3}]} expected: | [1,[2,{"d":3}],2,{"d":3}] - name: binding variable scope in parenthesis args: - -c - '. as $x | (.foo as $x | [$x]) | .+[$x]' input: | {"foo": 42} expected: | [42,{"foo":42}] - name: binding variable scope in array args: - -c - '. as $x | [.foo as $x | $x] | $x' input: | {"foo":42} expected: | {"foo":42} - name: binding variable scope in object args: - -c - '. as $x | {bar: (.foo as $x | $x)} | $x' input: | {"foo":"bar"} expected: | {"foo":"bar"} - name: binding variable scope in object key args: - -c - '. as $x | {(.foo as $x | $x): $x}' input: | {"foo":"bar"} expected: | {"bar":{"foo":"bar"}} - name: binding variable scope in condition args: - -c - '. as $x | if (.foo,.bar) as $x | $x then 1 as $x | $x else 2 as $x | $x end | [., $x]' input: | {"foo":"bar"} expected: | [1,{"foo":"bar"}] [2,{"foo":"bar"}] - name: binding variable scope in try catch args: - -c - '. as $x | try .foo as $x | $x | error catch $x' input: | {"foo":42} expected: | {"foo":42} - name: binding variable scope in reduce args: - '[range(.)] | 3 as $x | reduce .[] as $i (.[5] as $x | $x; . + $x + $i)' input: '10' expected: | 80 - name: binding variable scope in foreach args: - -c - '[2 as $x | foreach range(.) as $item (. as $x | $x; . + $item * $x; . + $x)]' input: '5' expected: | [7,9,13,19,27] - name: binding variable scope error in parenthesis args: - '(. as $i | $i) | $i' input: '0' error: | variable not defined: $i exit_code: 3 - name: binding variable scope error in array args: - '[. as $i | $i] | $i' input: '0' error: | variable not defined: $i exit_code: 3 - name: binding variable scope error in object args: - '{x: (. as $i | $i)} | $i' input: '0' error: | variable not defined: $i exit_code: 3 - name: binding variable scope error in object key args: - '{(. as $i | $i): $i}' input: '"x"' error: | variable not defined: $i exit_code: 3 - name: function scope in parenthesis args: - 'def f: 1; . | (def f: 2; f) | f, .' input: '0' expected: | 1 2 - name: function scope in array args: - -c - 'def f: 1; . | [def f: 2; f] | f, .' input: '0' expected: | 1 [2] - name: function scope in object args: - -c - 'def f: 1; . | {bar: (def f: .foo; f)} | f, .' input: | {"foo":"bar"} expected: | 1 {"bar":"bar"} - name: function scope in object key args: - -c - 'def f: 1; . | {(def f: "foo"; f): f}' input: '0' expected: | {"foo":1} - name: function scope error in parenthesis args: - '(def f: 1; f) | f' input: '0' error: | function not defined: f exit_code: 3 - name: function scope error in array args: - '[def f: 1; f] | f' input: '0' error: | function not defined: f exit_code: 3 - name: function scope error in object args: - '{x: (def f: 1; f)} | f' input: '0' error: | function not defined: f exit_code: 3 - name: function scope error in object key args: - '{(def f: 1; f): f}' input: '"x"' error: | function not defined: f exit_code: 3 - name: first/0, last/0 functions args: - -c - '[first, last]' input: '[1,2,3,4,5]' expected: | [1,5] - name: first/1, last/1, nth functions args: - -c - '[first(.[]), last(.[]), nth(0,3,5), nth(0,3,5;.[]), nth(9; range(infinite))]' input: '[1,2,3,4,5]' expected: | [1,5,1,4,null,1,4,9] - name: first/1 function with optional operator args: - 'first(.), first(.?), first(.,.), first(.?,.), first(.,.?), first(.?,.?)' input: 'null' expected: | null null null null null null - name: first/1 function nested args: - 'first((null, null) | first(null))' input: 'null' expected: | null - name: all/2 function in first/1 argument args: - 'first(range(1; 200) | . as $x | select(all(range(1; 7); $x % . == 0)))' input: 'null' expected: | 60 - name: limit/2 function args: - -c - '[limit(0,3,13; range(infinite))]' input: 'null' expected: | [0,1,2,0,1,2,3,4,5,6,7,8,9,10,11,12] - name: all/0, any/0 functions args: - -c - '[all, any]' input: | [] [false, false] [false, true] [true, true] expected: | [true,false] [false,false] [false,true] [true,true] - name: all/1, any/1 functions args: - -c - '[all(not), any(not)]' input: | [] [false, false] [false, true] [true, true] expected: | [true,false] [true,true] [false,true] [false,false] - name: all/2, any/2 functions args: - -c - '[all(range(.); not), any(range(.); not)]' input: '0 1' expected: | [true,false] [false,false] - name: isempty function args: - -c - '[isempty(empty), isempty(range(infinite)), isempty(1,error("foo"))]' input: 'null' expected: | [true,false,false] - name: isempty function nested args: - 'isempty(isempty(empty))' input: 'null' expected: | false - name: first/1 in isempty function args: - 'isempty(first(null))' input: 'null' expected: | false - name: trigonometric functions args: - -c - 'map(sin),map(cos),map(tan) | map(. * 1000000000 | floor / 1000000000)' input: '[0, 1, 2]' expected: | [0,0.841470984,0.909297426] [1,0.540302305,-0.416146837] [0,1.557407724,-2.185039864] - name: inverse trigonometric functions args: - -c - 'map(asin),map(acos),map(atan) | map(. * 1000000000 | floor / 1000000000)' input: '[0, 1, 2]' expected: | [0,1.570796326,null] [1.570796326,0,null] [0,0.785398163,1.107148717] - name: hyperbolic functions args: - -c - 'map(sinh),map(cosh),map(tanh) | map(. * 1000000000 | floor / 1000000000)' input: '[0, 1, 2]' expected: | [0,1.175201193,3.626860407] [1,1.543080634,3.762195691] [0,0.761594155,0.96402758] - name: inverse hyperbolic functions args: - -c - 'map(asinh),map(acosh),map(atanh) | map(. * 1000000000 | floor / 1000000000)' input: '[0, 1, 2]' expected: | [0,0.881373587,1.443635475] [null,0,1.316957896] [0,1.7976931348623157e+308,null] - name: floor, round, nearbyint, rint, ceil, trunc, fabs, sqrt and cbrt functions args: - -c - 'range(.) | sin | (fabs,sqrt,cbrt,sin) * 100000 | [(floor,round,nearbyint,rint,ceil,trunc) / 100000]' input: '5' expected: | [0,0,0,0,0,0] [0,0,0,0,0,0] [0,0,0,0,0,0] [0,0,0,0,0,0] [0.84147,0.84147,0.84147,0.84147,0.84148,0.84147] [0.91731,0.91732,0.91732,0.91732,0.91732,0.91731] [0.94408,0.94409,0.94409,0.94409,0.94409,0.94408] [0.74562,0.74562,0.74562,0.74562,0.74563,0.74562] [0.90929,0.9093,0.9093,0.9093,0.9093,0.90929] [0.95357,0.95357,0.95357,0.95357,0.95358,0.95357] [0.9688,0.9688,0.9688,0.9688,0.96881,0.9688] [0.78907,0.78907,0.78907,0.78907,0.78908,0.78907] [0.14112,0.14112,0.14112,0.14112,0.14113,0.14112] [0.37565,0.37566,0.37566,0.37566,0.37566,0.37565] [0.52063,0.52063,0.52063,0.52063,0.52064,0.52063] [0.14065,0.14065,0.14065,0.14065,0.14066,0.14065] [0.7568,0.7568,0.7568,0.7568,0.75681,0.7568] [null,null,null,null,null,null] [-0.9113,-0.9113,-0.9113,-0.9113,-0.91129,-0.91129] [-0.68661,-0.6866,-0.6866,-0.6866,-0.6866,-0.6866] - name: significand function args: - -c - '(range(10)|tan),0/0 | significand | . * 100000 | floor / 100000' input: '0' expected: | 0 1.5574 -1.09252 -1.14038 1.15782 -1.69026 -1.16403 1.74289 -1.69993 -1.80927 null - name: exp, exp10, exp2, expm1, pow10 functions args: - -c - '[range(.)] | map(exp),map(exp10),map(exp2),map(expm1),map(pow10) | map(. * 100000 | floor / 100000)' input: '10' expected: | [1,2.71828,7.38905,20.08553,54.59815,148.41315,403.42879,1096.63315,2980.95798,8103.08392] [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000] [1,2,4,8,16,32,64,128,256,512] [0,1.71828,6.38905,19.08553,53.59815,147.41315,402.42879,1095.63315,2979.95798,8102.08392] [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000] - name: frexp, modf function args: - -c - '[range(.) | tan] | map(frexp),map(modf) | map(map(. * 100000 | floor / 100000))' input: '10' expected: | [[0,0],[0.7787,1],[-0.54626,2],[-0.57019,-2],[0.57891,1],[-0.84513,2],[-0.58202,-1],[0.87144,0],[-0.84997,3],[-0.90464,-1]] [[0,0],[0.5574,1],[-0.18504,-2],[-0.14255,-0],[0.15782,1],[-0.38052,-3],[-0.29101,-0],[0.87144,0],[-0.79972,-6],[-0.45232,-0]] - name: log, log10, log1p, log2, logb functions args: - -c - '[range(.) | tan] | map(log),map(log10),map(log1p),map(log2),map(logb) | map(. * 100000 | floor / 100000)' input: '10' expected: | [-1.7976931348623157e+308,0.44302,null,null,0.14654,null,null,-0.1376,null,null] [-1.7976931348623157e+308,0.1924,null,null,0.06364,null,null,-0.05976,null,null] [0,0.93899,null,-0.15379,0.76909,null,-0.34391,0.62671,null,-0.60206] [-1.7976931348623157e+308,0.63914,null,null,0.21141,null,null,-0.19852,null,null] [-1.7976931348623157e+308,0,1,-3,0,1,-2,-1,2,-2] - name: gamma, tgamma, lgamma functions args: - -c - '[range(.) | tan] | map(gamma),map(tgamma),map(lgamma) | map(. * 100000 | floor / 100000)' input: '10' expected: | [1.7976931348623157e+308,0.88943,-2.39565,-7.75522,0.93049,0.34209,-4.41241,1.09278,-0.00159,-3.58744] [1.7976931348623157e+308,0.88943,-2.39565,-7.75522,0.93049,0.34209,-4.41241,1.09278,-0.00159,-3.58744] [1.7976931348623157e+308,-0.11718,0.87365,2.04836,-0.07204,-1.07268,1.48441,0.08872,-6.44927,1.27743] - name: erf, erfc, j0, j1, y0, y1 functions args: - -c - '[range(.) | tan] | map(erf),map(erfc),map(j0),map(j1),map(y0),map(y1) | map(. * 100000 | floor / 100000)' input: '10' expected: | [0,0.97237,-0.998,-0.15977,0.89845,-1,-0.31933,0.7822,-1,-0.47762] [1,0.02762,1.99799,1.15976,0.10154,1.99999,1.31932,0.21779,2,1.47761] [1,0.47958,0.11869,0.99492,0.69191,-0.36073,0.97894,0.81896,0.29307,0.9495] [0,0.56529,-0.55806,-0.0711,0.48717,-0.18735,-0.14397,0.39565,0.0653,-0.22043] [-1.7976931348623157e+308,0.40504,null,null,0.20121,null,null,-0.01971,null,null] [-1.7976931348623157e+308,-0.37491,null,null,-0.653,null,null,-0.90154,null,null] - name: 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 functions args: - -c - '[range(-.;.)] | [atan2(.[]; .[])], [copysign(.[]; .[])], [drem(.[]; .[])], [fdim(.[]; .[])], [fmax(.[]; .[])], [fmin(.[]; .[])], [fmod(.[]; .[])], [hypot(.[]; .[])], [jn(.[]; .[])], [ldexp(.[]; .[])], [nextafter(.[]; .[])], [nexttoward(.[]; .[])], [remainder(.[]; .[])], [scalb(.[]; .[])], [scalbln(.[]; .[])], [pow(.[]; .[])], [yn(.[]; .[])] | map(. * 10000 | round | . / 10000)' input: '3' expected: | [-2.3562,-2.5536,-2.8198,3.1416,2.8198,2.5536,-2.1588,-2.3562,-2.6779,3.1416,2.6779,2.3562,-1.8925,-2.0344,-2.3562,3.1416,2.3562,2.0344,-1.5708,-1.5708,-1.5708,0,1.5708,1.5708,-1.249,-1.1071,-0.7854,0,0.7854,1.1071,-0.9828,-0.7854,-0.4636,0,0.4636,0.7854] [-3,-2,-1,-0,-1,-2,-3,-2,-1,-0,-1,-2,-3,-2,-1,-0,-1,-2,3,2,1,0,1,2,3,2,1,0,1,2,3,2,1,0,1,2] [-0,1,-1,0,1,-1,1,-0,-1,0,1,0,-0,-0,-0,0,0,0,null,null,null,null,null,null,-0,-0,-0,0,0,0,1,-0,-1,0,1,0] [0,1,2,3,4,5,0,0,1,2,3,4,0,0,0,1,2,3,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0] [-3,-2,-1,0,1,2,-2,-2,-1,0,1,2,-1,-1,-1,0,1,2,0,0,0,0,1,2,1,1,1,1,1,2,2,2,2,2,2,2] [-3,-3,-3,-3,-3,-3,-3,-2,-2,-2,-2,-2,-3,-2,-1,-1,-1,-1,-3,-2,-1,0,0,0,-3,-2,-1,0,1,1,-3,-2,-1,0,1,2] [-0,-2,-1,0,1,2,-1,-0,-1,0,1,0,-0,-0,-0,0,0,0,null,null,null,null,null,null,-0,-0,-0,0,0,0,-1,-0,-1,0,1,0] [4.2426,3.6056,3.1623,3,3.1623,3.6056,3.6056,2.8284,2.2361,2,2.2361,2.8284,3.1623,2.2361,1.4142,1,1.4142,2.2361,3,2,1,0,1,2,3.1623,2.2361,1.4142,1,1.4142,2.2361,3.6056,2.8284,2.2361,2,2.2361,2.8284] [0.3091,0.4861,0.3391,-0.2601,-0.3391,0.4861,0.1289,0.3528,0.5767,0.2239,-0.5767,0.3528,0.0196,0.1149,0.4401,0.7652,-0.4401,0.1149,0,0,0,1,0,0,-0.0196,0.1149,-0.4401,0.7652,0.4401,0.1149,-0.1289,0.3528,-0.5767,0.2239,0.5767,0.3528] [-0.375,-0.25,-0.125,0,0.125,0.25,-0.75,-0.5,-0.25,0,0.25,0.5,-1.5,-1,-0.5,0,0.5,1,-3,-2,-1,0,1,2,-6,-4,-2,0,2,4,-12,-8,-4,0,4,8] [-3,-2,-1,-0,1,2,-3,-2,-1,-0,1,2,-3,-2,-1,-0,1,2,-3,-2,-1,0,1,2,-3,-2,-1,0,1,2,-3,-2,-1,0,1,2] [-3,-2,-1,-0,1,2,-3,-2,-1,-0,1,2,-3,-2,-1,-0,1,2,-3,-2,-1,0,1,2,-3,-2,-1,0,1,2,-3,-2,-1,0,1,2] [-0,1,-1,0,1,-1,1,-0,-1,0,1,0,-0,-0,-0,0,0,0,null,null,null,null,null,null,-0,-0,-0,0,0,0,1,-0,-1,0,1,0] [-0.375,-0.25,-0.125,0,0.125,0.25,-0.75,-0.5,-0.25,0,0.25,0.5,-1.5,-1,-0.5,0,0.5,1,-3,-2,-1,0,1,2,-6,-4,-2,0,2,4,-12,-8,-4,0,4,8] [-0.375,-0.25,-0.125,0,0.125,0.25,-0.75,-0.5,-0.25,0,0.25,0.5,-1.5,-1,-0.5,0,0.5,1,-3,-2,-1,0,1,2,-6,-4,-2,0,2,4,-12,-8,-4,0,4,8] [-0.037,-0.125,-1,1.7976931348623157e+308,1,0.125,0.1111,0.25,1,1.7976931348623157e+308,1,0.25,-0.3333,-0.5,-1,1.7976931348623157e+308,1,0.5,1,1,1,1,1,1,-3,-2,-1,0,1,2,9,4,1,0,1,4] [null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1.7976931348623157e+308,-1.7976931348623157e+308,1.7976931348623157e+308,-1.7976931348623157e+308,-1.7976931348623157e+308,-1.7976931348623157e+308,5.8215,-1.6507,0.7812,0.0883,-0.7812,-1.6507,1.1278,-0.6174,0.107,0.5104,-0.107,-0.6174] - name: fma/3 function args: - -c - '[range(.) | . * 2 | sin] | [fma(.[]; .[]; .[])] | map(. * 100000 | floor / 100000)' input: '3' expected: | [0,0,0,0,0.82682,-0.68816,0,-0.68816,0.57275,0.90929,0.90929,0.90929,0.90929,1.73611,0.22113,0.90929,0.22113,1.48204,-0.75681,-0.75681,-0.75681,-0.75681,0.07001,-1.44497,-0.75681,-1.44497,-0.18406] - name: infinite function args: - -c - '[infinite, -infinite] | (.[], .[]+.[], .[]-.[], .[]*.[], .[]/.[], .[]%.[]%256, 1/.[], -1/.[], .[]-1e300, .[]+1e300) | [., . == infinite, . == -infinite, contains(infinite)]' input: 'null' expected: | [1.7976931348623157e+308,true,false,true] [-1.7976931348623157e+308,false,true,false] [1.7976931348623157e+308,true,false,true] [null,false,false,false] [null,false,false,false] [-1.7976931348623157e+308,false,true,false] [null,false,false,false] [-1.7976931348623157e+308,false,true,false] [1.7976931348623157e+308,true,false,true] [null,false,false,false] [1.7976931348623157e+308,true,false,true] [-1.7976931348623157e+308,false,true,false] [-1.7976931348623157e+308,false,true,false] [1.7976931348623157e+308,true,false,true] [null,false,false,false] [null,false,false,false] [null,false,false,false] [null,false,false,false] [0,false,false,false] [-1,false,false,false] [255,false,false,false] [0,false,false,false] [0,false,false,false] [-0,false,false,false] [-0,false,false,false] [0,false,false,false] [1.7976931348623157e+308,true,false,true] [-1.7976931348623157e+308,false,true,false] [1.7976931348623157e+308,true,false,true] [-1.7976931348623157e+308,false,true,false] - name: isfinite and isinfinite functions args: - -c - 'infinite | ., -., 1/., 1/(-.), nan, null, "", {}, [] | [., isfinite, isinfinite]' input: 'null' expected: | [1.7976931348623157e+308,false,true] [-1.7976931348623157e+308,false,true] [0,true,false] [-0,true,false] [null,true,false] [null,false,false] ["",false,false] [{},false,false] [[],false,false] - name: finites function args: - 'nan, infinite, -infinite | ., -., 1/. | finites' input: 'null' expected: | null null null 0 -0 - name: nan and isnan functions args: - -c - 'nan, 0/0, 0/0, infinite-infinite, infinite/infinite, 0, null | [., .+1, isnan]' input: 'null' expected: | [null,null,true] [null,null,true] [null,null,true] [null,null,true] [null,null,true] [0,1,false] [null,1,false] - name: nan and comparison args: - -c - 'nan | [.<., .>., .<0, .>0, .==., .!=.], ([., 1, ., 2, .] | [min, max], sort, group_by(.))' input: 'null' expected: | [true,false,true,false,false,true] [null,2] [null,null,null,1,2] [[null],[null],[null],[1],[2]] - name: isnormal function args: - '0, 2.2250738585072011e-308, 2.2250738585072014e-308, 1, 1.7976931348623157e+308, nan, infinite, "", [], {} | isnormal' input: 'null' expected: | false false true true true false false false false false - name: normals function args: - '0, 2.2250738585072011e-308, 2.2250738585072014e-308, 1, 1.7976931348623157e+308, nan, infinite | ., -. | normals' input: 'null' expected: | 2.2250738585072014e-308 -2.2250738585072014e-308 1 -1 1.7976931348623157e+308 -1.7976931348623157e+308 - name: normals with nextafter function args: - '0, 2.2250738585072014e-308, 1.7976931348623157e+308, nan, infinite | nextafter(.; 0, infinite) | normals' input: 'null' expected: | 2.225073858507202e-308 1.7976931348623155e+308 1.7976931348623157e+308 - name: stringify nan and infinite args: - -c - 'nan, infinite, -infinite | [., tostring]' input: 'null' expected: | [null,"null"] [1.7976931348623157e+308,"1.7976931348623157e+308"] [-1.7976931348623157e+308,"-1.7976931348623157e+308"] - name: truncate_stream function args: - -c - '[truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]])]' input: '0 1 2' expected: | [[[0],1],[[1,0],2],[[1,0]],[[1]]] [[[0],2],[[0]]] [] - name: fromstream function args: - -c - 'fromstream(0,1,2|truncate_stream([[0],1],[[1,0],2],[[1,0]],[[1]]))' input: 'null' expected: | [1,[2]] [2] - name: stream function args: - -c - 'tostream' input: '[0,[1,{"a":1},{"b":2}]]' expected: | [[0],0] [[1,0],1] [[1,1,"a"],1] [[1,1,"a"]] [[1,2,"b"],2] [[1,2,"b"]] [[1,2]] [[1]] - name: setpath function args: - -c - 'setpath([["a","b",0,1,2],[1,2,"a","b"]][]; 1,2)' input: 'null' expected: | {"a":{"b":[[null,[null,null,1]]]}} [null,[null,null,{"a":{"b":1}}]] {"a":{"b":[[null,[null,null,2]]]}} [null,[null,null,{"a":{"b":2}}]] - name: setpath function deeply args: - -c - 'setpath(["a","b",0,1]; 1,2)' input: '{"a":{"b":[]}}' expected: | {"a":{"b":[[null,1]]}} {"a":{"b":[[null,2]]}} - name: setpath function negative index args: - -c - 'setpath(-2,0,2 | [.]; 4,5)' input: '[1,2,3]' expected: | [1,4,3] [4,2,3] [1,2,4] [1,5,3] [5,2,3] [1,2,5] - name: setpath function empty path args: - -c - 'setpath([]; 10)' input: 'null' expected: | 10 - name: setpath function error args: - 'setpath([-1]; 10)' input: '[]' error: | setpath([-1]; 10) cannot be applied to []: array index should not be negative: -1 - name: setpath function error args: - 'setpath(["a","b","c"]; 10)' input: '{"a":{"b":[]}}' error: | setpath(["a","b","c"]; 10) cannot be applied to {"a":{"b":[]}}: expected an object but got: array ([]) - name: setpath function error args: - 'setpath(["a",1]; 10)' input: '{"a":{"b":[]}}' error: | setpath(["a",1]; 10) cannot be applied to {"a":{"b":[]}}: expected an array but got: object ({"b":[]}) - name: setpath function error args: - 'setpath([{}]; 10)' input: 'null' error: | setpath([{}]; 10) cannot be applied to null: expected "start" and "end" for slicing but got: object ({}) - name: setpath function error args: - 'setpath([{start:0}]; 10)' input: 'null' error: | setpath([{"start":0}]; 10) cannot be applied to null: expected "start" and "end" for slicing but got: object ({"start":0}) - name: setpath function error args: - 'setpath([[]]; 10)' input: '[]' error: | setpath([[]]; 10) cannot be applied to []: expected a number for indexing an array but got: array ([]) - name: setpath function error args: - 'setpath([{start:0,end:1}]; 10)' input: '[]' error: | setpath([{"end":1,"start":0}]; 10) cannot be applied to []: expected an array but got: number (10) - name: delpaths function args: - -c - 'delpaths([["a",1,"b",1]])' input: '{"a":[{},{"b":[1,2,3]}]}' expected: | {"a":[{},{"b":[1,3]}]} - name: delpaths function against array args: - -c - 'delpaths([[2],[6],[4]])' input: '[1,2,3,4,5]' expected: | [1,2,4] - name: delpaths function not found key args: - -c - 'delpaths([["a","b","c"]])' input: '{}' expected: | {} - name: delpaths function out of index args: - -c - 'delpaths([[2],[-10],[10],[-3]])' input: '[1,2,3,4,5]' expected: | [1,2,4,5] - name: delpaths function with overlapping paths args: - -c - 'delpaths([["x"],["x",0,"y"]], [["x",0],["x",0,"y"]], [["x"],["x",{"start":0}]])' input: '{"x":[{"y":0}]}' expected: | {} {"x":[]} {} - name: delpaths function error args: - 'delpaths([{}])' input: '[]' error: | delpaths([{}]) cannot be applied to []: expected an array but got: object ({}) - name: delpaths function error args: - 'delpaths([["x"]])' input: '[]' error: | delpaths([["x"]]) cannot be applied to []: expected an object but got: array ([]) - name: getpath function args: - -c - 'getpath(["a",1,"b",2])' input: '{"a":[{},{"b":[1,2,3]}]}' expected: | 3 - name: getpath function empty path args: - -c - 'getpath([])' input: '{"a":[{},{"b":[1,2,3]}]}' expected: | {"a":[{},{"b":[1,2,3]}]} - name: getpath function error args: - 'getpath(["a",1,"b","c"])' input: '{"a":[{},{"b":[1,2,3,4,5,6,7]}]}' error: | getpath(["a",1,"b","c"]) cannot be applied to {"a":[{},{"b":[1,2,3,4,5, ...}: expected an object but got: array ([1,2,3,4,5,6,7]) - name: setpath, delpaths, getpath functions args: - -c - '["foo",1] as $p | getpath($p), setpath($p; 2), delpaths([$p]), .' input: | {"foo":[0,1]} {"bar":false} expected: | 1 {"foo":[0,2]} {"foo":[0]} {"foo":[0,1]} null {"bar":false,"foo":[null,2]} {"bar":false} {"bar":false} - name: setpath to getpath args: - -c - '.[] | try (getpath(["a",0,"b"]) |= 5) catch .' input: '[null,{"b":0},{"a":0},{"a":null},{"a":[0,1]},{"a":{"b":1}},{"a":[{}]},{"a":[{"c":3}]}]' expected: | {"a":[{"b":5}]} {"a":[{"b":5}],"b":0} "getpath([\"a\",0,\"b\"]) cannot be applied to: object ({\"a\":0})" {"a":[{"b":5}]} "getpath([\"a\",0,\"b\"]) cannot be applied to: object ({\"a\":[0,1]})" "getpath([\"a\",0,\"b\"]) cannot be applied to {\"a\":{\"b\":1}}: expected an array but got: object ({\"b\":1})" {"a":[{"b":5}]} {"a":[{"b":5,"c":3}]} - name: map_values function args: - -c - 'map_values(.*2)' input: '[1,2,3]' expected: | [2,4,6] - name: map_values function with optional operator args: - -c - 'map_values(tonumber? // .)' input: '{"x": "1", "y": "1.5", "z": "z"}' expected: | {"x":1,"y":1.5,"z":"z"} - name: del function args: - -c - 'del(.), del(empty), del((.foo,.bar,.baz) | .[2,3,0]), del(.foo[0], .bar[0], .foo, .baz.bar[0].x), del(.foo[1:][:3][1:], .bar[:2][1:])' input: '{"foo": [0,1,2,3,4], "bar": [0,1]}' expected: | null {"bar":[0,1],"foo":[0,1,2,3,4]} {"bar":[1],"foo":[1,4]} {"bar":[1]} {"bar":[0],"foo":[0,1,4]} - name: del function with array slicing args: - -c - 'del(.[1], .[-6], .[2], .[-3:9])' input: '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]' expected: | [0,3,5,6,9] - name: del function against large array with select args: - '[range(.)] | (5,7) as $x | del(.[] | select(. % $x == 0)) | add' input: '50000' expected: | 1000000000 1071421429 - name: del function against large array with slicing args: - '[range(.)] | del(.[range(length) | {start:.,end:(.+1)}])' input: '50000' expected: | [] - name: assignment operator against object args: - -c - '.foo.bar = 1' input: 'null' expected: | {"foo":{"bar":1}} - name: assignment operator against array args: - -c - '.[-1,0] = 3' input: '[0,1,2]' expected: | [3,1,3] - name: assignment operator against number args: - '. = 1' input: '0' expected: | 1 - name: assignment operator against number error args: - '.foo = 1' input: '0' error: | expected an object but got: number (0) - name: assignment operator against object with multiple paths args: - -c - '(.foo,.bar,.baz) = 1' input: 'null' expected: | {"bar":1,"baz":1,"foo":1} - name: assignment operator against array with slicing args: - -c - '.[2:4] = ([], [0], [4,3,2]), .[1:-1] = [3,2], .[2:0] = [.[]], .[10:] = [1], .[-20:-10] = [1], .[0:5] = [9], .[1:][1:] = [9], .[1:][:3] = [6,7,8], .[:4][1:][:3] = [9], .[1:][1:-2] = [9], .[:4][-3:][:5] = [9], (null | .[:1][2:] = [9])' input: '[1,2,3,4,5]' expected: | [1,2,5] [1,2,0,5] [1,2,4,3,2,5] [1,3,2,5] [1,2,1,2,3,4,5,3,4,5] [1,2,3,4,5,1] [1,1,2,3,4,5] [9] [1,2,9] [1,6,7,8,5] [1,9,5] [1,2,9,4,5] [1,9,5] [9] - name: assignment operator with object and array indexing args: - -c - '.foo.[0].bar.[1] = 1' input: 'null' expected: | {"foo":[{"bar":[null,1]}]} - name: assignment operator against array with suffix array indexing args: - -c - '.[2][2] = 1' input: '[1]' expected: | [1,null,[null,null,1]] - name: assignment operator against array with suffix object indexing args: - -c - '.[2].foo = 1' input: '[1]' expected: | [1,null,{"foo":1}] - name: assignment operator against object with multiple suffix indexing args: - -c - '.foo[2].bar = 2' input: '{"foo":[1], "bar":3}' expected: | {"bar":3,"foo":[1,null,{"bar":2}]} - name: assignment operator with query and suffix object indexing args: - -c - '(.foo).bar = 1' input: 'null' expected: | {"foo":{"bar":1}} - name: assignment operator with object indexing and iterator suffix args: - -c - '.foo[].bar = 1' input: '{"foo":[null,null]}' expected: | {"foo":[{"bar":1},{"bar":1}]} - name: assignment operator with overlapping paths args: - -c - '[(.x,.x.y,.x.y.z) = {}, (.x.y.z,.x.y,.x) = {}]' input: 'null' expected: | [{"x":{"y":{"z":{}}}},{"x":{}}] - name: assignment operator with query value args: - -c - '.foo = .bar' input: '{"bar":42}' expected: | {"bar":42,"foo":42} - name: assignment operator with multiple paths and query values args: - -c - '(.foo,.bar,.baz) = .bar[]' input: '{"bar":[0,1,2]}' expected: | {"bar":0,"baz":0,"foo":0} {"bar":1,"baz":1,"foo":1} {"bar":2,"baz":2,"foo":2} - name: assignment operator with overlapping paths and multiple values args: - -c - '.. = ..' input: '{"x":{"y":{}}} [[[]]]' expected: | {"x":{"x":{"y":{}},"y":{"x":{"y":{}}}}} {"x":{"y":{"y":{}}},"y":{}} {"x":{"y":{}}} [[[[[]]]]] [[[[]]]] [[[]]] - name: assignment operator with overlapping and slicing paths args: - -c - '..[:0] = ..' input: '[[[]]]' expected: | [[[[[]],[]],[]],[[]]] [[[[]]],[[]]] [[[]]] - name: assignment operator array index negative error args: - '.[-1,0] = 1' input: '[]' error: | setpath([-1]; 1) cannot be applied to []: array index should not be negative: -1 - name: assignment operator array index limit error args: - '.[200000000] = 1' input: '[]' error: | array index too large: 200000000 - name: assignment operator with error function args: - -c - 'error = 1' input: 'null' expected: | null - name: assignment operator with error function and valid paths args: - -c - '(.x,error,.y) = 1' input: 'null' expected: | {"x":1,"y":1} - name: assignment operator with select args: - -c - '(.[] | select(.k == "b") | .v) = 0' input: '[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "c", "v": 3}]' expected: | [{"k":"a","v":1},{"k":"b","v":0},{"k":"c","v":3}] - name: assignment operator with fork args: - -c - '[range(3)] | .[] = 0 | [.[] = 1, .[] = 2]' input: 'null' expected: | [[1,1,1],[2,2,2]] - name: assignment operator against large array args: - '[range(.)] | .[] = 1 | add' input: '50000' expected: | 50000 - name: assignment operator against large array with select args: - '[range(.)] | (.[] | select(. % 2 > 0)) = 1 | add' input: '50000' expected: | 625000000 - name: assignment operator against large array with range args: - '[range(.)] | .[range(length)] = 1 | add' input: '50000' expected: | 50000 - name: assignment operator against large array with slicing args: - '[range(.)] | .[range(length) | {start:.,end:(.+1)}] = [1] | add' input: '50000' expected: | 50000 - name: assignment operator against large object args: - '[{key:range(.)|tostring}] | from_entries | .[] = 1 | add' input: '50000' expected: | 50000 - name: assignment operator constructing large array args: - '.[range(50000)] = 1 | add' input: 'null' expected: | 50000 - name: update-assignment operator args: - -c - '.foo |= .+1' input: '{"foo": 1}' expected: | {"foo":2} - name: update-assignment operator deeply args: - -c - '.[0].a |= {"old":., "new":(.+1)}' input: '[{"a":1,"b":2}]' expected: | [{"a":{"new":2,"old":1},"b":2}] - name: update-assignment operator in function args: - -c - 'def inc(x): x |= .+1; inc(.[].a)' input: '[{"a":1,"b":2},{"a":2,"b":4},{"a":7,"b":8}]' expected: | [{"a":2,"b":2},{"a":3,"b":4},{"a":8,"b":8}] - name: update-assignment operator with empty args: - -c - '.foo |= empty' input: '{"foo": 42, "bar": 48}' expected: | {"bar":48} - name: update-assignment operator with repeat args: - -c - '.foo |= repeat(.)' input: '{"foo": 42, "bar": 48}' expected: | {"bar":48,"foo":42} - name: update-assignment operator with same paths args: - -c - '(.foo,.foo,.foo) |= .+1' input: '{"foo": 0}' expected: | {"foo":3} - name: update-assignment operator against array with empty function args: - -c - '(.[] | select(. % 2 > 0)) |= empty' input: '[1,2,3,4,5]' expected: | [2,4] - name: update-assignment operator against array with conditional empty function args: - -c - '[range(.)] | .[] |= if . % 2 == 0 then empty else [., -.] end' input: '10' expected: | [[1,-1],[3,-3],[5,-5],[7,-7],[9,-9]] - name: update-assignment operator with fork args: - -c - '[range(3)] | .[] |= 0 | [.[] |= 1, .[] |= 2]' input: 'null' expected: | [[1,1,1],[2,2,2]] - name: update-assignment operator against large array args: - '[range(.)] | .[] |= . * 2 | add' input: '50000' expected: | 2499950000 - name: update-assignment operator against large array with select args: - '[range(.)] | (.[] | select(. % 2 > 0)) |= (. * 2) | add' input: '50000' expected: | 1874975000 - name: update-assignment operator against large object args: - '[range(.) | {key:tostring,value:.}] | from_entries | .[] |= . * 2 | add' input: '50000' expected: | 2499950000 - name: update-assignment operator against array with overlapping paths args: - -c - '(.[1:3][],.[2:4][],.[2]) |= .+1' input: '[1,2,3,4,5]' expected: | [1,3,6,5,5] - name: update-assignment operator with optional operator args: - -c - '.foo |= .?' input: '{"foo": 42}' expected: | {"foo":42} - name: update-assignment operator with infinite range args: - -c - '[range(.)] | .[] |= range(infinite)' input: '5' expected: | [0,0,0,0,0] - name: update-assignment operator with try catch args: - -c - '. |= try . catch .' input: '1' expected: | 1 - name: update-assignment operator with alternative operator args: - -c - '.foo |= (fromjson? // "x")' input: | {"foo": 42} {"foo": "42"} {"foo": "a"} {"foo": "0 a"} {"foo": "true"} {"foo": "[{}]"} {"foo": "[][]"} expected: | {"foo":"x"} {"foo":42} {"foo":"x"} {"foo":"x"} {"foo":true} {"foo":[{}]} {"foo":"x"} - name: update operators args: - -c - '.[] += 2, .[] -= 2, .[] *= 2, .[] /= 2, .[] %= 2' input: '[1,2,3]' expected: | [3,4,5] [-1,0,1] [2,4,6] [0.5,1,1.5] [1,0,1] - name: update operator with query in right hand side args: - -c - '.foo += .foo, .bar //= .foo, .[] /= .foo, .[] = .foo' input: '{"foo":2}' expected: | {"foo":4} {"bar":2,"foo":2} {"foo":1} {"foo":2} - name: update operators associativity error args: - '. += 1 -= 2' input: '1' error: | invalid query: . += 1 -= 2 . += 1 -= 2 ^ unexpected token "-=" exit_code: 3 - name: error/0 function args: - '.foo[] | error' input: '{ "foo": ["error message", "bar"] }' error: | error: error message exit_code: 5 - name: error/0 function with null args: - 'error' input: 'null' - name: error/0 function on string and try catch args: - 'try error catch .' input: '"foo"' expected: | "foo" - name: error/0 function on values and try catch args: - 'null,1,[],{x:42} | try error catch .' input: '0' expected: | 1 [] { "x": 42 } - name: error/1 function args: - '.foo | error("error message")' input: '{ "foo": 1 }' error: | error: error message exit_code: 5 - name: error/1 function and try catch args: - 'try error({ x: 42 }) catch .' input: '0' expected: | { "x": 42 } - name: error/1 function with null args: - 'error(null)' input: '1' - name: path function identity args: - 'path(.)' input: '{}' expected: | [] - name: path function deep args: - -c - 'path(.a|.[1,2:3,4].b[5,6])' input: '{}' expected: | ["a",{"end":3,"start":1},"b",5] ["a",{"end":4,"start":1},"b",5] ["a",{"end":3,"start":2},"b",5] ["a",{"end":4,"start":2},"b",5] ["a",{"end":3,"start":1},"b",6] ["a",{"end":4,"start":1},"b",6] ["a",{"end":3,"start":2},"b",6] ["a",{"end":4,"start":2},"b",6] - name: path function select args: - -c - '[path(.[] | select(. > 3))]' input: '[1,3,5,7,3,8,9,1]' expected: | [[2],[3],[5],[6]] - name: path function recurse args: - -c - '[path(..)]' input: '{"a":{"b":[0,1,2]},"c":[3,4]}' expected: | [[],["a"],["a","b"],["a","b",0],["a","b",1],["a","b",2],["c"],["c",0],["c",1]] - name: path function and pattern args: - -c - '(.a as $x | (.b,.c)) = "b"' input: '{"a":null,"b":null}' expected: | {"a":null,"b":"b","c":"b"} - name: path function result args: - -c - '.a[path(.b)[0]]' input: '{"a":{"b":0}}' expected: | 0 - name: path function with select args: - -c - 'path(.[] | select(.id | startswith("x")) | .id)' input: '[{"id": "xy"}, {"id": "yx"}]' expected: | [0,"id"] - name: path function with getpath args: - -c - '[path(getpath([[0]] | .[0],.[]))]' input: 'null' expected: | [[0],[0]] - name: path function against nan args: - -c - '[nan,{x:nan}] | path(.[1].x)' input: 'null' expected: | [1,"x"] - name: path function nested args: - -c - 'path(.a[path(.b[.a:.b])[0]])' input: '{"a":{"b":0}}' expected: | ["a","b"] - name: path function error with arithmetic operator args: - 'path(. + 1)' input: 'null' error: | invalid path against: number (1) - name: path function error with object indexing args: - 'path({}.x)' input: '{}' error: | invalid path against: object ({}) - name: path function error with array indexing args: - 'path([0][0 + 0])' input: '[0]' error: | invalid path against: array ([0]) - name: path function error with array slicing args: - 'path([0][0:1])' input: '[0]' error: | invalid path against: array ([0]) - name: path function error with getpath function args: - 'path({} | getpath([]))' input: '{}' error: | invalid path against: object ({}) - name: path function with binding array slicing args: - '.[:1] as $x | path($x.[0])' input: '[0]' expected: | [ 0 ] - name: path function error with binding array slicing args: - '.[:0] as $x | path($x.[0])' input: '[0]' error: | invalid path against: array ([]) - name: path function with function declaration args: - 'def f($x): .y; path(f(.x))' input: 'null' expected: | [ "y" ] - name: path function with assignment operator args: - 'path(. = .x)' input: 'null' expected: | [] - name: path function with error function args: - '[path(error)]' input: 'null' expected: | [] - name: path function with error function and valid paths args: - -c - '[path(.x,error,.y)]' input: 'null' expected: | [["x"],["y"]] - name: path function with error message args: - '[path("x"|error)]' input: 'null' error: | error: x - name: path function iterate error of array args: - '[.[]][].a = 1' input: '[{"a":0}]' error: | invalid path on iterating against: array ([{"a":0}]) - name: path function iterate error of object args: - '{}[] = 10' input: '{}' error: | invalid path on iterating against: object ({}) - name: paths/0 function args: - -c - 'paths' input: '[1,[[1,{"a":2},3],{"a":[2]}]]' expected: | [0] [1] [1,0] [1,0,0] [1,0,1] [1,0,1,"a"] [1,0,2] [1,1] [1,1,"a"] [1,1,"a",0] - name: paths/1 function args: - -c - 'paths(. > 2)' input: '[1,[[1,{"a":2},3],{"a":[2]}]]' expected: | [1] [1,0] [1,0,1] [1,0,2] [1,1] [1,1,"a"] - name: bsearch function args: - 'bsearch(0,1,2,3,4,5,6)' input: '[1,3,5]' expected: | -1 0 -2 1 -3 2 -4 - name: gmtime, localtime functions args: - -c - 'gmtime, localtime' input: '1500000000 1567890123.456 1600000000.111' expected: | # tested with UTC-7 [2017,6,14,2,40,0,5,194] [2017,6,13,19,40,0,4,193] [2019,8,7,21,2,3.4560000889999998,6,249] [2019,8,7,14,2,3.4560000889999998,6,249] [2020,8,13,12,26,40.111000061,0,256] [2020,8,13,5,26,40.111000061,0,256] - name: mktime, strftime, strflocaltime, todate functions args: - -c - 'mktime, ((.,mktime) | strftime("%Y-%m-%dT%H:%M:%SZ")), ((.,mktime) | strflocaltime("%Y-%m-%dT%H:%M:%S%z")), todate' input: | [2017,6,14,2,40,0,5,194] [2019,8,7,21,2,3.4560000889999998,6,249] [2020,8,13,12,26,40.111000061,0,256] expected: | 1500000000 "2017-07-14T02:40:00Z" "2017-07-14T02:40:00Z" "2017-07-14T02:40:00-0700" "2017-07-13T19:40:00-0700" "2017-07-14T02:40:00Z" 1567890123.456 "2019-09-07T21:02:03Z" "2019-09-07T21:02:03Z" "2019-09-07T21:02:03-0700" "2019-09-07T14:02:03-0700" "2019-09-07T21:02:03Z" 1600000000.111 "2020-09-13T12:26:40Z" "2020-09-13T12:26:40Z" "2020-09-13T12:26:40-0700" "2020-09-13T05:26:40-0700" "2020-09-13T12:26:40Z" - name: strptime, fromdate functions args: - -c - 'strptime("%Y-%m-%dT%H:%M:%S%z"), fromdate' input: | "2017-07-14T02:40:00Z" "2019-09-08T06:02:03+0900" "2020-09-13T07:26:40-05:00" expected: | [2017,6,14,2,40,0,5,194] 1500000000 [2019,8,7,21,2,3,6,249] 1567890123 [2020,8,13,12,26,40,0,256] 1600000000 - name: strftime, strptime functions args: - 'strptime("%c") | strftime("%c")' input: | "Thu Jul 23 20:41:06 2020" expected: | "Thu Jul 23 20:41:06 2020" - name: now function args: - -c - 'now | type' input: 'null' expected: | "number" - name: debug function args: - 'recurse(debug|.[]?)' input: | [{}] [1,{"a":2}] expected: | [ {} ] {} [ 1, { "a": 2 } ] 1 { "a": 2 } 2 error: | ["DEBUG:",[{}]] ["DEBUG:",{}] ["DEBUG:",[1,{"a":2}]] ["DEBUG:",1] ["DEBUG:",{"a":2}] ["DEBUG:",2] - name: stderr function args: - 'recurse(stderr|.[]?)' input: | [{}] [1,{"a":2}] "DEBUG:" expected: | [ {} ] {} [ 1, { "a": 2 } ] 1 { "a": 2 } 2 "DEBUG:" error: '[{}]{}[1,{"a":2}]1{"a":2}2"DEBUG:"' - name: debug and stderr in builtins args: - 'builtins[] | select(test("debug|stderr"))' input: 'null' expected: | "debug/0" "stderr/0" - name: input_filename function with stdin input args: - 'input_filename' input: '0 1 2' expected: | "" "" "" - name: input_filename function with null input option args: - -n - 'input_filename' input: '0' expected: | null - name: input_filename function with raw string input option args: - --raw-input - 'input_filename' input: | 1 2 3 expected: | "" "" "" - name: input_filename function with slurp option args: - --slurp - 'input_filename' input: | 1 2 3 expected: | "" - name: input_filename function with input and slurp option args: - --raw-input - --slurp - 'input_filename' input: | 1 2 3 expected: | "" - name: input_filename function with json file arguments args: - 'input_filename' - 'testdata/1.json' - 'testdata/2.json' input: '0' expected: | "testdata/1.json" "testdata/2.json" - name: input_filename function with null input option and json file arguments args: - -n - '., inputs, (try input catch .) | input_filename' - 'testdata/1.json' - 'testdata/2.json' input: '0' expected: | null "testdata/1.json" "testdata/2.json" null - name: input_filename function with hyphen for stdin args: - 'input_filename' - '-' - 'testdata/1.json' - '-' - 'testdata/2.json' - '-' input: '0' expected: | "" "testdata/1.json" "testdata/2.json" - name: input_filename function with yaml input option args: - --yaml-input - 'input_filename' input: '0' expected: | "" - name: input_filename function with yaml file arguments args: - --yaml-input - 'input_filename' - 'testdata/1.yaml' input: '0' expected: | "testdata/1.yaml" "testdata/1.yaml" "testdata/1.yaml" - name: input_filename in builtins args: - 'builtins[] | select(test("input_filename"))' input: 'null' expected: | "input_filename/0" - name: halt function args: - 'def f($n): if $n > 0 then $n, f($n - 1) else halt end; f(.), .' input: '3' expected: | 3 2 1 - name: halt function and raw string input option args: - -R - 'halt' input: | 1 2 3 - name: halt function and json file arguments args: - 'halt' - 'testdata/1.json' - 'testdata/2.json' - name: halt_error/0 function args: - 'halt_error' input: '1' error: | 1 exit_code: 5 - name: halt_error/0 function on nan args: - '0/0 | halt_error' input: 'null' error: | null exit_code: 5 - name: halt_error/0 function and try catch args: - 'range(5) | try halt_error catch .' input: 'null' error: | 0 exit_code: 5 - name: halt_error/0 function with null input and try catch args: - 'try halt_error catch .' input: 'null' exit_code: 5 - name: halt_error/0 function with multiple input values args: - 'halt_error' input: | 1 2 3 error: | 1 2 3 exit_code: 5 - name: halt_error/0 function and raw string input option args: - -R - 'halt_error' input: | 1 2 3 error: | 123 exit_code: 5 - name: halt_error/1 function args: - 'halt_error(42)' input: 'null' exit_code: 42 - name: builtins function args: - 'builtins | length > 100' input: 'null' expected: | true - name: env function args: - 'env|length > 0' input: 'null' expected: | true - name: env in function args: - 'def f: env; f|length > 0' input: 'null' expected: | true - name: env in builtins args: - 'builtins[] | select(test("env"))' input: 'null' expected: | "env/0" - name: $ENV variable args: - '[$ENV|length > 0, 1 as $ENV | $ENV]' input: 'null' expected: | [ true, 1 ] - name: env arguments count error args: - 'env(0)' input: 'null' error: | function not defined: env/1 exit_code: 3 - name: input function args: - -n - 'input, input' input: '1' expected: | 1 error: | break - name: input function with binomial operator args: - -n - 'input + input[0]' input: | [1] 2 expected: | 3 - name: input function in function args: - -n - 'def f: [def f: input; range(3) | f]; f' input: '1 2 3' expected: | [ 1, 2, 3 ] - name: input function with files args: - -n - -c - 'range(5) | input' - 'testdata/1.json' - 'testdata/7.json' - 'testdata/1.json' expected: | {"foo":10} 1 2 {"foo":42} {"foo":10} - name: input function without null input option args: - '. + input' input: '1 2' expected: | 3 - name: input function with stream option args: - -n - -c - --stream - 'range(5) | input' input: '{"a":[1,2,3]}' expected: | [["a",0],1] [["a",1],2] [["a",2],3] [["a",2]] [["a"]] - name: inputs function args: - -n - 'reduce inputs as $x (0; . + $x)' input: '1 2 3 4 5' expected: | 15 - name: inputs function with stream option args: - -n - -c - --stream - 'range(5) | input' input: '{"a":[1,2,3]}' expected: | [["a",0],1] [["a",1],2] [["a",2],3] [["a",2]] [["a"]] - name: inputs function with stream option and truncate_stream function args: - -n - --stream - 'fromstream(1 | truncate_stream(inputs) | select(length > 1) | .[0] |= .[1:])' input: '{"a":[1,true,null,"x"]}' expected: | 1 true null "x" - name: inputs function with stream option args: - -n - -c - --stream - '[inputs][]' input: '{"a":1} {"b":2} {"c":3}' expected: | [["a"],1] [["a"]] [["b"],2] [["b"]] [["c"],3] [["c"]] - name: inputs function with stream and slurp option args: - -n - -c - --stream - --slurp - 'inputs[]' input: '{"a":1} {"b":2} {"c":3}' expected: | [["a"],1] [["a"]] [["b"],2] [["b"]] [["c"],3] [["c"]] - name: inputs function with raw input option args: - -n - -R - 'inputs' input: | a b c d 1 expected: | "a b" "c" "d" "1" - name: inputs function with raw input and slurp option args: - -n - -R - -s - 'inputs' input: | a b c expected: | "a b\nc\n" - name: inputs function with yaml input option args: - -n - --yaml-input - 'inputs' input: | foo: 1 --- bar: 2 --- baz: 3 expected: | { "foo": 1 } { "bar": 2 } { "baz": 3 } - name: inputs function with yaml input, slurp option args: - -n - --slurp - --yaml-input - 'inputs' input: | foo: 1 --- bar: 2 --- baz: 3 expected: | [ { "foo": 1 }, { "bar": 2 }, { "baz": 3 } ] - name: match function args: - -c - '[match("bcd")], [match("☆★"; "g")], (("","g","i","ig") as $flag | [match("bcd"; $flag)]), [match("ABC.?D.?E"; "mg")], [match("A(?B(C)(.?D))(?.?E)")], [match("b(?c)(d)"; "igm")], [match("bc(c(.*))?d")]' input: '"abcde☆★abcde☆★ABCDE☆ABC\nD\nE"' expected: | [{"captures":[],"length":3,"offset":1,"string":"bcd"}] [{"captures":[],"length":2,"offset":5,"string":"☆★"},{"captures":[],"length":2,"offset":12,"string":"☆★"}] [{"captures":[],"length":3,"offset":1,"string":"bcd"}] [{"captures":[],"length":3,"offset":1,"string":"bcd"},{"captures":[],"length":3,"offset":8,"string":"bcd"}] [{"captures":[],"length":3,"offset":1,"string":"bcd"}] [{"captures":[],"length":3,"offset":1,"string":"bcd"},{"captures":[],"length":3,"offset":8,"string":"bcd"},{"captures":[],"length":3,"offset":15,"string":"BCD"}] [{"captures":[],"length":5,"offset":14,"string":"ABCDE"},{"captures":[],"length":7,"offset":20,"string":"ABC\nD\nE"}] [{"captures":[{"length":3,"name":"bar","offset":15,"string":"BCD"},{"length":1,"name":null,"offset":16,"string":"C"},{"length":1,"name":null,"offset":17,"string":"D"},{"length":1,"name":"foo","offset":18,"string":"E"}],"length":5,"offset":14,"string":"ABCDE"}] [{"captures":[{"length":1,"name":"foo","offset":2,"string":"c"},{"length":1,"name":null,"offset":3,"string":"d"}],"length":3,"offset":1,"string":"bcd"},{"captures":[{"length":1,"name":"foo","offset":9,"string":"c"},{"length":1,"name":null,"offset":10,"string":"d"}],"length":3,"offset":8,"string":"bcd"},{"captures":[{"length":1,"name":"foo","offset":16,"string":"C"},{"length":1,"name":null,"offset":17,"string":"D"}],"length":3,"offset":15,"string":"BCD"}] [{"captures":[{"length":0,"name":null,"offset":-1,"string":null},{"length":0,"name":null,"offset":-1,"string":null}],"length":3,"offset":1,"string":"bcd"}] - name: match function type error args: - 'match("x"; "g")' input: '{}' error: | match("x"; "g") cannot be applied to: object ({}) - name: match function flag error args: - 'match("x"; "a")' input: '""' error: | unsupported regular expression flag: "a" - name: match function regular expression error args: - 'match("["; "g")' input: '""' error: | invalid regular expression "[": error parsing regexp: missing closing ]: `[` - name: test function args: - -c - '[test("xyz"), test("bcd"), test("efg"), test("efg"; "i"), test("☆★")]' input: '"abcde☆★abcde☆★ABCDEFG"' expected: | [false,true,false,true,true] - name: capture function args: - -c - 'capture("bcd"), capture("b(?c.?(?d..))"), [capture("(?b(?c).?(?d..))"; "gmi")]' input: '"abcdeABCDEabcde"' expected: | {} {"bar":"deA","foo":"cdeA"} [{"a":"bcdeA","bar":"deA","foo":"c"},{"a":"BCDEa","bar":"DEa","foo":"C"}] - name: scan function args: - -c - 'scan("abc"), scan("abc"; "i"), scan("(b(c*)(d*))"), scan("(b(c*)(d*))"; "i")' input: '"abcdeABCCDDEabcce"' expected: | "abc" "abc" "abc" "ABC" "abc" ["bcd","c","d"] ["bcc","cc",""] ["bcd","c","d"] ["BCCDD","CC","DD"] ["bcc","cc",""] - name: split/2 function args: - -c - 'split("a"), split("a+"), split("a+"; ""), split("a+"; "i")' input: '"abaacaAad"' expected: | ["","b","","c","A","d"] ["abaacaAad"] ["","b","c","A","d"] ["","b","c","d"] - name: splits function args: - -c - '[splits("a")], [splits("a+")], [splits("a+"; "")], [splits("a+"; "i")]' input: '"abaacaAad"' expected: | ["","b","","c","A","d"] ["","b","c","A","d"] ["","b","c","A","d"] ["","b","c","d"] - name: sub function args: - 'sub("a"; "b"), sub("aaa"; "b"), sub("a"; "b","c"), sub("a(?.)"; "\(.a)b\(.a)"; "ig"), sub("(?★)"; "\(.foo)☆\(.foo)"), sub("^"; "b")' input: '"abc☆★☆ABC"' expected: | "bbc☆★☆ABC" "abc☆★☆ABC" "bbc☆★☆ABC" "cbc☆★☆ABC" "bbbc☆★☆BbBC" "abc☆★☆★☆ABC" "babc☆★☆ABC" - name: gsub function args: - 'gsub("a"; "b"), gsub("a.c"; "b"), gsub("a"; "b","c"), gsub("a(?..)"; "\(.a+.a)"; "i"), gsub("(?★)"; "\(.foo)☆\(.foo)"), gsub("^"; "a")' input: '"abcABC☆★☆ABCabc"' expected: | "bbcABC☆★☆ABCbbc" "bABC☆★☆ABCb" "bbcABC☆★☆ABCbbc" "cbcABC☆★☆ABCbbc" "bbcABC☆★☆ABCcbc" "cbcABC☆★☆ABCcbc" "bcbcBCBC☆★☆BCBCbcbc" "abcABC☆★☆★☆ABCabc" "aabcABC☆★☆ABCabc" - name: INDEX function args: - -c - 'INDEX(range(5); .*2)' input: 'null' expected: | {"0":0,"2":1,"4":2,"6":3,"8":4} - name: JOIN function args: - -c - '[range(5)] | JOIN(map(.*3); .*2)' input: 'null' expected: | [[0,0],[1,6],[2,12],[3,null],[4,null]] - name: IN function args: - -c - '[range(5; 13)|IN(range(0; 10; 3))]' input: 'null' expected: | [false,true,false,false,true,false,false,false] - name: format function args: - 'format("html","uri","csv","tsv","sh","base64")' input: '[1,2,3]' expected: | "[1,2,3]" "%5B1%2C2%2C3%5D" "1,2,3" "1\t2\t3" "1 2 3" "WzEsMiwzXQ==" - name: format function error args: - -n - 'format("t")' error: | format not defined: @t - name: format strings @text args: - -n - '@text' expected: | "null" - name: format strings @json args: - '@json' input: '{"a":42}' expected: | "{\"a\":42}" - name: format strings @json with string interpolation args: - '@json "0\(.)1\(. + .)2\([2])3"' input: '[1]' expected: | "0[1]1[1,1]2[2]3" - name: format strings @html args: - '@html' input: | {"foo": "
&'\"
"} expected: | "{"foo":"<div>&'\\"</div>"}" - name: format strings @html with string interpolation args: - '@html "x\(.)x\(.+.)x\("<>")x"' input: | ["<>"] expected: | "x["<>"]x["<>","<>"]x<>x" - name: format strings @uri args: - '@uri' input: | [1, {"foo": "
&'\"()
"}] expected: | "%5B1%2C%7B%22foo%22%3A%22%3Cdiv%3E%26%27%5C%22%28%29%3C%2Fdiv%3E%22%7D%5D" - name: format strings @uri with string interpolation args: - '@uri "q=\(.)&q=\(. + .)"' input: | ["<>", "{&}"] expected: | "q=%5B%22%3C%3E%22%2C%22%7B%26%7D%22%5D&q=%5B%22%3C%3E%22%2C%22%7B%26%7D%22%2C%22%3C%3E%22%2C%22%7B%26%7D%22%5D" - name: format strings @urid args: - '@urid' input: | "%5B1%2C%7B%22foo%22%3A%22%3Cdiv%3E%26%27%5C%22%28%29%3C%2Fdiv%3E%22%7D%5D" expected: | "[1,{\"foo\":\"
&'\\\"()
\"}]" - name: format strings @urid error args: - '@urid' input: | "%5B1%2C%" error: | @urid cannot be applied to "%5B1%2C%": invalid URL escape "%" - name: format strings @csv args: - '@csv' input: | [1, "foo", null, "foo,\n\"bar\"\tbaz\u0000\\0"] expected: | "1,\"foo\",,\"foo,\n\"\"bar\"\"\tbaz\\0\\0\"" - name: format strings @csv with string interpolation args: - '@csv "\(.),\(. + .),\([nan])"' input: | [1, "foo", null, 2] expected: | "1,\"foo\",,2,1,\"foo\",,2,1,\"foo\",,2," - name: format strings @csv error args: - '@csv' input: 'null' error: | @csv cannot be applied to: null - name: format strings @csv row error args: - '@csv' input: | [1, "foo", null, {"foo":1}] error: | @csv cannot format an array including: object ({"foo":1}) - name: format strings @tsv args: - '@tsv' input: | [1, "foo", null, "foo,\n\"bar\"\tbaz\u0000\\0"] expected: | "1\tfoo\t\tfoo,\\n\"bar\"\\tbaz\\0\\\\0" - name: format strings @tsv with string interpolation args: - '@tsv "\(.),\(. + .),\([nan])"' input: | [1, "foo", null, 2] expected: | "1\tfoo\t\t2,1\tfoo\t\t2\t1\tfoo\t\t2," - name: format strings @tsv error args: - '@tsv' input: '0' error: | @tsv cannot be applied to: number (0) - name: format strings @tsv row error args: - '@tsv' input: | [1, "foo", null, ["foo"]] error: | @tsv cannot format an array including: array (["foo"]) - name: format strings @sh args: - '@sh' input: | null true [1, "f'o'o\u0000\\0", null, false] expected: | "null" "true" "1 'f'\\''o'\\''o\\0\\0' null false" - name: format strings @sh error args: - '@sh' input: | [{"foo": "<>"}] error: | @sh cannot format an array including: object ({"foo":"<>"}) - name: format strings @base64 args: - '@base64' input: '{"foo":"bar"}' expected: | "eyJmb28iOiJiYXIifQ==" - name: format strings @base64 with string interpolation args: - '@base64 "\(.),\(. + .),\(. + . + .)"' input: '[1,2]' expected: | "WzEsMl0=,WzEsMiwxLDJd,WzEsMiwxLDIsMSwyXQ==" - name: format strings @base64d args: - -R - '@base64d' input: 'eyJmb28iOiJiYXIifQ==' expected: | "{\"foo\":\"bar\"}" - name: format strings @base64d without padding args: - -R - '@base64d' input: 'eyJmb28iOiJiYXIifQ' expected: | "{\"foo\":\"bar\"}" - name: format strings @base64d with trailing characters after padding args: - -R - '@base64d' input: 'eyJmb28iOiJiYXIifQ=x=y' expected: | "{\"foo\":\"bar\"}" - name: format strings @base64d error args: - '@base64d' input: | ":" error: | @base64d cannot be applied to ":": illegal base64 data at input byte 0 - name: format strings not defined error args: - -n - '@foo' error: | format not defined: @foo exit_code: 5 - name: format strings not defined error with numeric characters args: - -n - '@0' error: | format not defined: @0 exit_code: 5 - name: format strings not defined error with optional operator args: - -n - '@foo?, @bar"\(0|debug)"?, 1' expected: | 1 error: | ["DEBUG:",0] - name: destructuring alternative operator args: - '. as {$a} ?// [$a] ?// $a | $a' input: '[1] [2] {"a":3} 4' expected: | 1 2 3 4 - name: destructuring alternative operator with error backtrack args: - -c - '. as {$a} ?// [$a] ?// $a | if 1 < $a and $a < 4 then $a|error else $a end' input: '[1] [2] {"a":3} 4' expected: | 1 [2] {"a":3} 4 - name: destructuring alternative operator with multiple variables args: - -c - '. as {$a, b: [$c, {$d}]} ?// [$a, {$b}, $e] ?// $f | [$a, $b, $c, $d, $e, $f]' input: '{"a":1,"b":[2,{"d":3}]} [4,{"b":5,"c":6},7,8,9] "foo"' expected: | [1,null,2,3,null,null] [4,5,null,null,7,null] [null,null,null,null,null,"foo"] - name: destructuring alternative operator with array pattern against string args: - '. as [$a] ?// $a | $a' input: 'null "" "abc" [] ["abc"]' expected: | null "" "abc" null "abc" - name: compact output option args: - -c - '.' input: '{ "foo": ["hello", "world"] }' expected: | {"foo":["hello","world"]} - name: compact output long option args: - --compact-output - '.' input: '{ "foo": ["hello", "world"] }' expected: | {"foo":["hello","world"]} - name: compact output long option with value args: - --compact-output=1 - '.' input: '0' error: | boolean flag `--compact-output' cannot have an argument exit_code: 2 - name: raw string output option args: - -r - '.foo[]' input: '{ "foo": ["hello", "world", "multiple\nline"] }' expected: | hello world multiple line - name: raw string output long option args: - --raw-output - '.foo[]' input: '{ "foo": ["hello", "world", "multiple\nline"] }' expected: | hello world multiple line - name: raw string output option against non-string args: - -r - '.' input: '{ "foo": "multiple\nline" }' expected: | { "foo": "multiple\nline" } - name: join output option args: - -j - '.[]' input: '["foo",1,2,3]' expected: "foo123" - name: join output long option args: - --join-output - '.[]' input: '["foo",[{},{}],1,[2,3]]' expected: "foo[\n {},\n {}\n]1[\n 2,\n 3\n]" - name: nul output option args: - '-0' - '.[]' input: '["foo",1,2,3]' expected: "foo\x001\x002\x003\x00" - name: nul output long option args: - --nul-output - '.[]' input: '["foo",[{},{}],1,[2,3]]' expected: "foo\x00[\n {},\n {}\n]\x001\x00[\n 2,\n 3\n]\x00" - name: nul output option at the last argument args: - '.[]' - '-0' input: '["foo",1,2,3]' expected: "foo\x001\x002\x003\x00" - name: color output option args: - -C - '., null, nan, infinite' input: '{"x":"foo","y":128,"z":[null,false,true,3.14,"foo",{}]}' expected: "{\n\ \ \e[34;1m\"x\"\e[0m: \e[32m\"foo\"\e[0m,\n\ \ \e[34;1m\"y\"\e[0m: \e[36m128\e[0m,\n\ \ \e[34;1m\"z\"\e[0m: [\n\ \ \e[90mnull\e[0m,\n\ \ \e[33mfalse\e[0m,\n\ \ \e[33mtrue\e[0m,\n\ \ \e[36m3.14\e[0m,\n\ \ \e[32m\"foo\"\e[0m,\n\ \ {}\n\ \ ]\n\ }\n\ \e[90mnull\e[0m\n\ \e[90mnull\e[0m\n\ \e[36m1.7976931348623157e+308\e[0m\n" - name: color output option configured by environment variable args: - -C - '., null, nan, infinite' input: | { "x": "foo", "y": 128, "z": [null, false, true, 3.14, "foo", {}] } env: - GOJQ_COLORS=31:32:33:34:35:36:41:42 expected: "\e[42m{\e[0m\n\ \ \e[36m\"x\"\e[0m\e[42m:\e[0m \e[35m\"foo\"\e[0m\e[42m,\e[0m\n\ \ \e[36m\"y\"\e[0m\e[42m:\e[0m \e[34m128\e[0m\e[42m,\e[0m\n\ \ \e[36m\"z\"\e[0m\e[42m:\e[0m \e[41m[\e[0m\n\ \ \e[31mnull\e[0m\e[41m,\e[0m\n\ \ \e[32mfalse\e[0m\e[41m,\e[0m\n\ \ \e[33mtrue\e[0m\e[41m,\e[0m\n\ \ \e[34m3.14\e[0m\e[41m,\e[0m\n\ \ \e[35m\"foo\"\e[0m\e[41m,\e[0m\n\ \ \e[42m{\e[0m\e[42m}\e[0m\n\ \ \e[41m]\e[0m\n\ \e[42m}\e[0m\n\ \e[31mnull\e[0m\n\ \e[31mnull\e[0m\n\ \e[34m1.7976931348623157e+308\e[0m\n" - name: color output option configured by environment variable with fewer colors args: - -C - '.' input: | { "foo": [null, false, true, 3.14, "bar"] } env: - GOJQ_COLORS=31;1:::32;2 expected: "{\n\ \ \"foo\": [\n\ \ \e[31;1mnull\e[0m,\n\ \ false,\n\ \ true,\n\ \ \e[32;2m3.14\e[0m,\n\ \ \"bar\"\n\ \ ]\n\ }\n" - name: invalid colors environment variable args: - -C - '.' input: | 0 env: - GOJQ_COLORS=30:31;;:32 error: | invalid color: "31;;" - name: color output long option args: - --color-output - '.' input: '{"foo":42}' expected: "{\n\ \ \e[34;1m\"foo\"\e[0m: \e[36m42\e[0m\n\ }\n" - name: color output with compact option args: - -C - -c - '.' input: '{"x":42,"y":3.14,"z":[10000000000000000000]}' expected: "{\ \e[34;1m\"x\"\e[0m:\e[36m42\e[0m,\ \e[34;1m\"y\"\e[0m:\e[36m3.14\e[0m,\ \e[34;1m\"z\"\e[0m:[\e[36m10000000000000000000\e[0m]\ }\n" - name: monochrome output option args: - -C - -M - '.' input: '{"foo":42}' expected: | { "foo": 42 } - name: monochrome output long option args: - -C - --monochrome-output - '.' input: '{"foo":42}' expected: | { "foo": 42 } - name: NO_COLOR environment variable with color output option args: - --color-output - '.' input: '{"foo":42}' env: - NO_COLOR=1 expected: "{\n\ \ \e[34;1m\"foo\"\e[0m: \e[36m42\e[0m\n\ }\n" - name: indent option args: - --indent - '1' - '.' input: '{ "foo": ["hello", "world"] }' expected: | { "foo": [ "hello", "world" ] } - name: indent option with value 0 args: - --indent - '0' - '.' input: '{ "foo": ["hello", "world"] }' expected: | {"foo":["hello","world"]} - name: indent option with deeply nested array args: - --indent - '7' - '.' input: '{ "foo": ["hello", ["world", [[[[[[[[[]]]]]]]]]]] }' expected: | { "foo": [ "hello", [ "world", [ [ [ [ [ [ [ [ [] ] ] ] ] ] ] ] ] ] ] } - name: indent option too many indentation count error args: - --indent=10 - '.' input: '{ "foo": { "baz": ["hello", "world"] } }' error: | too many indentation count: 10 - name: indent option negative indentation count error args: - --indent - -1 - '.' input: '{ "foo": { "baz": ["hello", "world"] } }' error: | negative indentation count: -1 - name: indent option without argument args: - --indent input: '0' error: | expected argument for flag `--indent' exit_code: 2 - name: indent option parse error args: - --indent=foo input: '0' error: | invalid argument for flag `--indent': strconv.Atoi: parsing "foo": invalid syntax exit_code: 2 - name: tab option args: - --tab - '.' input: '{ "foo": { "baz": ["hello", ["world", [[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]] } }' expected: | { "foo": { "baz": [ "hello", [ "world", [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] } } - name: raw string input option args: - -R - '.' input: "null\nfalse\r\nfoo\nbar" expected: | "null" "false\r" "foo" "bar" - name: raw string input long option args: - --raw-input - '.' input: "null\nfalse\r\nfoo\nbar\n" expected: | "null" "false\r" "foo" "bar" - name: raw string input option error args: - -R - '. / 2' input: | { "foo": "bar" } error: | cannot divide: string ("{") and number (2) cannot divide: string (" \"foo\": \"bar\"") and number (2) cannot divide: string ("}") and number (2) - name: null input value option args: - -n - '[.]' expected: | [ null ] - name: null input value long option args: - --null-input - '[.]' expected: | [ null ] - name: null input value option with compact option args: - -n - -c - '[.,.]' expected: | [null,null] - name: null input value option with raw string input option args: - -n - -R - '.' input: 'foo' expected: | null - name: slurp option args: - -s - '.' input: | null false [] { "foo": "bar" } expected: | [ null, false, [], { "foo": "bar" } ] - name: slurp long option args: - --slurp - --compact-output - '.' input: | null false expected: | [null,false] - name: slurp option with raw string input option args: - -s - -R - '.' input: "null\nfalse\r\nfoo\nbar" expected: | "null\nfalse\r\nfoo\nbar" - name: slurp option with null input option args: - -s - -n - '.' expected: | null - name: slurp option with multiple json files args: - -s - '.' - 'testdata/1.json' - 'testdata/2.json' expected: | [ { "foo": 10 }, [ { "bar": [] } ] ] - name: slurp and raw input option with multiple json files args: - -R - -s - '.' - 'testdata/1.json' - 'testdata/2.json' expected: | "{\"foo\":10}\n[{\n\"bar\"\n:\n[]\n}]\n" - name: stream option args: - -c - --stream - '.' input: | 1 2 [] {} [3] {"x":1,"y":2} [1,2,{"x":{"y":[3,[{}],{"z":{}}]}}] {"a":"b","c":[1,[2,{}]],"d":null,"e":1.234,"x":{"y":{"z":[1,2],"q":2},"w":3},"z":[{}]} expected: | [[],1] [[],2] [[],[]] [[],{}] [[0],3] [[0]] [["x"],1] [["y"],2] [["y"]] [[0],1] [[1],2] [[2,"x","y",0],3] [[2,"x","y",1,0],{}] [[2,"x","y",1,0]] [[2,"x","y",2,"z"],{}] [[2,"x","y",2,"z"]] [[2,"x","y",2]] [[2,"x","y"]] [[2,"x"]] [[2]] [["a"],"b"] [["c",0],1] [["c",1,0],2] [["c",1,1],{}] [["c",1,1]] [["c",1]] [["d"],null] [["e"],1.234] [["x","y","z",0],1] [["x","y","z",1],2] [["x","y","z",1]] [["x","y","q"],2] [["x","y","q"]] [["x","w"],3] [["x","w"]] [["z",0],{}] [["z",0]] [["z"]] - name: stream option with empty input args: - --stream - '.' input: '' expected: '' - name: stream option with null input option args: - -n - --stream - '.' input: '1' expected: | null - name: stream option with raw input option args: - -R - --stream - '.' input: | 1 2 3 expected: | "1" "2" "3" - name: stream option with slurp option args: - -c - --stream - --slurp - '.[]' input: '{"a":1} {"b":2} {"c":3}' expected: | [["a"],1] [["a"]] [["b"],2] [["b"]] [["c"],3] [["c"]] - name: stream option with unterminated input args: - -c - --stream - '.' input: '{"a":1,' expected: | [["a"],1] error: | invalid json: {"a":1, ^ unexpected EOF - name: yaml input option args: - --yaml-input - '.' input: | foo: bar: 42 baz: | a b qux: 100 --- foo: 1 --- bar expected: | { "foo": { "bar": 42, "baz": "a\nb\n" }, "qux": 100 } { "foo": 1 } "bar" - name: yaml file input args: - --yaml-input - '.' - 'testdata/1.yaml' expected: | { "foo": { "bar": 42, "baz": "a\nb\n" }, "qux": 100 } { "foo": 1 } "bar" - name: yaml input option with non-string keys args: - --yaml-input - '.' input: | foo: - 42: bar expected: | { "foo": [ { "42": "bar" } ] } - name: yaml input option with timestamps args: - --yaml-input - '.' input: | foo: 2021-01-02 bar: 2021-01-02t10:11:12Z baz: "2021-01-02" expected: | # it is impossible to keep the original timestamp strings { "bar": "2021-01-02T10:11:12Z", "baz": "2021-01-02", "foo": "2021-01-02T00:00:00Z" } - name: yaml input option with slurp option args: - --yaml-input - --slurp - '.' input: | foo: bar: 42 baz: | a b qux: 100 --- foo: 1 --- bar expected: | [ { "foo": { "bar": 42, "baz": "a\nb\n" }, "qux": 100 }, { "foo": 1 }, "bar" ] - name: yaml input option with hyphen for stdin args: - --yaml-input - '.' - '-' input: | foo: 1 expected: | { "foo": 1 } - name: yaml input option error args: - --yaml-input - '.' input: | foo: 10 10 bar: error: | invalid yaml: :2 2 | 10 ^ could not find expected ':' - name: yaml input option multiple errors args: - --yaml-input - '.' input: | foo: 1 bar: 2 foo: 3 error: | invalid yaml: :3 3 | foo: 3 ^ mapping key "foo" already defined at line 1 - name: yaml file input error args: - --yaml-input - '.' - 'testdata/2.yaml' error: | invalid yaml: testdata/2.yaml:5 5 | a: b: c ^ mapping values are not allowed in this context - name: yaml file input error in dos format args: - --yaml-input - '.' - 'testdata/3.yaml' error: | invalid yaml: testdata/3.yaml:5 5 | a: b: c ^ mapping values are not allowed in this context - name: yaml file input error in mac format args: - --yaml-input - '.' - 'testdata/4.yaml' error: | invalid yaml: testdata/4.yaml:5 5 | a: b: c ^ mapping values are not allowed in this context - name: yaml output option args: - --yaml-output - '.' input: '{"foo": 128, "bar": {"baz": [1,"qux", []]}}' expected: | bar: baz: - 1 - qux - [] foo: 128 - name: yaml output option with multiple values args: - --yaml-output - '..' input: '{"foo": 128, "bar": [1,2,"foo"]}' expected: | bar: - 1 - 2 - foo foo: 128 --- - 1 - 2 - foo --- 1 --- 2 --- foo --- 128 - name: yaml input and output option args: - --yaml-input - --yaml-output - '.' input: | foo: 42 --- foo: bar: 42 baz: | a b qux: 100 --- foo: 1 --- bar expected: | foo: 42 --- foo: bar: 42 baz: | a b qux: 100 --- foo: 1 --- bar - name: yaml output with indent option args: - --yaml-output - --indent - '8' - '.' input: '{ "foo": { "baz": ["hello", "world"] } }' expected: | foo: baz: - hello - world - name: yaml output with indent option error args: - --yaml-output - --indent - '10' - '.' input: '{ "foo": { "baz": ["hello", "world"] } }' error: | too many indentation count: 10 - name: yaml output with tab option error args: - --yaml-output - --tab - '.' input: '{"foo": 128, "bar": {"baz": [1,"qux", []]}}' error: | cannot use tabs for YAML output - name: source query from file args: - -n - -f - 'testdata/1.jq' expected: | null - name: source query from file short option in clumped options args: - -nf - 'testdata/1.jq' expected: | null - name: source query from file short option with equal sign args: - -n - -f=testdata/1.jq expected: | null - name: source query from file long option args: - --null-input - --from-file - 'testdata/1.jq' expected: | null - name: source query from file long option with equal sign args: - --null-input - --from-file=testdata/1.jq expected: | null - name: invalid query args: - '>abc' input: '{}' error: | invalid query: >abc >abc ^ unexpected token ">" exit_code: 3 - name: invalid query args: - '.abc[' input: '{}' error: | invalid query: .abc[ .abc[ ^ unexpected EOF exit_code: 3 - name: invalid query args: - '[ .[] | { .id } ]' input: '{}' error: | invalid query: [ .[] | { .id } ] [ .[] | { .id } ] ^ unexpected token ".id" exit_code: 3 - name: invalid query args: - 'if 0then. else. end' input: '{}' error: | invalid query: if 0then. else. end if 0then. else. end ^ invalid token "0t" exit_code: 3 - name: invalid query args: - '.foo & .bar' input: '{}' error: | invalid query: .foo & .bar .foo & .bar ^ unexpected token "&" exit_code: 3 - name: invalid query args: - 'foo☆' input: '{}' error: | invalid query: foo☆ foo☆ ^ unexpected token "☆" exit_code: 3 - name: invalid query args: - "\\/" input: '{}' error: | invalid query: \/ \/ ^ unexpected token "\\" exit_code: 3 - name: invalid query args: - "{\n[]}" input: '{}' error: | invalid query: :2 2 | []} ^ unexpected token "[" exit_code: 3 - name: invalid multiple line query args: - | [ .[] | { id } } ] input: '{}' error: | invalid query: :3 3 | | { id } } ^ unexpected token "}" exit_code: 3 - name: invalid multiple line query in file args: - -f - 'testdata/10.jq' error: | invalid query: testdata/10.jq:5 5 | bar ] ^ unexpected token "]" exit_code: 3 - name: invalid multiple line query in dos format args: - -f - 'testdata/11.jq' error: | invalid query: testdata/11.jq:5 5 | bar ] ^ unexpected token "]" exit_code: 3 - name: invalid multiple line query in mac format args: - -f - 'testdata/12.jq' error: | invalid query: testdata/12.jq:5 5 | bar ] ^ unexpected token "]" exit_code: 3 - name: invalid query in file args: - -f - 'testdata/2.jq' error: | invalid query: testdata/2.jq:3 3 | bar, [] ^ unexpected token "[" exit_code: 3 - name: query file not found args: - -f - 'testdata/3.jq' error: 'open testdata/3.jq:' - name: query file not specified args: - -f error: | expected argument for flag `-f' exit_code: 2 - name: invalid json unexpected eof error input: '{' error: | invalid json: { ^ unexpected EOF - name: invalid json unexpected eof error with multibyte characters input: '{ "12345":' error: | invalid json: { "12345": ^ unexpected EOF - name: invalid json unexpected eof error with multiple lines input: | [0 ,[ error: | invalid json: :2 2 | ,[ ^ unexpected EOF - name: invalid json invalid character with multibyte characters input: | { "12345" 100 } error: | invalid json: :2 2 | "12345" 100 ^ invalid character '1' after object key - name: invalid json string literal input: | { "123": n } error: | invalid json: :2 2 | "123": n ^ invalid character '\n' in literal null (expecting 'u') - name: multiple json in input input: '{}[]{"foo":10}{"bar":[]}' expected: | {} [] { "foo": 10 } { "bar": [] } - name: hyphen for stdin args: - '.' - '-' input: '{}' expected: | {} - name: json file arguments args: - '.' - 'testdata/1.json' - 'testdata/2.json' expected: | { "foo": 10 } [ { "bar": [] } ] - name: json file error json args: - '.' - 'testdata/3.json' error: | invalid json: testdata/3.json { ^ unexpected EOF - name: json file error json args: - '.' - 'testdata/4.json' error: | invalid json: testdata/4.json:3 3 | bar ^ invalid character 'b' looking for beginning of value - name: json file error json args: - '.' - 'testdata/8.json' error: | invalid json: testdata/8.json [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, , 1, 1, 1, 1, 1, 1, 1 ^ invalid character ',' looking for beginning of value - name: json file error json args: - '.' - 'testdata/9.json' error: | invalid json: testdata/9.json:3 3 | 0", "0", "0", "0", "0", "0", "0", , "0", "0"] ^ invalid character ',' looking for beginning of value - name: json file error json with multibyte character in the error line args: - '.' - 'testdata/5.json' error: | # file contents is read by inputReader so the entire error line is shown unlike the following input json test invalid json: testdata/5.json:3 3 | "abcde": :★★★★★★★★★★★★★★★ ^ invalid character ':' looking for beginning of value - name: json input error json with multibyte character in the error line args: - '.' input: | { "a": "This is a test for multibyte character in the error line of JSON. Since the first buffer size of JSON decoder is 512 (see encoding/json/stream.go) and the decoder stops reading bytes on encountering an error, the error line can be an invalid UTF-8 string even if the complete input is a valid UTF-8 string. Note that the error happens before 512 bytes and the corresponding line continues beyond 512 bytes with a multibyte character at the boundary.", "abcde": :★★★★★★★★★★★★★★★ error: | invalid json: :3 3 | "abcde": :★★★★★★★★★★★★ ^ invalid character ':' looking for beginning of value - name: json file not found error args: - '.' - 'testdata/1.json' - 'testdata/6.json' - 'testdata/2.json' expected: | { "foo": 10 } [ { "bar": [] } ] error: 'open testdata/6.json:' - name: module directory option args: - -c - -L - 'testdata' - 'include "4"; 1 | f | g' input: '0' expected: | {"foo":[1,2,3,4,[1,2,{"foo":42}]]} - name: module directory option args: - -c - -L - 'testdata' - 'import "4" as m1; 1 | m1::f | m1::g' input: '0' expected: | {"foo":[1,2,3,4,[1,2,{"foo":42}]]} - name: module directory option args: - -c - -L - 'testdata' - 'include "m1"; include "m3"; [f, g]' input: '0' expected: | [42,43,44,45] - name: module directory option args: - -c - -L - 'testdata' - 'import "7" as $foo; $foo, $foo::foo' input: '0' expected: | [1,2,{"foo":42}] [1,2,{"foo":42}] - name: module directory option args: - -c - -L - 'testdata' - 'import "m1" as $x; $x, $x::x, $x[1].m1*100000000000000000000' input: '0' expected: | [42,{"m1":42}] [42,{"m1":42}] 4200000000000000000000 - name: module directory option with argjson args: - -c - -L - 'testdata' - --argjson - 'x' - '{}' - 'import "m1" as $x; $x' input: '0' expected: | [42,{"m1":42}] - name: module directory option parse error args: - 'include "m"; f' input: '0' error: 'compile error: module not found: "m"' exit_code: 3 - name: module directory option function error args: - -L - 'testdata' - 'include "5"; i' input: '0' error: 'function not defined: i/0' exit_code: 3 - name: module directory option variable error args: - -L - 'testdata' - 'include "4"; $x' input: '0' error: 'compile error: variable not defined: $x' exit_code: 3 - name: module directory option variable name conflict args: - -c - -L - 'testdata' - 'include "6"; include "13"; . as $x | $x, g, h' input: '1' expected: | 1 [1,2,{"foo":42}] [[{"foo":10}],[1,2,{"foo":42}]] - name: module directory option json parse error args: - -Ltestdata - 'import "4" as $x; $x' input: '0' error: | compile error: invalid json: testdata/4.json:3 3 | bar ^ invalid character 'b' looking for beginning of value exit_code: 3 - name: module directory option with same alias name args: - -c - -L=testdata - 'include "4"; import "6" as bar; bar::g' input: '1' expected: | [1,2,{"foo":42}] - name: module directory option without argument args: - -c - -L input: '0' error: | expected argument for flag `-L' exit_code: 2 - name: search directory in query args: - 'include "m2" { search: "testdata/m2" }; g' input: '0' expected: | 0 43 44 - name: empty module args: - -L - 'testdata' - 'include "8"; .' input: '0' expected: | 0 - name: invalid query in a module args: - -L - 'testdata' - 'include "7"; .' input: '0' error: | invalid query: testdata/7.jq:1 1 | def f: 1 + ^ unexpected EOF exit_code: 3 - name: module directive args: - -c - -L - 'testdata' - 'include "9"; f(2)' input: '3' expected: | 6 - name: module in query args: - -n - 'module {}; .' expected: | null - name: modulemeta function args: - -c - -L - 'testdata' - 'tostring | modulemeta' input: '4 5 6 8 9' expected: | {"deps":[{"is_data":false,"relpath":"5"},{"as":"bar","is_data":false,"relpath":"6"}]} {"deps":[{"as":"foo","is_data":false,"relpath":"6","test":"foo"}]} {"deps":[{"as":"x","is_data":true,"relpath":"7"}]} {"deps":[]} {"deps":[],"description":"This is a description","name":"sample-module","x":[[],{},{"y":10,"z":20}],"y":{"z":[128,"",false,true,null,[[]]]}} - name: modulemeta function error args: - -c - -L - 'testdata' - '"3" | modulemeta' input: '0' error: | module not found: "3" - name: arg option args: - --arg - 'foo' - 'value 1' - --arg - 'bar' - '42' - '{ $foo, $bar }' input: '123' expected: | { "bar": "42", "foo": "value 1" } - name: arg option arguments count error args: - --arg error: | expected 2 arguments for flag `--arg' exit_code: 2 - name: arg option arguments count error args: - --arg - 'foo' error: | expected 2 arguments for flag `--arg' exit_code: 2 - name: arg option variable name error args: - --arg - 'f.oo' - '10' - '.' error: | invalid variable name: $f.oo exit_code: 3 - name: argjson option args: - --argjson - 'foo' - '{ "foo": 42 }' - --argjson - 'bar' - '10' - '{ $foo, $bar }' input: '123' expected: | { "bar": 10, "foo": { "foo": 42 } } - name: argjson option with big integer args: - --argjson - 'foo' - '10000000000000000000000000000000' - '$foo * 2' input: '0' expected: | 20000000000000000000000000000000 - name: arg and argjson options args: - --arg - 'x' - '1' - --argjson - 'x' - '1' - --argjson - 'y' - '2' - --arg - 'y' - '2' - '{ $x, $y }' input: '0' expected: | { "x": "1", "y": 2 } - name: argjson option error args: - --argjson - '0x' - '0' - '.' error: | invalid variable name: $0x exit_code: 3 - name: argjson option parse error args: - --argjson - 'foo' - '{ foo }' - '.' error: | invalid json: $foo { foo } ^ invalid character 'f' looking for beginning of object key string - name: slurpfile option args: - --slurpfile - 'foo' - 'testdata/7.json' - '{ $foo }' input: 'null' expected: | { "foo": [ 1, 2, { "foo": 42 } ] } - name: slurpfile option invalid json error args: - --slurpfile - 'foo' - 'testdata/1.jq' - '{ $foo }' input: 'null' error: | invalid json: testdata/1.jq .foo.bar ^ invalid character '.' looking for beginning of value - name: slurpfile option invalid file error args: - --slurpfile - 'foo' - '-' - '{ $foo }' input: 'null' error: 'open -:' - name: rawfile option args: - --rawfile - 'foo' - 'testdata/7.json' - '{ $foo }' input: 'null' expected: | { "foo": "1\n2\n{ \"foo\": 42 }\n" } - name: rawfile option error args: - --rawfile - 'foo' - 'testdata/6.json' - '{ $foo }' input: 'null' error: 'open testdata/6.json:' - name: $ARGS variable args: - --arg - 'a' - '1' - --argjson - 'b' - '1' - --slurpfile - 'c' - 'testdata/1.json' - --rawfile - 'd' - 'testdata/1.json' - '$ARGS | ., .named.a' input: 'null' expected: | { "named": { "a": "1", "b": 1, "c": [ { "foo": 10 } ], "d": "{\"foo\":10}\n" }, "positional": [] } "1" - name: $ARGS variable with args option args: - --arg - 'foo' - 'value' - '$ARGS | ., .positional[]' - --args - '1' - '"bar"' - '{"x":1}' input: '0' expected: | { "named": { "foo": "value" }, "positional": [ "1", "\"bar\"", "{\"x\":1}" ] } "1" "\"bar\"" "{\"x\":1}" - name: args option without argument args: - '$ARGS' - --args input: '0' expected: | { "named": {}, "positional": [] } - name: query and options after args option args: - --args - '$ARGS' - 1 - -n - 2 - -c - -- - 3 - -- - -c input: '0' expected: | {"named":{},"positional":["1","2","3","--","-c"]} - name: double dash with args option args: - --args - -- - 1 - 2 - 3 input: '0' expected: | 0 - name: $ARGS variable with jsonargs option args: - '$ARGS | ., .positional[]' - --arg - 'foo' - 'value' - --jsonargs - '1' - '"bar"' - '{"x":1}' input: '0' expected: | { "named": { "foo": "value" }, "positional": [ 1, "bar", { "x": 1 } ] } 1 "bar" { "x": 1 } - name: jsonargs option without argument args: - '$ARGS' - --jsonargs input: '0' expected: | { "named": {}, "positional": [] } - name: jsonargs option parse error args: - '$ARGS' - --jsonargs - '1' - '"bar"' - '{"x":}' input: '0' error: | invalid json: --jsonargs {"x":} ^ invalid character '}' looking for beginning of value - name: args with jsonargs option args: - -c - '$ARGS' - --args - 1 - 2 - --jsonargs - 1 - 2 - --args - 1 - 2 input: '0' expected: | {"named":{},"positional":["1","2",1,2,"1","2"]} - name: args with jsonargs option args: - -c - '$ARGS' - --args - 1 - 2 - --jsonargs - 1 - 2 - --args - 1 - 2 - --jsonargs - 1 - 2 input: '0' expected: | {"named":{},"positional":["1","2",1,2,"1","2",1,2]} - name: exit status option with null result args: - -e - '.' input: 'null' expected: | null exit_code: 1 - name: exit status option with empty function args: - -e - 'empty' input: 'null' exit_code: 4 - name: exit status long option with false result args: - --exit-status - '.' input: 'false' expected: | false exit_code: 1 - name: exit status long option with true result args: - --exit-status - 'not' input: 'false' expected: | true - name: exit status long option with multiple results args: - --exit-status - '1,not,2' input: 'false' expected: | 1 true 2 - name: exit status long option with error function args: - --exit-status - 'error' input: 'false' error: | error: false exit_code: 5 - name: exit status long option with error function args: - --exit-status - 'error' input: 'null' exit_code: 4 - name: exit status long option with halt function args: - --exit-status - 'halt' input: 'false' exit_code: 0 - name: exit status long option with halt_error function args: - --exit-status - 'halt_error(0)' input: 'null' exit_code: 0 - name: exit status long option with halt_error function args: - --exit-status - 'halt_error(42)' input: 'null' exit_code: 42 - name: query with leading hyphen args: - '-.' input: '1' expected: | -1 - name: query with leading hyphen after a flag args: - -r - '-.' input: '1' expected: | -1 - name: query with leading -0 and spaces args: - '-0 + .' input: '1' expected: | 1 - name: query with leading -0 but no spaces args: - '-0.1' input: '1' expected: | -0.1 - name: query with leading -0 before json files args: - -c - '-0 * 1' - 'testdata/1.json' - 'testdata/2.json' input: '1' expected: | 0 0 - name: double dash args: - '--' - '.' input: '1' expected: | 1 - name: double dash with query with leading hyphen args: - '--' - '-.' input: '1' expected: | -1 - name: double dash with query with leading -0 args: - '--' - '-0' input: '1' expected: | 0 - name: double dash with nul output option args: - '-0' - '--' - '-0' input: '1' expected: "0\x00" - name: double double dash args: - '--' - '--' - '.' input: '1' error: | invalid query: -- -- ^ unexpected EOF exit_code: 3 - name: short clumped options args: - -cRr - '.' input: 'foo' expected: | foo - name: invalid short option error args: - -q - '.' error: | unknown flag `q' exit_code: 2 - name: invalid short option error in clumped options args: - -nqe - '.' error: | unknown flag `q' exit_code: 2 - name: invalid long option error args: - --qqq - '.' error: | unknown flag `--qqq' exit_code: 2 gojq-0.12.13/cli/testdata/000077500000000000000000000000001443612442000151765ustar00rootroot00000000000000gojq-0.12.13/cli/testdata/1.jq000066400000000000000000000000111443612442000156620ustar00rootroot00000000000000.foo.bar gojq-0.12.13/cli/testdata/1.json000066400000000000000000000000131443612442000162230ustar00rootroot00000000000000{"foo":10} gojq-0.12.13/cli/testdata/1.yaml000066400000000000000000000001001443612442000162110ustar00rootroot00000000000000foo: bar: 42 baz: | a b qux: 100 --- foo: 1 --- bar gojq-0.12.13/cli/testdata/10.jq000066400000000000000000000000221443612442000157440ustar00rootroot00000000000000.foo, [], bar ] gojq-0.12.13/cli/testdata/11.jq000066400000000000000000000000271443612442000157520ustar00rootroot00000000000000.foo, [], bar ] gojq-0.12.13/cli/testdata/12.jq000066400000000000000000000000221443612442000157460ustar00rootroot00000000000000.foo, [], bar ] gojq-0.12.13/cli/testdata/13.jq000066400000000000000000000000561443612442000157560ustar00rootroot00000000000000import "1" as $x; include "6"; def h: [$x,g]; gojq-0.12.13/cli/testdata/2.jq000066400000000000000000000000341443612442000156700ustar00rootroot00000000000000.foo | { foo, bar, [] } gojq-0.12.13/cli/testdata/2.json000066400000000000000000000000211443612442000162230ustar00rootroot00000000000000[{ "bar" : [] }] gojq-0.12.13/cli/testdata/2.yaml000066400000000000000000000000441443612442000162210ustar00rootroot00000000000000foo: bar: a a: b: c baz: 1 gojq-0.12.13/cli/testdata/3.json000066400000000000000000000000021443612442000162230ustar00rootroot00000000000000{ gojq-0.12.13/cli/testdata/3.yaml000066400000000000000000000000521443612442000162210ustar00rootroot00000000000000foo: bar: a a: b: c baz: 1 gojq-0.12.13/cli/testdata/4.jq000066400000000000000000000001371443612442000156760ustar00rootroot00000000000000include "5"; import "6" as bar; def f: [., . * 2, h, (bar::i|.+1), bar::g]; def g: { foo: . }; gojq-0.12.13/cli/testdata/4.json000066400000000000000000000000271443612442000162330ustar00rootroot00000000000000{ "foo": bar } gojq-0.12.13/cli/testdata/4.yaml000066400000000000000000000000441443612442000162230ustar00rootroot00000000000000foo: bar: a a: b: c baz: 1 gojq-0.12.13/cli/testdata/5.jq000066400000000000000000000000661443612442000157000ustar00rootroot00000000000000import "6" as foo { test: "foo" }; def h: . * foo::i; gojq-0.12.13/cli/testdata/5.json000066400000000000000000000010101443612442000162250ustar00rootroot00000000000000{ "a": "This is a test for multibyte character in the error line of JSON. Since the first buffer size of JSON decoder is 512 (see encoding/json/stream.go) and the decoder stops reading bytes on encountering an error, the error line can be an invalid UTF-8 string even if the complete input is a valid UTF-8 string. Note that the error happens before 512 bytes and the corresponding line continues beyond 512 bytes with a multibyte character at the boundary.", "abcde": :★★★★★★★★★★★★★★★ gojq-0.12.13/cli/testdata/6.jq000066400000000000000000000000471443612442000157000ustar00rootroot00000000000000import "7" as $x; def i: 3; def g: $x; gojq-0.12.13/cli/testdata/7.jq000066400000000000000000000000121443612442000156710ustar00rootroot00000000000000def f: 1 +gojq-0.12.13/cli/testdata/7.json000066400000000000000000000000221443612442000162310ustar00rootroot000000000000001 2 { "foo": 42 } gojq-0.12.13/cli/testdata/8.jq000066400000000000000000000000011443612442000156700ustar00rootroot00000000000000 gojq-0.12.13/cli/testdata/8.json000066400000000000000000000001701443612442000162360ustar00rootroot00000000000000[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, , 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, gojq-0.12.13/cli/testdata/9.jq000066400000000000000000000003141443612442000157000ustar00rootroot00000000000000module { "name": "sample-module", "description": "This is a description", x: [ [], {}, { y: 10, z: 20 } ], y: { z: [128, "", false, true, null, [[]]] }, }; def f($x): $x * .; gojq-0.12.13/cli/testdata/9.json000066400000000000000000000012371443612442000162440ustar00rootroot00000000000000["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", , "0", "0"] gojq-0.12.13/cli/testdata/m1/000077500000000000000000000000001443612442000155135ustar00rootroot00000000000000gojq-0.12.13/cli/testdata/m1/m1.jq000066400000000000000000000000451443612442000163630ustar00rootroot00000000000000import "m2" as x; def f: 42 | x::g; gojq-0.12.13/cli/testdata/m1/m1.json000066400000000000000000000000221443612442000167150ustar00rootroot0000000000000042 { "m1": 42 } gojq-0.12.13/cli/testdata/m2/000077500000000000000000000000001443612442000155145ustar00rootroot00000000000000gojq-0.12.13/cli/testdata/m2/m2.jq000066400000000000000000000001361443612442000163660ustar00rootroot00000000000000import "m3" as x { search: "./" }; import "m3" as $x { search: "./" }; def g: ., x::g, $x[]; gojq-0.12.13/cli/testdata/m2/m3.jq000066400000000000000000000000131443612442000163610ustar00rootroot00000000000000def g: 43; gojq-0.12.13/cli/testdata/m2/m3.json000066400000000000000000000000031443612442000167170ustar00rootroot0000000000000044 gojq-0.12.13/cli/testdata/m3/000077500000000000000000000000001443612442000155155ustar00rootroot00000000000000gojq-0.12.13/cli/testdata/m3/m3.jq000066400000000000000000000000131443612442000163620ustar00rootroot00000000000000def g: 45; gojq-0.12.13/cli/yaml.go000066400000000000000000000013111443612442000146520ustar00rootroot00000000000000package cli import ( "fmt" "time" ) // Workaround for https://github.com/go-yaml/yaml/issues/139 func normalizeYAML(v any) any { switch v := v.(type) { case map[any]any: w := make(map[string]any, len(v)) for k, v := range v { w[fmt.Sprint(k)] = normalizeYAML(v) } return w case map[string]any: w := make(map[string]any, len(v)) for k, v := range v { w[k] = normalizeYAML(v) } return w case []any: for i, w := range v { v[i] = normalizeYAML(w) } return v // go-yaml unmarshals timestamp string to time.Time but gojq cannot handle it. // It is impossible to keep the original timestamp strings. case time.Time: return v.Format(time.RFC3339) default: return v } } gojq-0.12.13/cmd/000077500000000000000000000000001443612442000133615ustar00rootroot00000000000000gojq-0.12.13/cmd/gojq/000077500000000000000000000000001443612442000143215ustar00rootroot00000000000000gojq-0.12.13/cmd/gojq/main.go000066400000000000000000000002061443612442000155720ustar00rootroot00000000000000// gojq - Go implementation of jq package main import ( "os" "github.com/itchyny/gojq/cli" ) func main() { os.Exit(cli.Run()) } gojq-0.12.13/code.go000066400000000000000000000027351443612442000140660ustar00rootroot00000000000000package 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) } } gojq-0.12.13/compare.go000066400000000000000000000031631443612442000145760ustar00rootroot00000000000000package gojq import ( "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 compare(l, r) } func compare(l, r any) int { return binopTypeSwitch(l, r, compareInt, func(l, r float64) any { switch { case l < r || math.IsNaN(l): return -1 case l == r: return 0 default: return 1 } }, func(l, r *big.Int) any { return l.Cmp(r) }, func(l, r string) any { switch { case l < r: return -1 case l == r: return 0 default: return 1 } }, func(l, r []any) any { n := len(l) if len(r) < n { n = len(r) } for i := 0; i < n; i++ { if cmp := compare(l[i], r[i]); cmp != 0 { return cmp } } return compareInt(len(l), len(r)) }, func(l, r map[string]any) any { 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) any { return compareInt(typeIndex(l), typeIndex(r)) }, ).(int) } func compareInt(l, r int) any { switch { case l < r: return -1 case l == r: return 0 default: return 1 } } 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: return 3 case string: return 4 case []any: return 5 case map[string]any: return 6 } } gojq-0.12.13/compare_test.go000066400000000000000000000035551443612442000156420ustar00rootroot00000000000000package gojq_test import ( "fmt" "math" "math/big" "testing" "github.com/itchyny/gojq" ) func TestCompare(t *testing.T) { testCases := []struct { l, r any expected int }{ {nil, nil, 0}, {nil, false, -1}, {false, nil, 1}, {false, false, 0}, {false, true, -1}, {true, false, 1}, {true, true, 0}, {true, 0, -1}, {0, true, 1}, {0, 0, 0}, {0, 1, -1}, {1, 0, 1}, {1, 1, 0}, {0, math.NaN(), 1}, {math.NaN(), 0, -1}, {math.NaN(), math.NaN(), -1}, {1, 1.00, 0}, {1.00, 1, 0}, {1.00, 1.01, -1}, {1.01, 1.00, 1}, {1.01, 1.01, 0}, {1, big.NewInt(0), 1}, {big.NewInt(0), 1, -1}, {0, big.NewInt(0), 0}, {1, "", -1}, {"", 1, 1}, {"", "", 0}, {"", "abc", -1}, {"abc", "", 1}, {"abc", "abc", 0}, {"", []any{}, -1}, {[]any{}, "", 1}, {[]any{}, []any{}, 0}, {[]any{}, []any{nil}, -1}, {[]any{nil}, []any{}, 1}, {[]any{nil}, []any{nil}, 0}, {[]any{0, 1, 2}, []any{0, 1, nil}, 1}, {[]any{0, 1, 2}, []any{0, 1, 2, nil}, -1}, {[]any{0, 1, 2, false, nil}, []any{0, 1, 2, nil, false}, 1}, {[]any{}, map[string]any{}, -1}, {map[string]any{}, []any{}, 1}, {map[string]any{}, map[string]any{}, 0}, {map[string]any{"a": nil}, map[string]any{"a": nil}, 0}, {map[string]any{"a": nil}, map[string]any{"a": nil, "b": nil}, -1}, {map[string]any{"a": nil, "b": nil}, map[string]any{"a": nil, "c": nil}, -1}, {map[string]any{"a": 0, "b": 0, "c": 0}, map[string]any{"a": 0, "b": 0, "c": 0}, 0}, {map[string]any{"a": 0, "b": 0, "d": 0}, map[string]any{"a": 0, "b": 1, "c": 0}, 1}, {map[string]any{"a": 0, "b": 1, "c": 2}, map[string]any{"a": 0, "b": 2, "c": 1}, -1}, } for _, tc := range testCases { t.Run(fmt.Sprintf("%v,%v", tc.l, tc.r), func(t *testing.T) { got := gojq.Compare(tc.l, tc.r) if got != tc.expected { t.Errorf("Compare(%v, %v): got %d, expected %d", tc.l, tc.r, got, tc.expected) } }) } } gojq-0.12.13/compiler.go000066400000000000000000001214561443612442000147700ustar00rootroot00000000000000package gojq import ( "context" "errors" "fmt" "sort" "strconv" "strings" ) type compiler struct { moduleLoader ModuleLoader environLoader func() []string variables []string customFuncs map[string]function inputIter Iter codes []*code codeinfos []codeinfo builtinScope *scopeinfo scopes []*scopeinfo scopecnt int } // Code is a compiled jq query. type Code struct { variables []string codes []*code codeinfos []codeinfo } // Run runs the code with the variable values (which should be in the // same order as the given variables using [WithVariables]) and returns // a result iterator. // // It is safe to call this method in goroutines, to reuse a compiled [*Code]. // But for arguments, do not give values sharing same data between goroutines. func (c *Code) Run(v any, values ...any) Iter { return c.RunWithContext(context.Background(), v, values...) } // RunWithContext runs the code with context. func (c *Code) RunWithContext(ctx context.Context, v any, values ...any) Iter { if len(values) > len(c.variables) { return NewIter(&tooManyVariableValuesError{}) } else if len(values) < len(c.variables) { return NewIter(&expectedVariableError{c.variables[len(values)]}) } for i, v := range values { values[i] = normalizeNumbers(v) } return newEnv(ctx).execute(c, normalizeNumbers(v), values...) } type scopeinfo struct { variables []*varinfo funcs []*funcinfo id int depth int variablecnt int } type varinfo struct { name string index [2]int depth int } type funcinfo struct { name string pc int argcnt int } // Compile compiles a query. func Compile(q *Query, options ...CompilerOption) (*Code, error) { c := &compiler{} for _, opt := range options { opt(c) } c.builtinScope = c.newScope() scope := c.newScope() c.scopes = []*scopeinfo{scope} setscope := c.lazy(func() *code { return &code{op: opscope, v: [3]int{scope.id, scope.variablecnt, 0}} }) if c.moduleLoader != nil { if moduleLoader, ok := c.moduleLoader.(interface { LoadInitModules() ([]*Query, error) }); ok { qs, err := moduleLoader.LoadInitModules() if err != nil { return nil, err } for _, q := range qs { if err := c.compileModule(q, ""); err != nil { return nil, err } } } } if err := c.compile(q); err != nil { return nil, err } setscope() c.optimizeTailRec() c.optimizeCodeOps() return &Code{ variables: c.variables, codes: c.codes, codeinfos: c.codeinfos, }, nil } func (c *compiler) compile(q *Query) error { for _, name := range c.variables { if !newLexer(name).validVarName() { return &variableNameError{name} } c.appendCodeInfo(name) c.append(&code{op: opstore, v: c.pushVariable(name)}) } for _, i := range q.Imports { if err := c.compileImport(i); err != nil { return err } } if err := c.compileQuery(q); err != nil { return err } c.append(&code{op: opret}) return nil } func (c *compiler) compileImport(i *Import) error { var path, alias string var err error if i.ImportPath != "" { path, alias = i.ImportPath, i.ImportAlias } else { path = i.IncludePath } if c.moduleLoader == nil { return fmt.Errorf("cannot load module: %q", path) } if strings.HasPrefix(alias, "$") { var vals any if moduleLoader, ok := c.moduleLoader.(interface { LoadJSONWithMeta(string, map[string]any) (any, error) }); ok { if vals, err = moduleLoader.LoadJSONWithMeta(path, i.Meta.ToValue()); err != nil { return err } } else if moduleLoader, ok := c.moduleLoader.(interface { LoadJSON(string) (any, error) }); ok { if vals, err = moduleLoader.LoadJSON(path); err != nil { return err } } else { return fmt.Errorf("module not found: %q", path) } vals = normalizeNumbers(vals) c.append(&code{op: oppush, v: vals}) c.append(&code{op: opstore, v: c.pushVariable(alias)}) c.append(&code{op: oppush, v: vals}) c.append(&code{op: opstore, v: c.pushVariable(alias + "::" + alias[1:])}) return nil } var q *Query if moduleLoader, ok := c.moduleLoader.(interface { LoadModuleWithMeta(string, map[string]any) (*Query, error) }); ok { if q, err = moduleLoader.LoadModuleWithMeta(path, i.Meta.ToValue()); err != nil { return err } } else if moduleLoader, ok := c.moduleLoader.(interface { LoadModule(string) (*Query, error) }); ok { if q, err = moduleLoader.LoadModule(path); err != nil { return err } } c.appendCodeInfo("module " + path) if err = c.compileModule(q, alias); err != nil { return err } c.appendCodeInfo("end of module " + path) return nil } func (c *compiler) compileModule(q *Query, alias string) error { scope := c.scopes[len(c.scopes)-1] scope.depth++ defer func(l int) { scope.depth-- scope.variables = scope.variables[:l] }(len(scope.variables)) if alias != "" { defer func(l int) { for _, f := range scope.funcs[l:] { f.name = alias + "::" + f.name } }(len(scope.funcs)) } for _, i := range q.Imports { if err := c.compileImport(i); err != nil { return err } } for _, fd := range q.FuncDefs { if err := c.compileFuncDef(fd, false); err != nil { return err } } return nil } func (c *compiler) newVariable() [2]int { return c.createVariable("") } func (c *compiler) pushVariable(name string) [2]int { s := c.scopes[len(c.scopes)-1] for _, v := range s.variables { if v.name == name && v.depth == s.depth { return v.index } } return c.createVariable(name) } func (c *compiler) createVariable(name string) [2]int { s := c.scopes[len(c.scopes)-1] v := [2]int{s.id, s.variablecnt} s.variablecnt++ s.variables = append(s.variables, &varinfo{name, v, s.depth}) return v } func (c *compiler) lookupVariable(name string) ([2]int, error) { for i := len(c.scopes) - 1; i >= 0; i-- { s := c.scopes[i] for j := len(s.variables) - 1; j >= 0; j-- { if w := s.variables[j]; w.name == name { return w.index, nil } } } return [2]int{}, &variableNotFoundError{name} } func (c *compiler) lookupFuncOrVariable(name string) (*funcinfo, *varinfo) { for i, isFunc := len(c.scopes)-1, name[0] != '$'; i >= 0; i-- { s := c.scopes[i] if isFunc { for j := len(s.funcs) - 1; j >= 0; j-- { if f := s.funcs[j]; f.name == name && f.argcnt == 0 { return f, nil } } } for j := len(s.variables) - 1; j >= 0; j-- { if v := s.variables[j]; v.name == name { return nil, v } } } return nil, nil } func (c *compiler) lookupBuiltin(name string, argcnt int) *funcinfo { s := c.builtinScope for i := len(s.funcs) - 1; i >= 0; i-- { if f := s.funcs[i]; f.name == name && f.argcnt == argcnt { return f } } return nil } func (c *compiler) appendBuiltin(name string, argcnt int) func() { setjump := c.lazy(func() *code { return &code{op: opjump, v: len(c.codes)} }) c.appendCodeInfo(name) c.builtinScope.funcs = append( c.builtinScope.funcs, &funcinfo{name, len(c.codes), argcnt}, ) return func() { setjump() c.appendCodeInfo("end of " + name) } } func (c *compiler) newScope() *scopeinfo { i := c.scopecnt // do not use len(c.scopes) because it pops c.scopecnt++ return &scopeinfo{id: i} } func (c *compiler) newScopeDepth() func() { scope := c.scopes[len(c.scopes)-1] l, m := len(scope.variables), len(scope.funcs) scope.depth++ return func() { scope.depth-- scope.variables = scope.variables[:l] scope.funcs = scope.funcs[:m] } } func (c *compiler) compileFuncDef(e *FuncDef, builtin bool) error { var scope *scopeinfo if builtin { scope = c.builtinScope } else { scope = c.scopes[len(c.scopes)-1] } defer c.lazy(func() *code { return &code{op: opjump, v: len(c.codes)} })() c.appendCodeInfo(e.Name) scope.funcs = append(scope.funcs, &funcinfo{e.Name, len(c.codes), len(e.Args)}) defer func(scopes []*scopeinfo, variables []string) { c.scopes, c.variables = scopes, variables }(c.scopes, c.variables) c.variables = c.variables[len(c.variables):] scope = c.newScope() if builtin { c.scopes = []*scopeinfo{c.builtinScope, scope} } else { c.scopes = append(c.scopes, scope) } defer c.lazy(func() *code { return &code{op: opscope, v: [3]int{scope.id, scope.variablecnt, len(e.Args)}} })() if len(e.Args) > 0 { type varIndex struct { name string index [2]int } vis := make([]varIndex, 0, len(e.Args)) v := c.newVariable() c.append(&code{op: opstore, v: v}) for _, arg := range e.Args { if arg[0] == '$' { c.appendCodeInfo(arg[1:]) w := c.createVariable(arg[1:]) c.append(&code{op: opstore, v: w}) vis = append(vis, varIndex{arg, w}) } else { c.appendCodeInfo(arg) c.append(&code{op: opstore, v: c.createVariable(arg)}) } } for _, w := range vis { c.append(&code{op: opload, v: v}) c.append(&code{op: opexpbegin}) c.append(&code{op: opload, v: w.index}) c.append(&code{op: opcallpc}) c.appendCodeInfo(w.name) c.append(&code{op: opstore, v: c.pushVariable(w.name)}) c.append(&code{op: opexpend}) } c.append(&code{op: opload, v: v}) } if err := c.compile(e.Body); err != nil { return err } c.appendCodeInfo("end of " + e.Name) return nil } func (c *compiler) compileQuery(e *Query) error { for _, fd := range e.FuncDefs { if err := c.compileFuncDef(fd, false); err != nil { return err } } if e.Func != "" { switch e.Func { case ".": return c.compileTerm(&Term{Type: TermTypeIdentity}) case "..": return c.compileTerm(&Term{Type: TermTypeRecurse}) case "null": return c.compileTerm(&Term{Type: TermTypeNull}) case "true": return c.compileTerm(&Term{Type: TermTypeTrue}) case "false": return c.compileTerm(&Term{Type: TermTypeFalse}) default: return c.compileFunc(&Func{Name: e.Func}) } } else if e.Term != nil { return c.compileTerm(e.Term) } switch e.Op { case OpPipe: if err := c.compileQuery(e.Left); err != nil { return err } return c.compileQuery(e.Right) case OpComma: return c.compileComma(e.Left, e.Right) case OpAlt: return c.compileAlt(e.Left, e.Right) case OpAssign, OpModify, OpUpdateAdd, OpUpdateSub, OpUpdateMul, OpUpdateDiv, OpUpdateMod, OpUpdateAlt: return c.compileQueryUpdate(e.Left, e.Right, e.Op) case OpOr: return c.compileIf( &If{ Cond: e.Left, Then: &Query{Term: &Term{Type: TermTypeTrue}}, Else: &Query{Term: &Term{Type: TermTypeIf, If: &If{ Cond: e.Right, Then: &Query{Term: &Term{Type: TermTypeTrue}}, Else: &Query{Term: &Term{Type: TermTypeFalse}}, }}}, }, ) case OpAnd: return c.compileIf( &If{ Cond: e.Left, Then: &Query{Term: &Term{Type: TermTypeIf, If: &If{ Cond: e.Right, Then: &Query{Term: &Term{Type: TermTypeTrue}}, Else: &Query{Term: &Term{Type: TermTypeFalse}}, }}}, Else: &Query{Term: &Term{Type: TermTypeFalse}}, }, ) default: return c.compileCall( e.Op.getFunc(), []*Query{e.Left, e.Right}, ) } } func (c *compiler) compileComma(l, r *Query) error { setfork := c.lazy(func() *code { return &code{op: opfork, v: len(c.codes)} }) if err := c.compileQuery(l); err != nil { return err } defer c.lazy(func() *code { return &code{op: opjump, v: len(c.codes)} })() setfork() return c.compileQuery(r) } func (c *compiler) compileAlt(l, r *Query) error { c.append(&code{op: oppush, v: false}) found := c.newVariable() c.append(&code{op: opstore, v: found}) setfork := c.lazy(func() *code { return &code{op: opfork, v: len(c.codes)} // opload found }) if err := c.compileQuery(l); err != nil { return err } c.append(&code{op: opdup}) c.append(&code{op: opjumpifnot, v: len(c.codes) + 4}) // oppop c.append(&code{op: oppush, v: true}) // found some value c.append(&code{op: opstore, v: found}) defer c.lazy(func() *code { return &code{op: opjump, v: len(c.codes)} })() c.append(&code{op: oppop}) c.append(&code{op: opbacktrack}) setfork() c.append(&code{op: opload, v: found}) c.append(&code{op: opjumpifnot, v: len(c.codes) + 3}) c.append(&code{op: opbacktrack}) // if found, backtrack c.append(&code{op: oppop}) return c.compileQuery(r) } func (c *compiler) compileQueryUpdate(l, r *Query, op Operator) error { switch op { case OpAssign: // optimize assignment operator with constant indexing and slicing // .foo.[0].[1:2] = f => setpath(["foo",0,{"start":1,"end":2}]; f) if xs := l.toIndices(nil); xs != nil { // ref: compileCall v := c.newVariable() c.append(&code{op: opstore, v: v}) c.append(&code{op: opload, v: v}) if err := c.compileQuery(r); err != nil { return err } c.append(&code{op: oppush, v: xs}) c.append(&code{op: opload, v: v}) c.append(&code{op: opcall, v: [3]any{internalFuncs["setpath"].callback, 2, "setpath"}}) return nil } fallthrough case OpModify: return c.compileFunc( &Func{ Name: op.getFunc(), Args: []*Query{l, r}, }, ) default: name := "$%0" c.append(&code{op: opdup}) if err := c.compileQuery(r); err != nil { return err } c.append(&code{op: opstore, v: c.pushVariable(name)}) return c.compileFunc( &Func{ Name: "_modify", Args: []*Query{ l, {Term: &Term{ Type: TermTypeFunc, Func: &Func{ Name: op.getFunc(), Args: []*Query{ {Term: &Term{Type: TermTypeIdentity}}, {Func: name}, }, }, }}, }, }, ) } } func (c *compiler) compileBind(e *Term, b *Bind) error { c.append(&code{op: opdup}) c.append(&code{op: opexpbegin}) if err := c.compileTerm(e); err != nil { return err } var pc int var vs [][2]int for i, p := range b.Patterns { var pcc int var err error if i < len(b.Patterns)-1 { defer c.lazy(func() *code { return &code{op: opforkalt, v: pcc} })() } if 0 < i { for _, v := range vs { c.append(&code{op: oppush, v: nil}) c.append(&code{op: opstore, v: v}) } } if vs, err = c.compilePattern(vs[:0], p); err != nil { return err } if i < len(b.Patterns)-1 { defer c.lazy(func() *code { return &code{op: opjump, v: pc} })() pcc = len(c.codes) } } if len(b.Patterns) > 1 { pc = len(c.codes) } if len(b.Patterns) == 1 && c.codes[len(c.codes)-2].op == opexpbegin { c.codes[len(c.codes)-2].op = opnop } else { c.append(&code{op: opexpend}) } return c.compileQuery(b.Body) } func (c *compiler) compilePattern(vs [][2]int, p *Pattern) ([][2]int, error) { var err error c.appendCodeInfo(p) if p.Name != "" { v := c.pushVariable(p.Name) c.append(&code{op: opstore, v: v}) return append(vs, v), nil } else if len(p.Array) > 0 { v := c.newVariable() c.append(&code{op: opstore, v: v}) for i, p := range p.Array { c.append(&code{op: opload, v: v}) c.append(&code{op: opindexarray, v: i}) if vs, err = c.compilePattern(vs, p); err != nil { return nil, err } } return vs, nil } else if len(p.Object) > 0 { v := c.newVariable() c.append(&code{op: opstore, v: v}) for _, kv := range p.Object { var key, name string c.append(&code{op: opload, v: v}) if key = kv.Key; key != "" { if key[0] == '$' { key, name = key[1:], key } } else if kv.KeyString != nil { if key = kv.KeyString.Str; key == "" { if err := c.compileString(kv.KeyString, nil); err != nil { return nil, err } } } else if kv.KeyQuery != nil { if err := c.compileQuery(kv.KeyQuery); err != nil { return nil, err } } if key != "" { c.append(&code{op: opindex, v: key}) } else { c.append(&code{op: opload, v: v}) c.append(&code{op: oppush, v: nil}) // ref: compileCall c.append(&code{op: opcall, v: [3]any{internalFuncs["_index"].callback, 2, "_index"}}) } if name != "" { if kv.Val != nil { c.append(&code{op: opdup}) } if vs, err = c.compilePattern(vs, &Pattern{Name: name}); err != nil { return nil, err } } if kv.Val != nil { if vs, err = c.compilePattern(vs, kv.Val); err != nil { return nil, err } } } return vs, nil } else { return nil, fmt.Errorf("invalid pattern: %s", p) } } func (c *compiler) compileIf(e *If) error { c.appendCodeInfo(e) c.append(&code{op: opdup}) // duplicate the value for then or else clause c.append(&code{op: opexpbegin}) pc := len(c.codes) f := c.newScopeDepth() if err := c.compileQuery(e.Cond); err != nil { return err } f() if pc == len(c.codes) { c.codes = c.codes[:pc-1] } else { c.append(&code{op: opexpend}) } pcc := len(c.codes) setjumpifnot := c.lazy(func() *code { return &code{op: opjumpifnot, v: len(c.codes)} // skip then clause }) f = c.newScopeDepth() if err := c.compileQuery(e.Then); err != nil { return err } f() defer c.lazy(func() *code { return &code{op: opjump, v: len(c.codes)} })() setjumpifnot() if len(e.Elif) > 0 { return c.compileIf(&If{e.Elif[0].Cond, e.Elif[0].Then, e.Elif[1:], e.Else}) } if e.Else != nil { defer c.newScopeDepth()() defer func() { // optimize constant results // opdup, ..., opjumpifnot, opconst, opjump, opconst // => opnop, ..., opjumpifnot, oppush, opjump, oppush if pcc+4 == len(c.codes) && c.codes[pcc+1] != nil && c.codes[pcc+1].op == opconst && c.codes[pcc+3] != nil && c.codes[pcc+3].op == opconst { c.codes[pc-2].op = opnop c.codes[pcc+1].op = oppush c.codes[pcc+3].op = oppush } }() return c.compileQuery(e.Else) } return nil } func (c *compiler) compileTry(e *Try) error { c.appendCodeInfo(e) setforktrybegin := c.lazy(func() *code { return &code{op: opforktrybegin, v: len(c.codes)} }) f := c.newScopeDepth() if err := c.compileQuery(e.Body); err != nil { return err } f() c.append(&code{op: opforktryend}) defer c.lazy(func() *code { return &code{op: opjump, v: len(c.codes)} })() setforktrybegin() if e.Catch != nil { defer c.newScopeDepth()() return c.compileQuery(e.Catch) } c.append(&code{op: opbacktrack}) return nil } func (c *compiler) compileReduce(e *Reduce) error { c.appendCodeInfo(e) defer c.newScopeDepth()() setfork := c.lazy(func() *code { return &code{op: opfork, v: len(c.codes)} }) c.append(&code{op: opdup}) v := c.newVariable() f := c.newScopeDepth() if err := c.compileQuery(e.Start); err != nil { return err } f() c.append(&code{op: opstore, v: v}) if err := c.compileTerm(e.Term); err != nil { return err } if _, err := c.compilePattern(nil, e.Pattern); err != nil { return err } c.append(&code{op: opload, v: v}) f = c.newScopeDepth() if err := c.compileQuery(e.Update); err != nil { return err } f() c.append(&code{op: opstore, v: v}) c.append(&code{op: opbacktrack}) setfork() c.append(&code{op: oppop}) c.append(&code{op: opload, v: v}) return nil } func (c *compiler) compileForeach(e *Foreach) error { c.appendCodeInfo(e) defer c.newScopeDepth()() c.append(&code{op: opdup}) v := c.newVariable() f := c.newScopeDepth() if err := c.compileQuery(e.Start); err != nil { return err } f() c.append(&code{op: opstore, v: v}) if err := c.compileTerm(e.Term); err != nil { return err } if _, err := c.compilePattern(nil, e.Pattern); err != nil { return err } c.append(&code{op: opload, v: v}) f = c.newScopeDepth() if err := c.compileQuery(e.Update); err != nil { return err } f() c.append(&code{op: opdup}) c.append(&code{op: opstore, v: v}) if e.Extract != nil { defer c.newScopeDepth()() return c.compileQuery(e.Extract) } return nil } func (c *compiler) compileLabel(e *Label) error { c.appendCodeInfo(e) v := c.pushVariable("$%" + e.Ident[1:]) c.append(&code{op: opforklabel, v: v}) return c.compileQuery(e.Body) } func (c *compiler) compileBreak(label string) error { v, err := c.lookupVariable("$%" + label[1:]) if err != nil { return &breakError{label, nil} } c.append(&code{op: oppop}) c.append(&code{op: opload, v: v}) c.append(&code{op: opcall, v: [3]any{funcBreak(label), 0, "_break"}}) return nil } func funcBreak(label string) func(any, []any) any { return func(v any, _ []any) any { return &breakError{label, v} } } func (c *compiler) compileTerm(e *Term) error { if len(e.SuffixList) > 0 { s := e.SuffixList[len(e.SuffixList)-1] t := *e // clone without changing e t.SuffixList = t.SuffixList[:len(e.SuffixList)-1] return c.compileTermSuffix(&t, s) } switch e.Type { case TermTypeIdentity: return nil case TermTypeRecurse: return c.compileFunc(&Func{Name: "recurse"}) case TermTypeNull: c.append(&code{op: opconst, v: nil}) return nil case TermTypeTrue: c.append(&code{op: opconst, v: true}) return nil case TermTypeFalse: c.append(&code{op: opconst, v: false}) return nil case TermTypeIndex: return c.compileIndex(&Term{Type: TermTypeIdentity}, e.Index) case TermTypeFunc: return c.compileFunc(e.Func) case TermTypeObject: return c.compileObject(e.Object) case TermTypeArray: return c.compileArray(e.Array) case TermTypeNumber: c.append(&code{op: opconst, v: toNumber(e.Number)}) return nil case TermTypeUnary: return c.compileUnary(e.Unary) case TermTypeFormat: return c.compileFormat(e.Format, e.Str) case TermTypeString: return c.compileString(e.Str, nil) case TermTypeIf: return c.compileIf(e.If) case TermTypeTry: return c.compileTry(e.Try) case TermTypeReduce: return c.compileReduce(e.Reduce) case TermTypeForeach: return c.compileForeach(e.Foreach) case TermTypeLabel: return c.compileLabel(e.Label) case TermTypeBreak: return c.compileBreak(e.Break) case TermTypeQuery: defer c.newScopeDepth()() return c.compileQuery(e.Query) default: panic("invalid term: " + e.String()) } } func (c *compiler) compileIndex(e *Term, x *Index) error { if k := x.toIndexKey(); k != nil { if err := c.compileTerm(e); err != nil { return err } c.appendCodeInfo(x) c.append(&code{op: opindex, v: k}) return nil } c.appendCodeInfo(x) if x.Str != nil { return c.compileCall("_index", []*Query{{Term: e}, {Term: &Term{Type: TermTypeString, Str: x.Str}}}) } if !x.IsSlice { return c.compileCall("_index", []*Query{{Term: e}, x.Start}) } if x.Start == nil { return c.compileCall("_slice", []*Query{{Term: e}, x.End, {Term: &Term{Type: TermTypeNull}}}) } if x.End == nil { return c.compileCall("_slice", []*Query{{Term: e}, {Term: &Term{Type: TermTypeNull}}, x.Start}) } return c.compileCall("_slice", []*Query{{Term: e}, x.End, x.Start}) } func (c *compiler) compileFunc(e *Func) error { if len(e.Args) == 0 { if f, v := c.lookupFuncOrVariable(e.Name); f != nil { return c.compileCallPc(f, e.Args) } else if v != nil { if e.Name[0] == '$' { c.append(&code{op: oppop}) c.append(&code{op: opload, v: v.index}) } else { c.append(&code{op: opload, v: v.index}) c.append(&code{op: opcallpc}) } return nil } else if e.Name == "$ENV" || e.Name == "env" { env := make(map[string]any) if c.environLoader != nil { for _, kv := range c.environLoader() { if i := strings.IndexByte(kv, '='); i > 0 { env[kv[:i]] = kv[i+1:] } } } c.append(&code{op: opconst, v: env}) return nil } else if e.Name[0] == '$' { return &variableNotFoundError{e.Name} } } else { for i := len(c.scopes) - 1; i >= 0; i-- { s := c.scopes[i] for j := len(s.funcs) - 1; j >= 0; j-- { if f := s.funcs[j]; f.name == e.Name && f.argcnt == len(e.Args) { return c.compileCallPc(f, e.Args) } } } } if f := c.lookupBuiltin(e.Name, len(e.Args)); f != nil { return c.compileCallPc(f, e.Args) } if fds, ok := builtinFuncDefs[e.Name]; ok { for _, fd := range fds { if len(fd.Args) == len(e.Args) { if err := c.compileFuncDef(fd, true); err != nil { return err } break } } if len(fds) == 0 { switch e.Name { case "_assign": c.compileAssign() case "_modify": c.compileModify() } } if f := c.lookupBuiltin(e.Name, len(e.Args)); f != nil { return c.compileCallPc(f, e.Args) } } if fn, ok := internalFuncs[e.Name]; ok && fn.accept(len(e.Args)) { switch e.Name { case "empty": c.append(&code{op: opbacktrack}) return nil case "path": c.append(&code{op: oppathbegin}) if err := c.compileCall(e.Name, e.Args); err != nil { return err } c.codes[len(c.codes)-1] = &code{op: oppathend} return nil case "builtins": return c.compileCallInternal( [3]any{c.funcBuiltins, 0, e.Name}, e.Args, true, -1, ) case "input": if c.inputIter == nil { return &inputNotAllowedError{} } return c.compileCallInternal( [3]any{c.funcInput, 0, e.Name}, e.Args, true, -1, ) case "modulemeta": return c.compileCallInternal( [3]any{c.funcModulemeta, 0, e.Name}, e.Args, true, -1, ) default: return c.compileCall(e.Name, e.Args) } } if fn, ok := c.customFuncs[e.Name]; ok && fn.accept(len(e.Args)) { if err := c.compileCallInternal( [3]any{fn.callback, len(e.Args), e.Name}, e.Args, true, -1, ); err != nil { return err } if fn.iter { c.append(&code{op: opiter}) } return nil } return &funcNotFoundError{e} } // Appends the compiled code for the assignment operator (`=`) to maximize // performance. Originally the operator was implemented as follows. // // def _assign(p; $x): reduce path(p) as $q (.; setpath($q; $x)); // // To overcome the difficulty of reducing allocations on `setpath`, we use the // `allocator` type and track the allocated addresses during the reduction. func (c *compiler) compileAssign() { defer c.appendBuiltin("_assign", 2)() scope := c.newScope() v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1} x, a := [2]int{scope.id, 2}, [2]int{scope.id, 3} // Cannot reuse v, p due to backtracking in x. w, q := [2]int{scope.id, 4}, [2]int{scope.id, 5} c.appends( &code{op: opscope, v: [3]int{scope.id, 6, 2}}, &code{op: opstore, v: v}, // def _assign(p; $x): &code{op: opstore, v: p}, &code{op: opstore, v: x}, &code{op: opload, v: v}, &code{op: opexpbegin}, &code{op: opload, v: x}, &code{op: opcallpc}, &code{op: opstore, v: x}, &code{op: opexpend}, &code{op: oppush, v: nil}, &code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}}, &code{op: opstore, v: a}, &code{op: opload, v: v}, &code{op: opfork, v: len(c.codes) + 30}, // reduce [L1] &code{op: opdup}, &code{op: opstore, v: w}, &code{op: oppathbegin}, // path(p) &code{op: opload, v: p}, &code{op: opcallpc}, &code{op: opload, v: w}, &code{op: oppathend}, &code{op: opstore, v: q}, // as $q (.; &code{op: opload, v: a}, // setpath($q; $x) &code{op: opload, v: x}, &code{op: opload, v: q}, &code{op: opload, v: w}, &code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}}, &code{op: opstore, v: w}, &code{op: opbacktrack}, // ); &code{op: oppop}, // [L1] &code{op: opload, v: w}, &code{op: opret}, ) } // Appends the compiled code for the update-assignment operator (`|=`) to // maximize performance. We use the `allocator` type, just like `_assign/2`. func (c *compiler) compileModify() { defer c.appendBuiltin("_modify", 2)() scope := c.newScope() v, p := [2]int{scope.id, 0}, [2]int{scope.id, 1} f, d := [2]int{scope.id, 2}, [2]int{scope.id, 3} a, l := [2]int{scope.id, 4}, [2]int{scope.id, 5} c.appends( &code{op: opscope, v: [3]int{scope.id, 6, 2}}, &code{op: opstore, v: v}, // def _modify(p; f): &code{op: opstore, v: p}, &code{op: opstore, v: f}, &code{op: oppush, v: []any{}}, &code{op: opstore, v: d}, &code{op: oppush, v: nil}, &code{op: opcall, v: [3]any{funcAllocator, 0, "_allocator"}}, &code{op: opstore, v: a}, &code{op: opload, v: v}, &code{op: opfork, v: len(c.codes) + 39}, // reduce [L1] &code{op: oppathbegin}, // path(p) &code{op: opload, v: p}, &code{op: opcallpc}, &code{op: opload, v: v}, &code{op: oppathend}, &code{op: opstore, v: p}, // as $p (.; &code{op: opforklabel, v: l}, // label $l | &code{op: opload, v: v}, // &code{op: opfork, v: len(c.codes) + 36}, // [L2] &code{op: oppop}, // (getpath($p) | &code{op: opload, v: a}, &code{op: opload, v: p}, &code{op: opload, v: v}, &code{op: opcall, v: [3]any{internalFuncs["getpath"].callback, 1, "getpath"}}, &code{op: opload, v: f}, // f) &code{op: opcallpc}, &code{op: opload, v: p}, // setpath($p; ...) &code{op: opload, v: v}, &code{op: opcall, v: [3]any{funcSetpathWithAllocator, 3, "_setpath"}}, &code{op: opstore, v: v}, &code{op: opload, v: v}, // ., break $l &code{op: opfork, v: len(c.codes) + 34}, // [L4] &code{op: opjump, v: len(c.codes) + 38}, // [L3] &code{op: opload, v: l}, // [L4] &code{op: opcall, v: [3]any{funcBreak(""), 0, "_break"}}, &code{op: opload, v: p}, // append $p to $d [L2] &code{op: opappend, v: d}, // &code{op: opbacktrack}, // ) | [L3] &code{op: oppop}, // delpaths($d); [L1] &code{op: opload, v: a}, &code{op: opload, v: d}, &code{op: opload, v: v}, &code{op: opcall, v: [3]any{funcDelpathsWithAllocator, 2, "_delpaths"}}, &code{op: opret}, ) } func (c *compiler) funcBuiltins(any, []any) any { type funcNameArity struct { name string arity int } var xs []*funcNameArity for _, fds := range builtinFuncDefs { for _, fd := range fds { if fd.Name[0] != '_' { xs = append(xs, &funcNameArity{fd.Name, len(fd.Args)}) } } } for name, fn := range internalFuncs { if name[0] != '_' { for i, cnt := 0, fn.argcount; cnt > 0; i, cnt = i+1, cnt>>1 { if cnt&1 > 0 { xs = append(xs, &funcNameArity{name, i}) } } } } for name, fn := range c.customFuncs { if name[0] != '_' { for i, cnt := 0, fn.argcount; cnt > 0; i, cnt = i+1, cnt>>1 { if cnt&1 > 0 { xs = append(xs, &funcNameArity{name, i}) } } } } sort.Slice(xs, func(i, j int) bool { return xs[i].name < xs[j].name || xs[i].name == xs[j].name && xs[i].arity < xs[j].arity }) ys := make([]any, len(xs)) for i, x := range xs { ys[i] = x.name + "/" + strconv.Itoa(x.arity) } return ys } func (c *compiler) funcInput(any, []any) any { v, ok := c.inputIter.Next() if !ok { return errors.New("break") } return normalizeNumbers(v) } func (c *compiler) funcModulemeta(v any, _ []any) any { s, ok := v.(string) if !ok { return &func0TypeError{"modulemeta", v} } if c.moduleLoader == nil { return fmt.Errorf("cannot load module: %q", s) } var q *Query var err error if moduleLoader, ok := c.moduleLoader.(interface { LoadModuleWithMeta(string, map[string]any) (*Query, error) }); ok { if q, err = moduleLoader.LoadModuleWithMeta(s, nil); err != nil { return err } } else if moduleLoader, ok := c.moduleLoader.(interface { LoadModule(string) (*Query, error) }); ok { if q, err = moduleLoader.LoadModule(s); err != nil { return err } } meta := q.Meta.ToValue() if meta == nil { meta = make(map[string]any) } deps := []any{} for _, i := range q.Imports { v := i.Meta.ToValue() if v == nil { v = make(map[string]any) } else { for k := range v { // dirty hack to remove the internal fields if strings.HasPrefix(k, "$$") { delete(v, k) } } } if i.ImportPath == "" { v["relpath"] = i.IncludePath } else { v["relpath"] = i.ImportPath } if err != nil { return err } if i.ImportAlias != "" { v["as"] = strings.TrimPrefix(i.ImportAlias, "$") } v["is_data"] = strings.HasPrefix(i.ImportAlias, "$") deps = append(deps, v) } meta["deps"] = deps return meta } func (c *compiler) compileObject(e *Object) error { c.appendCodeInfo(e) if len(e.KeyVals) == 0 { c.append(&code{op: opconst, v: map[string]any{}}) return nil } defer c.newScopeDepth()() v := c.newVariable() c.append(&code{op: opstore, v: v}) pc := len(c.codes) for _, kv := range e.KeyVals { if err := c.compileObjectKeyVal(v, kv); err != nil { return err } } c.append(&code{op: opobject, v: len(e.KeyVals)}) // optimize constant objects l := len(e.KeyVals) if pc+l*3+1 != len(c.codes) { return nil } for i := 0; i < l; i++ { if c.codes[pc+i*3].op != oppush || c.codes[pc+i*3+1].op != opload || c.codes[pc+i*3+2].op != opconst { return nil } } w := make(map[string]any, l) for i := 0; i < l; i++ { w[c.codes[pc+i*3].v.(string)] = c.codes[pc+i*3+2].v } c.codes[pc-1] = &code{op: opconst, v: w} c.codes = c.codes[:pc] return nil } func (c *compiler) compileObjectKeyVal(v [2]int, kv *ObjectKeyVal) error { if key := kv.Key; key != "" { if key[0] == '$' { if kv.Val == nil { // {$foo} == {foo:$foo} c.append(&code{op: oppush, v: key[1:]}) } c.append(&code{op: opload, v: v}) if err := c.compileFunc(&Func{Name: key}); err != nil { return err } } else { c.append(&code{op: oppush, v: key}) if kv.Val == nil { // {foo} == {foo:.foo} c.append(&code{op: opload, v: v}) c.append(&code{op: opindex, v: key}) } } } else if key := kv.KeyString; key != nil { if key.Queries == nil { c.append(&code{op: oppush, v: key.Str}) if kv.Val == nil { // {"foo"} == {"foo":.["foo"]} c.append(&code{op: opload, v: v}) c.append(&code{op: opindex, v: key.Str}) } } else { c.append(&code{op: opload, v: v}) if err := c.compileString(key, nil); err != nil { return err } if kv.Val == nil { c.append(&code{op: opdup}) c.append(&code{op: opload, v: v}) c.append(&code{op: oppush, v: nil}) // ref: compileCall c.append(&code{op: opcall, v: [3]any{internalFuncs["_index"].callback, 2, "_index"}}) } } } else if kv.KeyQuery != nil { c.append(&code{op: opload, v: v}) f := c.newScopeDepth() if err := c.compileQuery(kv.KeyQuery); err != nil { return err } f() } if kv.Val != nil { c.append(&code{op: opload, v: v}) for _, e := range kv.Val.Queries { if err := c.compileQuery(e); err != nil { return err } } } return nil } func (c *compiler) compileArray(e *Array) error { c.appendCodeInfo(e) if e.Query == nil { c.append(&code{op: opconst, v: []any{}}) return nil } c.append(&code{op: oppush, v: []any{}}) arr := c.newVariable() c.append(&code{op: opstore, v: arr}) pc := len(c.codes) setfork := c.lazy(func() *code { return &code{op: opfork, v: len(c.codes)} }) defer c.newScopeDepth()() if err := c.compileQuery(e.Query); err != nil { return err } c.append(&code{op: opappend, v: arr}) c.append(&code{op: opbacktrack}) setfork() c.append(&code{op: oppop}) c.append(&code{op: opload, v: arr}) if e.Query.Op == OpPipe { return nil } // optimize constant arrays if (len(c.codes)-pc)%3 != 0 { return nil } l := (len(c.codes) - pc - 3) / 3 for i := 0; i < l; i++ { if c.codes[pc+i].op != opfork || c.codes[pc+i*2+l].op != opconst || (i < l-1 && c.codes[pc+i*2+l+1].op != opjump) { return nil } } v := make([]any, l) for i := 0; i < l; i++ { v[i] = c.codes[pc+i*2+l].v } c.codes[pc-2] = &code{op: opconst, v: v} c.codes = c.codes[:pc-1] return nil } func (c *compiler) compileUnary(e *Unary) error { c.appendCodeInfo(e) if v := e.toNumber(); v != nil { c.append(&code{op: opconst, v: v}) return nil } if err := c.compileTerm(e.Term); err != nil { return err } switch e.Op { case OpAdd: return c.compileCall("_plus", nil) case OpSub: return c.compileCall("_negate", nil) default: return fmt.Errorf("unexpected operator in Unary: %s", e.Op) } } func (c *compiler) compileFormat(format string, str *String) error { f := formatToFunc(format) if f == nil { f = &Func{ Name: "format", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: format[1:]}}}}, } } if str == nil { return c.compileFunc(f) } return c.compileString(str, f) } func formatToFunc(format string) *Func { switch format { case "@text": return &Func{Name: "tostring"} case "@json": return &Func{Name: "tojson"} case "@html": return &Func{Name: "_tohtml"} case "@uri": return &Func{Name: "_touri"} case "@urid": return &Func{Name: "_tourid"} case "@csv": return &Func{Name: "_tocsv"} case "@tsv": return &Func{Name: "_totsv"} case "@sh": return &Func{Name: "_tosh"} case "@base64": return &Func{Name: "_tobase64"} case "@base64d": return &Func{Name: "_tobase64d"} default: return nil } } func (c *compiler) compileString(s *String, f *Func) error { if s.Queries == nil { c.append(&code{op: opconst, v: s.Str}) return nil } if f == nil { f = &Func{Name: "tostring"} } var q *Query for _, e := range s.Queries { if e.Term.Str == nil { e = &Query{Left: e, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeFunc, Func: f}}} } if q == nil { q = e } else { q = &Query{Left: q, Op: OpAdd, Right: e} } } return c.compileQuery(q) } func (c *compiler) compileTermSuffix(e *Term, s *Suffix) error { if s.Index != nil { return c.compileIndex(e, s.Index) } else if s.Iter { if err := c.compileTerm(e); err != nil { return err } c.append(&code{op: opiter}) return nil } else if s.Optional { if len(e.SuffixList) > 0 { if u := e.SuffixList[len(e.SuffixList)-1].toTerm(); u != nil { // no need to clone (ref: compileTerm) e.SuffixList = e.SuffixList[:len(e.SuffixList)-1] if err := c.compileTerm(e); err != nil { return err } e = u } } return c.compileTry(&Try{Body: &Query{Term: e}}) } else if s.Bind != nil { return c.compileBind(e, s.Bind) } else { return fmt.Errorf("invalid suffix: %s", s) } } func (c *compiler) compileCall(name string, args []*Query) error { fn := internalFuncs[name] var indexing int switch name { case "_index", "_slice": indexing = 1 case "getpath": indexing = 0 default: indexing = -1 } if err := c.compileCallInternal( [3]any{fn.callback, len(args), name}, args, true, indexing, ); err != nil { return err } if fn.iter { c.append(&code{op: opiter}) } return nil } func (c *compiler) compileCallPc(fn *funcinfo, args []*Query) error { return c.compileCallInternal(fn.pc, args, false, -1) } func (c *compiler) compileCallInternal( fn any, args []*Query, internal bool, indexing int, ) error { if len(args) == 0 { c.append(&code{op: opcall, v: fn}) return nil } v := c.newVariable() c.append(&code{op: opstore, v: v}) if indexing >= 0 { c.append(&code{op: opexpbegin}) } for i := len(args) - 1; i >= 0; i-- { pc := len(c.codes) + 1 // skip opjump (ref: compileFuncDef) name := "lambda:" + strconv.Itoa(pc) if err := c.compileFuncDef(&FuncDef{Name: name, Body: args[i]}, false); err != nil { return err } if internal { switch len(c.codes) - pc { case 2: // optimize identity argument (opscope, opret) j := len(c.codes) - 3 c.codes[j] = &code{op: opload, v: v} c.codes = c.codes[:j+1] s := c.scopes[len(c.scopes)-1] s.funcs = s.funcs[:len(s.funcs)-1] c.deleteCodeInfo(name) case 3: // optimize one instruction argument (opscope, opX, opret) j := len(c.codes) - 4 if c.codes[j+2].op == opconst { c.codes[j] = &code{op: oppush, v: c.codes[j+2].v} c.codes = c.codes[:j+1] } else { c.codes[j] = &code{op: opload, v: v} c.codes[j+1] = c.codes[j+2] c.codes = c.codes[:j+2] } s := c.scopes[len(c.scopes)-1] s.funcs = s.funcs[:len(s.funcs)-1] c.deleteCodeInfo(name) default: c.append(&code{op: opload, v: v}) c.append(&code{op: oppushpc, v: pc}) c.append(&code{op: opcallpc}) } } else { c.append(&code{op: oppushpc, v: pc}) } if i == indexing { if c.codes[len(c.codes)-2].op == opexpbegin { c.codes[len(c.codes)-2] = c.codes[len(c.codes)-1] c.codes = c.codes[:len(c.codes)-1] } else { c.append(&code{op: opexpend}) } } } if indexing > 0 { c.append(&code{op: oppush, v: nil}) } else { c.append(&code{op: opload, v: v}) } c.append(&code{op: opcall, v: fn}) return nil } func (c *compiler) append(code *code) { c.codes = append(c.codes, code) } func (c *compiler) appends(codes ...*code) { c.codes = append(c.codes, codes...) } func (c *compiler) lazy(f func() *code) func() { i := len(c.codes) c.codes = append(c.codes, nil) return func() { c.codes[i] = f() } } func (c *compiler) optimizeTailRec() { var pcs []int scopes := map[int]bool{} L: for i, l := 0, len(c.codes); i < l; i++ { switch c.codes[i].op { case opscope: pcs = append(pcs, i) if v := c.codes[i].v.([3]int); v[2] == 0 { scopes[i] = v[1] == 0 } case opcall: var canjump bool if j, ok := c.codes[i].v.(int); !ok || len(pcs) == 0 || pcs[len(pcs)-1] != j { break } else if canjump, ok = scopes[j]; !ok { break } for j := i + 1; j < l; { switch c.codes[j].op { case opjump: j = c.codes[j].v.(int) case opret: if canjump { c.codes[i].op = opjump c.codes[i].v = pcs[len(pcs)-1] + 1 } else { c.codes[i].op = opcallrec } continue L default: continue L } } case opret: if len(pcs) == 0 { break L } pcs = pcs[:len(pcs)-1] } } } func (c *compiler) optimizeCodeOps() { for i, next := len(c.codes)-1, (*code)(nil); i >= 0; i-- { code := c.codes[i] switch code.op { case oppush, opdup, opload: switch next.op { case oppop: code.op = opnop next.op = opnop case opconst: code.op = opnop next.op = oppush } case opjump, opjumpifnot: if j := code.v.(int); j-1 == i { code.op = opnop } else if next = c.codes[j]; next.op == opjump { code.v = next.v } } next = code } } gojq-0.12.13/compiler_test.go000066400000000000000000000154471443612442000160310ustar00rootroot00000000000000package gojq_test import ( "context" "fmt" "log" "os" "reflect" "sync" "testing" "time" "unsafe" "github.com/itchyny/gojq" ) func ExampleCompile() { query, err := gojq.Parse(".[] | .foo") if err != nil { log.Fatalln(err) } code, err := gojq.Compile(query) if err != nil { log.Fatalln(err) } iter := code.Run([]any{ nil, "string", 42, []any{"foo"}, map[string]any{"foo": 42}, }) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { fmt.Println(err) continue } fmt.Printf("%#v\n", v) } // Output: // // expected an object but got: string ("string") // expected an object but got: number (42) // expected an object but got: array (["foo"]) // 42 } func ExampleCode_Run() { query, err := gojq.Parse(".foo") if err != nil { log.Fatalln(err) } code, err := gojq.Compile(query) if err != nil { log.Fatalln(err) } input := map[string]any{"foo": 42} iter := code.Run(input) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { log.Fatalln(err) } fmt.Printf("%#v\n", v) } // Output: // 42 } func ExampleCode_RunWithContext() { query, err := gojq.Parse("def f: f; f, f") if err != nil { log.Fatalln(err) } code, err := gojq.Compile(query) if err != nil { log.Fatalln(err) } ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() iter := code.RunWithContext(ctx, nil) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { fmt.Println(err) continue } _ = v } // Output: // context deadline exceeded } func TestCodeCompile_OptimizeConstants(t *testing.T) { query, err := gojq.Parse(`[1,{foo:2,"bar":+3},[-4]]`) if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } codes := reflect.ValueOf(code).Elem().FieldByName("codes") if got, expected := codes.Len(), 3; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if expected := []any{ 1, map[string]any{"foo": 2, "bar": 3}, []any{-4}, }; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestCodeCompile_OptimizeIndexSlice(t *testing.T) { query, err := gojq.Parse(`.foo."bar".["baz"].[-1]."".[0:1]`) if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } codes := reflect.ValueOf(code).Elem().FieldByName("codes") if got, expected := codes.Len(), 8; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if got != nil { t.Errorf("expected: %v, got: %v", nil, got) } } } func TestCodeCompile_OptimizeIndexSliceAssign(t *testing.T) { query, err := gojq.Parse(`.foo."bar".["baz"].[0]."".[0:1] = [0]`) if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } codes := reflect.ValueOf(code).Elem().FieldByName("codes") if got, expected := codes.Len(), 8; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if expected := map[string]any{ "foo": map[string]any{ "bar": map[string]any{ "baz": []any{map[string]any{"": []any{0}}}, }, }, }; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestCodeCompile_OptimizeTailRec_While(t *testing.T) { query, err := gojq.Parse("0 | while(. < 10; . + 1)") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } codes := reflect.ValueOf(code).Elem().FieldByName("codes") if got, expected := codes.Len(), 48; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } op1 := codes.Index(2).Elem().FieldByName("op") op2 := codes.Index(21).Elem().FieldByName("op") // test jump of call _while if got, expected := *(*int)(unsafe.Pointer(op2.UnsafeAddr())), *(*int)(unsafe.Pointer(op1.UnsafeAddr())); expected != got { t.Errorf("expected: %v, got: %v", expected, got) } iter := code.Run(nil) n := 0 for { got, ok := iter.Next() if !ok { break } if !reflect.DeepEqual(got, n) { t.Errorf("expected: %v, got: %v", n, got) } n++ } if expected := 10; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestCodeCompile_OptimizeTailRec_CallRec(t *testing.T) { query, err := gojq.Parse(` def f: . as $x | $x, (if $x < 3 then $x + 1 | f else empty end), $x; f `) if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } codes := reflect.ValueOf(code).Elem().FieldByName("codes") if got, expected := codes.Len(), 48; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } op1 := codes.Index(39).Elem().FieldByName("op") // callrec f op2 := codes.Index(38).Elem().FieldByName("op") // call _add/2 if got, expected := *(*int)(unsafe.Pointer(op2.UnsafeAddr()))+1, *(*int)(unsafe.Pointer(op1.UnsafeAddr())); expected != got { t.Errorf("expected: %v, got: %v", expected, got) } } func TestCodeCompile_OptimizeJumps(t *testing.T) { query, err := gojq.Parse("def f: 1; def g: 2; def h: 3; f") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } codes := reflect.ValueOf(code).Elem().FieldByName("codes") if got, expected := codes.Len(), 15; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } v := codes.Index(1).Elem().FieldByName("v") if got, expected := *(*any)(unsafe.Pointer(v.UnsafeAddr())), 13; expected != got { t.Errorf("expected: %v, got: %v", expected, got) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if expected := 1; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestCodeRun_Race(t *testing.T) { query, err := gojq.Parse("range(10)") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() iter := code.Run(nil) n := 0 for { got, ok := iter.Next() if !ok { break } if got != n { t.Errorf("expected: %v, got: %v", n, got) } n++ } if expected := 10; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } }() } wg.Wait() } func BenchmarkCompile(b *testing.B) { cnt, err := os.ReadFile("builtin.jq") if err != nil { b.Fatal(err) } query, err := gojq.Parse(string(cnt)) if err != nil { b.Fatal(err) } for i := 0; i < b.N; i++ { _, err := gojq.Compile( query, gojq.WithInputIter(gojq.NewIter()), ) if err != nil { b.Fatal(err) } } } gojq-0.12.13/debug.go000066400000000000000000000102121443612442000142270ustar00rootroot00000000000000//go:build gojq_debug // +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, formatOp(c.op, false), 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] fmt.Fprintf(&sb, "\t%d\t%s%s\t|", pc, formatOp(c.op, backtrack), 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 formatOp(c opcode, backtrack bool) string { if backtrack { return c.String() + " " + strings.Repeat(" ", 13-len(c.String())) } return c.String() + strings.Repeat(" ", 25-len(c.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%s%d\t|\t%s\n", op, strings.Repeat(" ", 22), 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) } } gojq-0.12.13/encoder.go000066400000000000000000000075541443612442000145770ustar00rootroot00000000000000package gojq import ( "bytes" "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, // 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 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 } if f >= math.MaxFloat64 { f = math.MaxFloat64 } else if f <= -math.MaxFloat64 { f = -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('}') } gojq-0.12.13/encoder_test.go000066400000000000000000000025211443612442000156230ustar00rootroot00000000000000package gojq_test import ( "fmt" "math" "math/big" "testing" "github.com/itchyny/gojq" ) func TestMarshal(t *testing.T) { testCases := []struct { value any expected string }{ { value: nil, expected: "null", }, { value: []any{false, true}, expected: "[false,true]", }, { value: []any{ 42, 3.14, 1e-6, 1e-7, -1e-9, 1e-10, math.NaN(), math.Inf(1), math.Inf(-1), new(big.Int).SetBytes([]byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff")), }, expected: "[42,3.14,0.000001,1e-7,-1e-9,1e-10,null,1.7976931348623157e+308,-1.7976931348623157e+308,340282366920938463463374607431768211455]", }, { value: []any{"", "abcde", "foo\x00\x1f\r\n\t\f\b<=>!\"#$%'& \\\x7fbar"}, expected: `["","abcde","foo\u0000\u001f\r\n\t\f\b<=>!\"#$%'& \\\u007fbar"]`, }, { value: []any{1, []any{2, []any{3, []any{map[string]any{}}}}}, expected: `[1,[2,[3,[{}]]]]`, }, { value: map[string]any{"x": []any{100}, "y": map[string]any{"z": 42}}, expected: `{"x":[100],"y":{"z":42}}`, }, } for _, tc := range testCases { t.Run(fmt.Sprintf("%v", tc.value), func(t *testing.T) { got, err := gojq.Marshal(tc.value) if err != nil { t.Fatal(err) } if string(got) != tc.expected { t.Errorf("expected: %s, got: %s", tc.expected, string(got)) } }) } } gojq-0.12.13/env.go000066400000000000000000000013401443612442000137330ustar00rootroot00000000000000package 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 expdepth int } gojq-0.12.13/error.go000066400000000000000000000166101443612442000143020ustar00rootroot00000000000000package 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 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 (err *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 halt bool } func (err *exitCodeError) Error() string { if s, ok := err.value.(string); ok { return "error: " + s } return "error: " + jsonMarshal(err.value) } func (err *exitCodeError) IsEmptyError() bool { return err.value == nil } func (err *exitCodeError) Value() any { return err.value } func (err *exitCodeError) ExitCode() int { return err.code } func (err *exitCodeError) IsHaltError() bool { return err.halt } type flattenDepthError struct { v float64 } func (err *flattenDepthError) Error() string { return "flatten depth should not be negative: " + Preview(err.v) } type joinTypeError struct { v any } func (err *joinTypeError) Error() string { return "join cannot be applied to an array including: " + typeErrorPreview(err.v) } type timeArrayError struct{} func (err *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 (err *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 (err *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) + ")" } } gojq-0.12.13/execute.go000066400000000000000000000227451443612442000146210ustar00rootroot00000000000000package 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 i := 0; i < n; i++ { v, k := env.pop(), env.pop() s, ok := k.(string) if !ok { err = &objectKeyNotStringError{k} break loop } 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 er := err.(type) { case *tryEndError: err = er.err break loop case *breakError: break loop case ValueError: if er, ok := er.(*exitCodeError); ok && er.halt { break loop } if v := er.Value(); v != nil { env.pop() env.push(v) } else { err = nil break loop } 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 := 0; i < argcnt; i++ { args[i] = env.pop() } w := v[0].(func(any, []any) any)(x, args) if e, ok := w.(error); ok { if er, ok := e.(*exitCodeError); !ok || er.value != nil || er.halt { 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.save() env.scopes.index = 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, 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.expdepth = env.forks[:len(env.forks)-1], 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 } gojq-0.12.13/func.go000066400000000000000000001254451443612442000141130ustar00rootroot00000000000000package gojq import ( "encoding/base64" "encoding/json" "errors" "fmt" "io" "math" "math/big" "net/url" "reflect" "regexp" "sort" "strconv" "strings" "time" "unicode/utf8" "github.com/itchyny/timefmt-go" ) //go:generate go run -modfile=go.dev.mod _tools/gen_builtin.go -i builtin.jq -o builtin.go var builtinFuncDefs map[string][]*FuncDef const ( argcount0 = 1 << iota argcount1 argcount2 argcount3 ) type function struct { argcount int iter bool callback func(any, []any) any } func (fn function) accept(cnt int) bool { return fn.argcount&(1<= 0 { return v } return -v case float64: return math.Abs(v) case *big.Int: if v.Sign() >= 0 { return v } return new(big.Int).Abs(v) case string: return len([]rune(v)) case []any: return len(v) case map[string]any: return len(v) default: return &func0TypeError{"length", v} } } func funcUtf8ByteLength(v any) any { s, ok := v.(string) if !ok { return &func0TypeError{"utf8bytelength", v} } return len(s) } func funcKeys(v any) any { switch v := v.(type) { case []any: w := make([]any, len(v)) for i := range v { w[i] = i } return w case map[string]any: w := make([]any, len(v)) for i, k := range keys(v) { w[i] = k } return w default: return &func0TypeError{"keys", v} } } func keys(v map[string]any) []string { w := make([]string, len(v)) var i int for k := range v { w[i] = k i++ } sort.Strings(w) return w } func values(v any) ([]any, bool) { switch v := v.(type) { case []any: return v, true case map[string]any: vs := make([]any, len(v)) for i, k := range keys(v) { vs[i] = v[k] } return vs, true default: return nil, false } } func funcHas(v, x any) any { switch v := v.(type) { case []any: if x, ok := toInt(x); ok { return 0 <= x && x < len(v) } case map[string]any: if x, ok := x.(string); ok { _, ok := v[x] return ok } case nil: return false } return &func1TypeError{"has", v, x} } func funcToEntries(v any) any { switch v := v.(type) { case []any: w := make([]any, len(v)) for i, x := range v { w[i] = map[string]any{"key": i, "value": x} } return w case map[string]any: w := make([]any, len(v)) for i, k := range keys(v) { w[i] = map[string]any{"key": k, "value": v[k]} } return w default: return &func0TypeError{"to_entries", v} } } func funcFromEntries(v any) any { vs, ok := v.([]any) if !ok { return &func0TypeError{"from_entries", v} } w := make(map[string]any, len(vs)) for _, v := range vs { switch v := v.(type) { case map[string]any: var ( key string value any ok bool ) for _, k := range [4]string{"key", "Key", "name", "Name"} { if k := v[k]; k != nil && k != false { if key, ok = k.(string); !ok { return &func0WrapError{"from_entries", vs, &objectKeyNotStringError{k}} } break } } if !ok { return &func0WrapError{"from_entries", vs, &objectKeyNotStringError{nil}} } for _, k := range [2]string{"value", "Value"} { if value, ok = v[k]; ok { break } } w[key] = value default: return &func0TypeError{"from_entries", v} } } return w } func funcAdd(v any) any { vs, ok := values(v) if !ok { return &func0TypeError{"add", v} } v = nil for _, x := range vs { switch x := x.(type) { case nil: continue case string: switch w := v.(type) { case nil: var sb strings.Builder sb.WriteString(x) v = &sb continue case *strings.Builder: w.WriteString(x) continue } case []any: switch w := v.(type) { case nil: s := make([]any, len(x)) copy(s, x) v = s continue case []any: v = append(w, x...) continue } case map[string]any: switch w := v.(type) { case nil: m := make(map[string]any, len(x)) for k, e := range x { m[k] = e } v = m continue case map[string]any: for k, e := range x { w[k] = e } continue } } if sb, ok := v.(*strings.Builder); ok { v = sb.String() } v = funcOpAdd(nil, v, x) if err, ok := v.(error); ok { return err } } if sb, ok := v.(*strings.Builder); ok { v = sb.String() } return v } func funcToNumber(v any) any { switch v := v.(type) { case int, float64, *big.Int: return v case string: if !newLexer(v).validNumber() { return &func0WrapError{"tonumber", v, errors.New("invalid number")} } return toNumber(v) default: return &func0TypeError{"tonumber", v} } } func toNumber(v string) any { return normalizeNumber(json.Number(v)) } func funcToString(v any) any { if s, ok := v.(string); ok { return s } return funcToJSON(v) } func funcType(v any) any { return TypeOf(v) } func funcReverse(v any) any { vs, ok := v.([]any) if !ok { return &func0TypeError{"reverse", v} } ws := make([]any, len(vs)) for i, v := range vs { ws[len(ws)-i-1] = v } return ws } func funcContains(v, x any) any { return binopTypeSwitch(v, x, func(l, r int) any { return l == r }, func(l, r float64) any { return l == r }, func(l, r *big.Int) any { return l.Cmp(r) == 0 }, func(l, r string) any { return strings.Contains(l, r) }, func(l, r []any) any { R: for _, r := range r { for _, l := range l { if funcContains(l, r) == true { continue R } } return false } return true }, func(l, r map[string]any) any { if len(l) < len(r) { return false } for k, r := range r { if l, ok := l[k]; !ok || funcContains(l, r) != true { return false } } return true }, func(l, r any) any { if l == r { return true } return &func1TypeError{"contains", l, r} }, ) } func funcIndices(v, x any) any { return indexFunc("indices", v, x, indices) } func indices(vs, xs []any) any { rs := []any{} if len(xs) == 0 { return rs } for i := 0; i <= len(vs)-len(xs); i++ { if compare(vs[i:i+len(xs)], xs) == 0 { rs = append(rs, i) } } return rs } func funcIndex(v, x any) any { return indexFunc("index", v, x, func(vs, xs []any) any { if len(xs) == 0 { return nil } for i := 0; i <= len(vs)-len(xs); i++ { if compare(vs[i:i+len(xs)], xs) == 0 { return i } } return nil }) } func funcRindex(v, x any) any { return indexFunc("rindex", v, x, func(vs, xs []any) any { if len(xs) == 0 { return nil } for i := len(vs) - len(xs); i >= 0; i-- { if compare(vs[i:i+len(xs)], xs) == 0 { return i } } return nil }) } func indexFunc(name string, v, x any, f func(_, _ []any) any) any { switch v := v.(type) { case nil: return nil case []any: switch x := x.(type) { case []any: return f(v, x) default: return f(v, []any{x}) } case string: if x, ok := x.(string); ok { return f(explode(v), explode(x)) } return &func1TypeError{name, v, x} default: return &func1TypeError{name, v, x} } } func funcStartsWith(v, x any) any { s, ok := v.(string) if !ok { return &func1TypeError{"startswith", v, x} } t, ok := x.(string) if !ok { return &func1TypeError{"startswith", v, x} } return strings.HasPrefix(s, t) } func funcEndsWith(v, x any) any { s, ok := v.(string) if !ok { return &func1TypeError{"endswith", v, x} } t, ok := x.(string) if !ok { return &func1TypeError{"endswith", v, x} } return strings.HasSuffix(s, t) } func funcLtrimstr(v, x any) any { s, ok := v.(string) if !ok { return v } t, ok := x.(string) if !ok { return v } return strings.TrimPrefix(s, t) } func funcRtrimstr(v, x any) any { s, ok := v.(string) if !ok { return v } t, ok := x.(string) if !ok { return v } return strings.TrimSuffix(s, t) } func funcExplode(v any) any { s, ok := v.(string) if !ok { return &func0TypeError{"explode", v} } return explode(s) } func explode(s string) []any { xs := make([]any, len([]rune(s))) var i int for _, r := range s { xs[i] = int(r) i++ } return xs } func funcImplode(v any) any { vs, ok := v.([]any) if !ok { return &func0TypeError{"implode", v} } var sb strings.Builder sb.Grow(len(vs)) for _, v := range vs { if r, ok := toInt(v); ok && 0 <= r && r <= utf8.MaxRune { sb.WriteRune(rune(r)) } else { return &func0TypeError{"implode", vs} } } return sb.String() } func funcSplit(v any, args []any) any { s, ok := v.(string) if !ok { return &func0TypeError{"split", v} } x, ok := args[0].(string) if !ok { return &func0TypeError{"split", x} } var ss []string if len(args) == 1 { ss = strings.Split(s, x) } else { var flags string if args[1] != nil { v, ok := args[1].(string) if !ok { return &func0TypeError{"split", args[1]} } flags = v } r, err := compileRegexp(x, flags) if err != nil { return err } ss = r.Split(s, -1) } xs := make([]any, len(ss)) for i, s := range ss { xs[i] = s } return xs } func funcASCIIDowncase(v any) any { s, ok := v.(string) if !ok { return &func0TypeError{"ascii_downcase", v} } return strings.Map(func(r rune) rune { if 'A' <= r && r <= 'Z' { return r + ('a' - 'A') } return r }, s) } func funcASCIIUpcase(v any) any { s, ok := v.(string) if !ok { return &func0TypeError{"ascii_upcase", v} } return strings.Map(func(r rune) rune { if 'a' <= r && r <= 'z' { return r - ('a' - 'A') } return r }, s) } func funcToJSON(v any) any { return jsonMarshal(v) } func funcFromJSON(v any) any { s, ok := v.(string) if !ok { return &func0TypeError{"fromjson", v} } var w any dec := json.NewDecoder(strings.NewReader(s)) dec.UseNumber() if err := dec.Decode(&w); err != nil { return &func0WrapError{"fromjson", v, err} } if _, err := dec.Token(); err != io.EOF { return &func0TypeError{"fromjson", v} } return normalizeNumbers(w) } func funcFormat(v, x any) any { s, ok := x.(string) if !ok { return &func0TypeError{"format", x} } format := "@" + s f := formatToFunc(format) if f == nil { return &formatNotFoundError{format} } return internalFuncs[f.Name].callback(v, nil) } var htmlEscaper = strings.NewReplacer( `<`, "<", `>`, ">", `&`, "&", `'`, "'", `"`, """, ) func funcToHTML(v any) any { switch x := funcToString(v).(type) { case string: return htmlEscaper.Replace(x) default: return x } } func funcToURI(v any) any { switch x := funcToString(v).(type) { case string: return url.QueryEscape(x) default: return x } } func funcToURId(v any) any { switch x := funcToString(v).(type) { case string: x, err := url.QueryUnescape(x) if err != nil { return &func0WrapError{"@urid", v, err} } return x default: return x } } var csvEscaper = strings.NewReplacer( `"`, `""`, "\x00", `\0`, ) func funcToCSV(v any) any { return formatJoin("csv", v, ",", func(s string) string { return `"` + csvEscaper.Replace(s) + `"` }) } var tsvEscaper = strings.NewReplacer( "\t", `\t`, "\r", `\r`, "\n", `\n`, "\\", `\\`, "\x00", `\0`, ) func funcToTSV(v any) any { return formatJoin("tsv", v, "\t", tsvEscaper.Replace) } var shEscaper = strings.NewReplacer( "'", `'\''`, "\x00", `\0`, ) func funcToSh(v any) any { if _, ok := v.([]any); !ok { v = []any{v} } return formatJoin("sh", v, " ", func(s string) string { return "'" + shEscaper.Replace(s) + "'" }) } func formatJoin(typ string, v any, sep string, escape func(string) string) any { vs, ok := v.([]any) if !ok { return &func0TypeError{"@" + typ, v} } ss := make([]string, len(vs)) for i, v := range vs { switch v := v.(type) { case []any, map[string]any: return &formatRowError{typ, v} case string: ss[i] = escape(v) default: if s := jsonMarshal(v); s != "null" || typ == "sh" { ss[i] = s } } } return strings.Join(ss, sep) } func funcToBase64(v any) any { switch x := funcToString(v).(type) { case string: return base64.StdEncoding.EncodeToString([]byte(x)) default: return x } } func funcToBase64d(v any) any { switch x := funcToString(v).(type) { case string: if i := strings.IndexRune(x, base64.StdPadding); i >= 0 { x = x[:i] } y, err := base64.RawStdEncoding.DecodeString(x) if err != nil { return &func0WrapError{"@base64d", v, err} } return string(y) default: return x } } func funcIndex2(_, v, x any) any { switch x := x.(type) { case string: switch v := v.(type) { case nil: return nil case map[string]any: return v[x] default: return &expectedObjectError{v} } case int, float64, *big.Int: i, _ := toInt(x) switch v := v.(type) { case nil: return nil case []any: return index(v, i) case string: return indexString(v, i) default: return &expectedArrayError{v} } case []any: switch v := v.(type) { case nil: return nil case []any: return indices(v, x) default: return &expectedArrayError{v} } case map[string]any: if v == nil { return nil } start, ok := x["start"] if !ok { return &expectedStartEndError{x} } end, ok := x["end"] if !ok { return &expectedStartEndError{x} } return funcSlice(nil, v, end, start) default: switch v.(type) { case []any: return &arrayIndexNotNumberError{x} case string: return &stringIndexNotNumberError{x} default: return &objectKeyNotStringError{x} } } } func index(vs []any, i int) any { i = clampIndex(i, -1, len(vs)) if 0 <= i && i < len(vs) { return vs[i] } return nil } func indexString(s string, i int) any { l := len([]rune(s)) i = clampIndex(i, -1, l) if 0 <= i && i < l { for _, r := range s { if i--; i < 0 { return string(r) } } } return nil } func funcSlice(_, v, e, s any) (r any) { switch v := v.(type) { case nil: return nil case []any: return slice(v, e, s) case string: return sliceString(v, e, s) default: return &expectedArrayError{v} } } func slice(vs []any, e, s any) any { var start, end int if s != nil { if i, ok := toInt(s); ok { start = clampIndex(i, 0, len(vs)) } else { return &arrayIndexNotNumberError{s} } } if e != nil { if i, ok := toInt(e); ok { end = clampIndex(i, start, len(vs)) } else { return &arrayIndexNotNumberError{e} } } else { end = len(vs) } return vs[start:end] } func sliceString(v string, e, s any) any { var start, end int l := len([]rune(v)) if s != nil { if i, ok := toInt(s); ok { start = clampIndex(i, 0, l) } else { return &stringIndexNotNumberError{s} } } if e != nil { if i, ok := toInt(e); ok { end = clampIndex(i, start, l) } else { return &stringIndexNotNumberError{e} } } else { end = l } if start < l { for i := range v { if start--; start < 0 { start = i break } } } else { start = len(v) } if end < l { for i := range v { if end--; end < 0 { end = i break } } } else { end = len(v) } return v[start:end] } func clampIndex(i, min, max int) int { if i < 0 { i += max } if i < min { return min } else if i < max { return i } else { return max } } func funcFlatten(v any, args []any) any { vs, ok := values(v) if !ok { return &func0TypeError{"flatten", v} } var depth float64 if len(args) == 0 { depth = -1 } else { depth, ok = toFloat(args[0]) if !ok { return &func0TypeError{"flatten", args[0]} } if depth < 0 { return &flattenDepthError{depth} } } return flatten([]any{}, vs, depth) } func flatten(xs, vs []any, depth float64) []any { for _, v := range vs { if vs, ok := v.([]any); ok && depth != 0 { xs = flatten(xs, vs, depth-1) } else { xs = append(xs, v) } } return xs } type rangeIter struct { value, end, step any } func (iter *rangeIter) Next() (any, bool) { if compare(iter.step, 0)*compare(iter.value, iter.end) >= 0 { return nil, false } v := iter.value iter.value = funcOpAdd(nil, v, iter.step) return v, true } func funcRange(_ any, xs []any) any { for _, x := range xs { switch x.(type) { case int, float64, *big.Int: default: return &func0TypeError{"range", x} } } return &rangeIter{xs[0], xs[1], xs[2]} } func funcMin(v any) any { vs, ok := v.([]any) if !ok { return &func0TypeError{"min", v} } return minMaxBy(vs, vs, true) } func funcMinBy(v, x any) any { vs, ok := v.([]any) if !ok { return &func1TypeError{"min_by", v, x} } xs, ok := x.([]any) if !ok { return &func1TypeError{"min_by", v, x} } if len(vs) != len(xs) { return &func1WrapError{"min_by", v, x, &lengthMismatchError{}} } return minMaxBy(vs, xs, true) } func funcMax(v any) any { vs, ok := v.([]any) if !ok { return &func0TypeError{"max", v} } return minMaxBy(vs, vs, false) } func funcMaxBy(v, x any) any { vs, ok := v.([]any) if !ok { return &func1TypeError{"max_by", v, x} } xs, ok := x.([]any) if !ok { return &func1TypeError{"max_by", v, x} } if len(vs) != len(xs) { return &func1WrapError{"max_by", v, x, &lengthMismatchError{}} } return minMaxBy(vs, xs, false) } func minMaxBy(vs, xs []any, isMin bool) any { if len(vs) == 0 { return nil } i, j, x := 0, 0, xs[0] for i++; i < len(xs); i++ { if compare(x, xs[i]) > 0 == isMin { j, x = i, xs[i] } } return vs[j] } type sortItem struct { value, key any } func sortItems(name string, v, x any) ([]*sortItem, error) { vs, ok := v.([]any) if !ok { if strings.HasSuffix(name, "_by") { return nil, &func1TypeError{name, v, x} } return nil, &func0TypeError{name, v} } xs, ok := x.([]any) if !ok { return nil, &func1TypeError{name, v, x} } if len(vs) != len(xs) { return nil, &func1WrapError{name, v, x, &lengthMismatchError{}} } items := make([]*sortItem, len(vs)) for i, v := range vs { items[i] = &sortItem{v, xs[i]} } sort.SliceStable(items, func(i, j int) bool { return compare(items[i].key, items[j].key) < 0 }) return items, nil } func funcSort(v any) any { return sortBy("sort", v, v) } func funcSortBy(v, x any) any { return sortBy("sort_by", v, x) } func sortBy(name string, v, x any) any { items, err := sortItems(name, v, x) if err != nil { return err } rs := make([]any, len(items)) for i, x := range items { rs[i] = x.value } return rs } func funcGroupBy(v, x any) any { items, err := sortItems("group_by", v, x) if err != nil { return err } rs := []any{} var last any for i, r := range items { if i == 0 || compare(last, r.key) != 0 { rs, last = append(rs, []any{r.value}), r.key } else { rs[len(rs)-1] = append(rs[len(rs)-1].([]any), r.value) } } return rs } func funcUnique(v any) any { return uniqueBy("unique", v, v) } func funcUniqueBy(v, x any) any { return uniqueBy("unique_by", v, x) } func uniqueBy(name string, v, x any) any { items, err := sortItems(name, v, x) if err != nil { return err } rs := []any{} var last any for i, r := range items { if i == 0 || compare(last, r.key) != 0 { rs, last = append(rs, r.value), r.key } } return rs } func funcJoin(v, x any) any { vs, ok := values(v) if !ok { return &func1TypeError{"join", v, x} } if len(vs) == 0 { return "" } sep, ok := x.(string) if len(vs) > 1 && !ok { return &func1TypeError{"join", v, x} } ss := make([]string, len(vs)) for i, v := range vs { switch v := v.(type) { case nil: case string: ss[i] = v case bool: if v { ss[i] = "true" } else { ss[i] = "false" } case int, float64, *big.Int: ss[i] = jsonMarshal(v) default: return &joinTypeError{v} } } return strings.Join(ss, sep) } func funcSignificand(v float64) float64 { if math.IsNaN(v) || math.IsInf(v, 0) || v == 0.0 { return v } return math.Float64frombits((math.Float64bits(v) & 0x800fffffffffffff) | 0x3ff0000000000000) } func funcExp10(v float64) float64 { return math.Pow(10, v) } func funcFrexp(v any) any { x, ok := toFloat(v) if !ok { return &func0TypeError{"frexp", v} } f, e := math.Frexp(x) return []any{f, e} } func funcModf(v any) any { x, ok := toFloat(v) if !ok { return &func0TypeError{"modf", v} } i, f := math.Modf(x) return []any{f, i} } func funcLgamma(v float64) float64 { v, _ = math.Lgamma(v) return v } func funcDrem(l, r float64) float64 { x := math.Remainder(l, r) if x == 0.0 { return math.Copysign(x, l) } return x } func funcJn(l, r float64) float64 { return math.Jn(int(l), r) } func funcLdexp(l, r float64) float64 { return math.Ldexp(l, int(r)) } func funcScalb(l, r float64) float64 { return l * math.Pow(2, r) } func funcScalbln(l, r float64) float64 { return l * math.Pow(2, r) } func funcYn(l, r float64) float64 { return math.Yn(int(l), r) } func funcInfinite(any) any { return math.Inf(1) } func funcIsfinite(v any) any { x, ok := toFloat(v) return ok && !math.IsInf(x, 0) } func funcIsinfinite(v any) any { x, ok := toFloat(v) return ok && math.IsInf(x, 0) } func funcNan(any) any { return math.NaN() } func funcIsnan(v any) any { x, ok := toFloat(v) if !ok { if v == nil { return false } return &func0TypeError{"isnan", v} } return math.IsNaN(x) } func funcIsnormal(v any) any { if v, ok := toFloat(v); ok { e := math.Float64bits(v) & 0x7ff0000000000000 >> 52 return 0 < e && e < 0x7ff } return false } // An `allocator` creates new maps and slices, stores the allocated addresses. // This allocator is used to reduce allocations on assignment operator (`=`), // update-assignment operator (`|=`), and the `map_values`, `del`, `delpaths` // functions. type allocator map[uintptr]struct{} func funcAllocator(any, []any) any { return allocator{} } func (a allocator) allocated(v any) bool { _, ok := a[reflect.ValueOf(v).Pointer()] return ok } func (a allocator) makeObject(l int) map[string]any { v := make(map[string]any, l) if a != nil { a[reflect.ValueOf(v).Pointer()] = struct{}{} } return v } func (a allocator) makeArray(l, c int) []any { if c < l { c = l } v := make([]any, l, c) if a != nil { a[reflect.ValueOf(v).Pointer()] = struct{}{} } return v } func funcSetpath(v, p, n any) any { // There is no need to use an allocator on a single update. return setpath(v, p, n, nil) } // Used in compiler#compileAssign and compiler#compileModify. func funcSetpathWithAllocator(v any, args []any) any { return setpath(v, args[0], args[1], args[2].(allocator)) } func setpath(v, p, n any, a allocator) any { path, ok := p.([]any) if !ok { return &func1TypeError{"setpath", v, p} } u, err := update(v, path, n, a) if err != nil { return &func2WrapError{"setpath", v, p, n, err} } return u } func funcDelpaths(v, p any) any { return delpaths(v, p, allocator{}) } // Used in compiler#compileAssign and compiler#compileModify. func funcDelpathsWithAllocator(v any, args []any) any { return delpaths(v, args[0], args[1].(allocator)) } func delpaths(v, p any, a allocator) any { paths, ok := p.([]any) if !ok { return &func1TypeError{"delpaths", v, p} } if len(paths) == 0 { return v } // Fills the paths with an empty value and then delete them. We cannot delete // in each loop because array indices should not change. For example, // jq -n "[0, 1, 2, 3] | delpaths([[1], [2]])" #=> [0, 3]. var empty struct{} var err error u := v for _, q := range paths { path, ok := q.([]any) if !ok { return &func1WrapError{"delpaths", v, p, &expectedArrayError{q}} } u, err = update(u, path, empty, a) if err != nil { return &func1WrapError{"delpaths", v, p, err} } } return deleteEmpty(u) } func update(v any, path []any, n any, a allocator) (any, error) { if len(path) == 0 { return n, nil } switch p := path[0].(type) { case string: switch v := v.(type) { case nil: return updateObject(nil, p, path[1:], n, a) case map[string]any: return updateObject(v, p, path[1:], n, a) case struct{}: return v, nil default: return nil, &expectedObjectError{v} } case int, float64, *big.Int: i, _ := toInt(p) switch v := v.(type) { case nil: return updateArrayIndex(nil, i, path[1:], n, a) case []any: return updateArrayIndex(v, i, path[1:], n, a) case struct{}: return v, nil default: return nil, &expectedArrayError{v} } case map[string]any: switch v := v.(type) { case nil: return updateArraySlice(nil, p, path[1:], n, a) case []any: return updateArraySlice(v, p, path[1:], n, a) case struct{}: return v, nil default: return nil, &expectedArrayError{v} } default: switch v.(type) { case []any: return nil, &arrayIndexNotNumberError{p} default: return nil, &objectKeyNotStringError{p} } } } func updateObject(v map[string]any, k string, path []any, n any, a allocator) (any, error) { x, ok := v[k] if !ok && n == struct{}{} { return v, nil } u, err := update(x, path, n, a) if err != nil { return nil, err } if a.allocated(v) { v[k] = u return v, nil } w := a.makeObject(len(v) + 1) for k, v := range v { w[k] = v } w[k] = u return w, nil } func updateArrayIndex(v []any, i int, path []any, n any, a allocator) (any, error) { var x any if j := clampIndex(i, -1, len(v)); j < 0 { if n == struct{}{} { return v, nil } return nil, &arrayIndexNegativeError{i} } else if j < len(v) { i = j x = v[i] } else { if n == struct{}{} { return v, nil } if i >= 0x8000000 { return nil, &arrayIndexTooLargeError{i} } } u, err := update(x, path, n, a) if err != nil { return nil, err } l, c := len(v), cap(v) if a.allocated(v) { if i < c { if i >= l { v = v[:i+1] } v[i] = u return v, nil } c *= 2 } if i >= l { l = i + 1 } w := a.makeArray(l, c) copy(w, v) w[i] = u return w, nil } func updateArraySlice(v []any, m map[string]any, path []any, n any, a allocator) (any, error) { s, ok := m["start"] if !ok { return nil, &expectedStartEndError{m} } e, ok := m["end"] if !ok { return nil, &expectedStartEndError{m} } var start, end int if i, ok := toInt(s); ok { start = clampIndex(i, 0, len(v)) } if i, ok := toInt(e); ok { end = clampIndex(i, start, len(v)) } else { end = len(v) } if start == end && n == struct{}{} { return v, nil } u, err := update(v[start:end], path, n, a) if err != nil { return nil, err } switch u := u.(type) { case []any: var w []any if len(u) == end-start && a.allocated(v) { w = v } else { w = a.makeArray(len(v)-(end-start)+len(u), 0) copy(w, v[:start]) copy(w[start+len(u):], v[end:]) } copy(w[start:], u) return w, nil case struct{}: var w []any if a.allocated(v) { w = v } else { w = a.makeArray(len(v), 0) copy(w, v) } for i := start; i < end; i++ { w[i] = u } return w, nil default: return nil, &expectedArrayError{u} } } func deleteEmpty(v any) any { switch v := v.(type) { case struct{}: return nil case map[string]any: for k, w := range v { if w == struct{}{} { delete(v, k) } else { v[k] = deleteEmpty(w) } } return v case []any: var j int for _, w := range v { if w != struct{}{} { v[j] = deleteEmpty(w) j++ } } for i := j; i < len(v); i++ { v[i] = nil } return v[:j] default: return v } } func funcGetpath(v, p any) any { path, ok := p.([]any) if !ok { return &func1TypeError{"getpath", v, p} } u := v for _, x := range path { switch v.(type) { case nil, []any, map[string]any: v = funcIndex2(nil, v, x) if err, ok := v.(error); ok { return &func1WrapError{"getpath", u, p, err} } default: return &func1TypeError{"getpath", u, p} } } return v } func funcTranspose(v any) any { vss, ok := v.([]any) if !ok { return &func0TypeError{"transpose", v} } if len(vss) == 0 { return []any{} } var l int for _, vs := range vss { vs, ok := vs.([]any) if !ok { return &func0TypeError{"transpose", v} } if k := len(vs); l < k { l = k } } wss := make([][]any, l) xs := make([]any, l) for i, k := 0, len(vss); i < l; i++ { s := make([]any, k) wss[i] = s xs[i] = s } for i, vs := range vss { for j, v := range vs.([]any) { wss[j][i] = v } } return xs } func funcBsearch(v, t any) any { vs, ok := v.([]any) if !ok { return &func1TypeError{"bsearch", v, t} } i := sort.Search(len(vs), func(i int) bool { return compare(vs[i], t) >= 0 }) if i < len(vs) && compare(vs[i], t) == 0 { return i } return -i - 1 } func funcGmtime(v any) any { if v, ok := toFloat(v); ok { return epochToArray(v, time.UTC) } return &func0TypeError{"gmtime", v} } func funcLocaltime(v any) any { if v, ok := toFloat(v); ok { return epochToArray(v, time.Local) } return &func0TypeError{"localtime", v} } func epochToArray(v float64, loc *time.Location) []any { t := time.Unix(int64(v), int64((v-math.Floor(v))*1e9)).In(loc) return []any{ t.Year(), int(t.Month()) - 1, t.Day(), t.Hour(), t.Minute(), float64(t.Second()) + float64(t.Nanosecond())/1e9, int(t.Weekday()), t.YearDay() - 1, } } func funcMktime(v any) any { a, ok := v.([]any) if !ok { return &func0TypeError{"mktime", v} } t, err := arrayToTime(a, time.UTC) if err != nil { return &func0WrapError{"mktime", v, err} } return timeToEpoch(t) } func timeToEpoch(t time.Time) float64 { return float64(t.Unix()) + float64(t.Nanosecond())/1e9 } func funcStrftime(v, x any) any { if w, ok := toFloat(v); ok { v = epochToArray(w, time.UTC) } a, ok := v.([]any) if !ok { return &func1TypeError{"strftime", v, x} } format, ok := x.(string) if !ok { return &func1TypeError{"strftime", v, x} } t, err := arrayToTime(a, time.UTC) if err != nil { return &func1WrapError{"strftime", v, x, err} } return timefmt.Format(t, format) } func funcStrflocaltime(v, x any) any { if w, ok := toFloat(v); ok { v = epochToArray(w, time.Local) } a, ok := v.([]any) if !ok { return &func1TypeError{"strflocaltime", v, x} } format, ok := x.(string) if !ok { return &func1TypeError{"strflocaltime", v, x} } t, err := arrayToTime(a, time.Local) if err != nil { return &func1WrapError{"strflocaltime", v, x, err} } return timefmt.Format(t, format) } func funcStrptime(v, x any) any { s, ok := v.(string) if !ok { return &func1TypeError{"strptime", v, x} } format, ok := x.(string) if !ok { return &func1TypeError{"strptime", v, x} } t, err := timefmt.Parse(s, format) if err != nil { return &func1WrapError{"strptime", v, x, err} } var u time.Time if t == u { return &func1TypeError{"strptime", v, x} } return epochToArray(timeToEpoch(t), time.UTC) } func arrayToTime(a []any, loc *time.Location) (time.Time, error) { var t time.Time if len(a) != 8 { return t, &timeArrayError{} } var y, m, d, h, min, sec, nsec int var ok bool if y, ok = toInt(a[0]); !ok { return t, &timeArrayError{} } if m, ok = toInt(a[1]); ok { m++ } else { return t, &timeArrayError{} } if d, ok = toInt(a[2]); !ok { return t, &timeArrayError{} } if h, ok = toInt(a[3]); !ok { return t, &timeArrayError{} } if min, ok = toInt(a[4]); !ok { return t, &timeArrayError{} } if x, ok := toFloat(a[5]); ok { sec = int(x) nsec = int((x - math.Floor(x)) * 1e9) } else { return t, &timeArrayError{} } if _, ok = toFloat(a[6]); !ok { return t, &timeArrayError{} } if _, ok = toFloat(a[7]); !ok { return t, &timeArrayError{} } return time.Date(y, time.Month(m), d, h, min, sec, nsec, loc), nil } func funcNow(any) any { return timeToEpoch(time.Now()) } func funcMatch(v, re, fs, testing any) any { name := "match" if testing == true { name = "test" } var flags string if fs != nil { v, ok := fs.(string) if !ok { return &func2TypeError{name, v, re, fs} } flags = v } s, ok := v.(string) if !ok { return &func2TypeError{name, v, re, fs} } restr, ok := re.(string) if !ok { return &func2TypeError{name, v, re, fs} } r, err := compileRegexp(restr, flags) if err != nil { return err } var xs [][]int if strings.ContainsRune(flags, 'g') && testing != true { xs = r.FindAllStringSubmatchIndex(s, -1) } else { got := r.FindStringSubmatchIndex(s) if testing == true { return got != nil } if got != nil { xs = [][]int{got} } } res, names := make([]any, len(xs)), r.SubexpNames() for i, x := range xs { captures := make([]any, (len(x)-2)/2) for j := 1; j < len(x)/2; j++ { var name any if n := names[j]; n != "" { name = n } if x[j*2] < 0 { captures[j-1] = map[string]any{ "name": name, "offset": -1, "length": 0, "string": nil, } continue } captures[j-1] = map[string]any{ "name": name, "offset": len([]rune(s[:x[j*2]])), "length": len([]rune(s[:x[j*2+1]])) - len([]rune(s[:x[j*2]])), "string": s[x[j*2]:x[j*2+1]], } } res[i] = map[string]any{ "offset": len([]rune(s[:x[0]])), "length": len([]rune(s[:x[1]])) - len([]rune(s[:x[0]])), "string": s[x[0]:x[1]], "captures": captures, } } return res } func compileRegexp(re, flags string) (*regexp.Regexp, error) { if strings.IndexFunc(flags, func(r rune) bool { return r != 'g' && r != 'i' && r != 'm' }) >= 0 { return nil, fmt.Errorf("unsupported regular expression flag: %q", flags) } re = strings.ReplaceAll(re, "(?<", "(?P<") if strings.ContainsRune(flags, 'i') { re = "(?i)" + re } if strings.ContainsRune(flags, 'm') { re = "(?s)" + re } r, err := regexp.Compile(re) if err != nil { return nil, fmt.Errorf("invalid regular expression %q: %s", re, err) } return r, nil } func funcCapture(v any) any { vs, ok := v.(map[string]any) if !ok { return &expectedObjectError{v} } v = vs["captures"] captures, ok := v.([]any) if !ok { return &expectedArrayError{v} } w := make(map[string]any, len(captures)) for _, capture := range captures { if capture, ok := capture.(map[string]any); ok { if name, ok := capture["name"].(string); ok { w[name] = capture["string"] } } } return w } func funcError(v any, args []any) any { if len(args) > 0 { v = args[0] } code := 5 if v == nil { code = 0 } return &exitCodeError{v, code, false} } func funcHalt(any) any { return &exitCodeError{nil, 0, true} } func funcHaltError(v any, args []any) any { code := 5 if len(args) > 0 { var ok bool if code, ok = toInt(args[0]); !ok { return &func0TypeError{"halt_error", args[0]} } } return &exitCodeError{v, code, true} } func toInt(x any) (int, bool) { switch x := x.(type) { case int: return x, true case float64: return floatToInt(x), true case *big.Int: if x.IsInt64() { if i := x.Int64(); math.MinInt <= i && i <= math.MaxInt { return int(i), true } } if x.Sign() > 0 { return math.MaxInt, true } return math.MinInt, true default: return 0, false } } func floatToInt(x float64) int { if math.MinInt <= x && x <= math.MaxInt { return int(x) } if x > 0 { return math.MaxInt } return math.MinInt } func toFloat(x any) (float64, bool) { switch x := x.(type) { case int: return float64(x), true case float64: return x, true case *big.Int: return bigToFloat(x), true default: return 0.0, false } } func bigToFloat(x *big.Int) float64 { if x.IsInt64() { return float64(x.Int64()) } if f, err := strconv.ParseFloat(x.String(), 64); err == nil { return f } return math.Inf(x.Sign()) } gojq-0.12.13/go.dev.mod000066400000000000000000000002641443612442000145030ustar00rootroot00000000000000module github.com/itchyny/gojq go 1.18 require ( github.com/itchyny/astgen-go v0.0.0-20210914105503-cc8fccf6f972 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect ) gojq-0.12.13/go.dev.sum000066400000000000000000000006301443612442000145250ustar00rootroot00000000000000github.com/itchyny/astgen-go v0.0.0-20210914105503-cc8fccf6f972 h1:XYWolmPDLTY9B1O5o/Ad811/mtVkaHWMiZdbPLm/nDA= github.com/itchyny/astgen-go v0.0.0-20210914105503-cc8fccf6f972/go.mod h1:jTXcxGeQMJfFN3wWjtzb4aAaWDDN+QbezE0HjH1XfNk= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= gojq-0.12.13/go.mod000066400000000000000000000004751443612442000137320ustar00rootroot00000000000000module github.com/itchyny/gojq go 1.18 require ( github.com/google/go-cmp v0.5.4 github.com/itchyny/timefmt-go v0.1.5 github.com/mattn/go-isatty v0.0.19 github.com/mattn/go-runewidth v0.0.14 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/rivo/uniseg v0.4.4 // indirect golang.org/x/sys v0.8.0 // indirect ) gojq-0.12.13/go.sum000066400000000000000000000033251443612442000137540ustar00rootroot00000000000000github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gojq-0.12.13/gojq.go000066400000000000000000000003271443612442000141070ustar00rootroot00000000000000// 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 gojq-0.12.13/iter.go000066400000000000000000000014061443612442000141110ustar00rootroot00000000000000package gojq // Iter is an interface for an iterator. type Iter interface { Next() (any, bool) } // NewIter creates a new [Iter] from values. func NewIter(values ...any) Iter { switch len(values) { case 0: return emptyIter{} case 1: return &unitIter{value: values[0]} default: iter := sliceIter(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 []any func (iter *sliceIter) Next() (any, bool) { if len(*iter) == 0 { return nil, false } value := (*iter)[0] *iter = (*iter)[1:] return value, true } gojq-0.12.13/lexer.go000066400000000000000000000250001443612442000142610ustar00rootroot00000000000000package 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:]) l.offset += size l.token = string(r) } } return int(ch) } func (l *lexer) next() (byte, bool) { for { ch := l.source[l.offset] l.offset++ if ch == '#' { if len(l.source) == l.offset { return 0, true } for !isNewLine(l.source[l.offset]) { l.offset++ if len(l.source) == l.offset { return 0, true } } } else if !isWhite(ch) { return ch, false } else if len(l.source) == l.offset { return 0, true } } } 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 := 0; i < len(src); i++ { 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 } type parseError struct { offset int token string 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 (err *parseError) Token() (string, int) { return err.token, err.offset } 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' } func isNewLine(ch byte) bool { switch ch { case '\n', '\r': return true default: return false } } gojq-0.12.13/module_loader.go000066400000000000000000000077541443612442000157750ustar00rootroot00000000000000package 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. // // LoadModule(string) (*Query, error) // LoadModuleWithMeta(string, map[string]any) (*Query, error) // LoadInitModules() ([]*Query, error) // LoadJSON(string) (any, error) // LoadJSONWithMeta(string, map[string]any) (any, error) type ModuleLoader any // NewModuleLoader creates a new [ModuleLoader] reading local modules in the paths. func NewModuleLoader(paths []string) ModuleLoader { return &moduleLoader{expandHomeDir(paths)} } 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(path, string(cnt)) 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(path, string(cnt)) 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 := searchPath(meta); path != "" { paths = append([]string{path}, paths...) } for _, base := range paths { path := filepath.Clean(filepath.Join(base, name+extension)) if _, err := os.Stat(path); err == nil { return path, err } path = filepath.Clean(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) } // This is a dirty hack to implement the "search" field. func parseModule(path, cnt string) (*Query, error) { q, err := Parse(cnt) if err != nil { return nil, err } for _, i := range q.Imports { if i.Meta == nil { continue } i.Meta.KeyVals = append( i.Meta.KeyVals, &ConstObjectKeyVal{ Key: "$$path", Val: &ConstTerm{Str: path}, }, ) } return q, nil } func searchPath(meta map[string]any) string { x, ok := meta["search"] if !ok { return "" } s, ok := x.(string) if !ok { return "" } if filepath.IsAbs(s) { return s } if strings.HasPrefix(s, "~") { if homeDir, err := os.UserHomeDir(); err == nil { return filepath.Join(homeDir, s[1:]) } } var path string if x, ok := meta["$$path"]; ok { path, _ = x.(string) } if path == "" { return s } return filepath.Join(filepath.Dir(path), s) } func expandHomeDir(paths []string) []string { var homeDir string var err error for i, path := range paths { if strings.HasPrefix(path, "~") { if homeDir == "" && err == nil { homeDir, err = os.UserHomeDir() } if homeDir != "" { paths[i] = filepath.Join(homeDir, path[1:]) } } } return paths } gojq-0.12.13/normalize.go000066400000000000000000000027411443612442000151510ustar00rootroot00000000000000package gojq import ( "encoding/json" "math" "math/big" "strings" ) func normalizeNumber(v json.Number) any { if i, err := v.Int64(); err == nil && math.MinInt <= i && i <= math.MaxInt { return int(i) } if strings.ContainsAny(v.String(), ".eE") { if f, err := v.Float64(); err == nil { return f } } if bi, ok := new(big.Int).SetString(v.String(), 10); ok { return bi } if strings.HasPrefix(v.String(), "-") { return math.Inf(-1) } return math.Inf(1) } func normalizeNumbers(v any) any { switch v := v.(type) { case json.Number: return normalizeNumber(v) case *big.Int: if v.IsInt64() { if i := v.Int64(); math.MinInt <= i && i <= math.MaxInt { return int(i) } } return v case int64: if math.MinInt <= v && v <= math.MaxInt { return int(v) } return big.NewInt(v) case int32: return int(v) case int16: return int(v) case int8: return int(v) case uint: if v <= math.MaxInt { return int(v) } return new(big.Int).SetUint64(uint64(v)) case uint64: if v <= math.MaxInt { return int(v) } return new(big.Int).SetUint64(v) case uint32: if uint64(v) <= math.MaxInt { return int(v) } return new(big.Int).SetUint64(uint64(v)) case uint16: return int(v) case uint8: return int(v) case float32: return float64(v) case []any: for i, x := range v { v[i] = normalizeNumbers(x) } return v case map[string]any: for k, x := range v { v[k] = normalizeNumbers(x) } return v default: return v } } gojq-0.12.13/operator.go000066400000000000000000000240271443612442000150050ustar00rootroot00000000000000package gojq import ( "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 OpPipe: panic("unreachable") case OpComma: panic("unreachable") case OpAdd: return "_add" case OpSub: return "_subtract" case OpMul: return "_multiply" case OpDiv: return "_divide" case OpMod: 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 OpAnd: panic("unreachable") case OpOr: panic("unreachable") case OpAlt: panic("unreachable") case OpAssign: return "_assign" case OpModify: return "_modify" case OpUpdateAdd: return "_add" case OpUpdateSub: return "_subtract" case OpUpdateMul: return "_multiply" case OpUpdateDiv: return "_divide" case OpUpdateMod: return "_modulo" case OpUpdateAlt: return "_alternative" default: panic(op) } } func binopTypeSwitch( l, r any, callbackInts func(_, _ int) any, callbackFloats func(_, _ float64) any, callbackBigInts func(_, _ *big.Int) any, callbackStrings func(_, _ string) any, callbackArrays func(_, _ []any) any, callbackMaps func(_, _ map[string]any) any, fallback func(_, _ any) any) any { 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 default: return &unaryTypeError{"plus", v} } } func funcOpNegate(v any) any { switch v := v.(type) { case int: return -v case float64: return -v case *big.Int: return new(big.Int).Neg(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)) for k, v := range l { m[k] = v } for k, v := range r { m[k] = v } 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 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)) for k, v := range l { m[k] = v } 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 n <= 0.0 || len(s) > 0 && n > float64(0x10000000/len(s)) || math.IsNaN(n) { return nil } if int(n) < 1 { return s } return strings.Repeat(s, int(n)) } func funcOpDiv(_, l, r any) any { return binopTypeSwitch(l, r, func(l, r int) any { if r == 0 { if l == 0 { return math.NaN() } return &zeroDivisionError{l, r} } if l%r == 0 { return l / r } return float64(l) / float64(r) }, func(l, r float64) any { if r == 0.0 { if l == 0.0 { return math.NaN() } return &zeroDivisionError{l, r} } return l / r }, func(l, r *big.Int) any { if r.Sign() == 0 { if l.Sign() == 0 { return math.NaN() } 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 { if r == 0 { return &zeroModuloError{l, r} } return l % r }, func(l, r float64) any { 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 } gojq-0.12.13/option.go000066400000000000000000000073071443612442000144640ustar00rootroot00000000000000package 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, you should take account to int, float64 and *big.Int. // 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<= iter.max { return nil, false } v := iter.value iter.value++ return v, true } func ExampleWithIterFunction() { query, err := gojq.Parse("f(3; 7)") if err != nil { log.Fatalln(err) } code, err := gojq.Compile( query, gojq.WithIterFunction("f", 2, 2, func(_ any, xs []any) gojq.Iter { if x, ok := xs[0].(int); ok { if y, ok := xs[1].(int); ok { return &rangeIter{x, y} } } return gojq.NewIter(fmt.Errorf("f cannot be applied to: %v", xs)) }), ) if err != nil { log.Fatalln(err) } iter := code.Run(nil) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { log.Fatalln(err) } fmt.Printf("%#v\n", v) } // Output: // 3 // 4 // 5 // 6 } gojq-0.12.13/option_module_loader_test.go000066400000000000000000000016211443612442000204070ustar00rootroot00000000000000package gojq_test import ( "fmt" "log" "github.com/itchyny/gojq" ) type moduleLoader struct{} func (*moduleLoader) LoadModule(name string) (*gojq.Query, error) { switch name { case "module1": return gojq.Parse(` module { name: "module1", test: 42 }; import "module2" as foo; def f: foo::f; `) case "module2": return gojq.Parse(` def f: .foo; `) } return nil, fmt.Errorf("module not found: %q", name) } func ExampleWithModuleLoader() { query, err := gojq.Parse(` import "module1" as m; m::f `) if err != nil { log.Fatalln(err) } code, err := gojq.Compile( query, gojq.WithModuleLoader(&moduleLoader{}), ) if err != nil { log.Fatalln(err) } input := map[string]any{"foo": 42} iter := code.Run(input) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { log.Fatalln(err) } fmt.Printf("%#v\n", v) } // Output: // 42 } gojq-0.12.13/option_test.go000066400000000000000000000460721443612442000155250ustar00rootroot00000000000000package gojq_test import ( "encoding/json" "errors" "fmt" "reflect" "testing" "github.com/itchyny/gojq" ) func TestWithModuleLoaderError(t *testing.T) { query, err := gojq.Parse(` import "module1" as m; m::f `) if err != nil { t.Fatal(err) } _, err = gojq.Compile(query) if got, expected := err.Error(), `cannot load module: "module1"`; got != expected { t.Errorf("expected: %v, got: %v", expected, got) } query, err = gojq.Parse("modulemeta") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } iter := code.Run("m") v, ok := iter.Next() if !ok { t.Fatal("should emit an error but got no output") } if err, ok := v.(error); ok { if expected := `cannot load module: "m"`; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } v, ok = iter.Next() if ok { t.Errorf("should not emit a value but got: %v", v) } } func TestWithModuleLoader_modulemeta(t *testing.T) { query, err := gojq.Parse(` "module1", "module2" | modulemeta `) if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithModuleLoader(&moduleLoader{}), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) n := 0 for { got, ok := iter.Next() if !ok { break } if n == 0 { if expected := map[string]any{ "deps": []any{ map[string]any{ "relpath": "module2", "as": "foo", "is_data": false, }, }, "name": "module1", "test": 42, }; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } else { if expected := map[string]any{ "deps": []any{}, // not a nil-slice }; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } n++ } } type moduleLoaderJSON struct{} func (*moduleLoaderJSON) LoadJSON(name string) (any, error) { if name == "module1" { return []any{1.0, 42, json.Number("123")}, nil } return nil, fmt.Errorf("module not found: %q", name) } func TestWithModuleLoader_JSON(t *testing.T) { query, err := gojq.Parse(` import "module1" as $m; [$m, $m[1]*$m[2]] `) if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithModuleLoader(&moduleLoaderJSON{}), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if expected := []any{[]any{1.0, 42, 123}, 5166}; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } type moduleLoaderInitModules struct{} func (*moduleLoaderInitModules) LoadInitModules() ([]*gojq.Query, error) { query, err := gojq.Parse(` def f: 42; def g: f * f; `) if err != nil { return nil, err } return []*gojq.Query{query}, nil } func TestWithModuleLoader_LoadInitModules(t *testing.T) { query, err := gojq.Parse("g") if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithModuleLoader(&moduleLoaderInitModules{}), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if expected := 42 * 42; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestWithEnvironLoader(t *testing.T) { query, err := gojq.Parse("env") if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithEnvironLoader(func() []string { return []string{"foo=42", "bar=128", "baz", "qux=", "=0"} }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } expected := map[string]any{"foo": "42", "bar": "128", "qux": ""} if !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestWithEnvironLoaderEmpty(t *testing.T) { query, err := gojq.Parse("env") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { got, ok := iter.Next() if !ok { break } if expected := map[string]any{}; !reflect.DeepEqual(got, expected) { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestWithVariablesError0(t *testing.T) { query, err := gojq.Parse(".") if err != nil { t.Fatal(err) } for _, name := range []string{"", "$", "_x", "x", "$0x", "$$", "$x ", " $x"} { _, err = gojq.Compile( query, gojq.WithVariables([]string{name}), ) if err == nil { t.Fatalf("%q is not a valid variable name", name) } if got, expected := err.Error(), "invalid variable name: "+name; got != expected { t.Errorf("expected: %v, got: %v", expected, got) } } } func TestWithVariablesError1(t *testing.T) { query, err := gojq.Parse(".") if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithVariables([]string{"$x"}), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) v, ok := iter.Next() if !ok { t.Fatal("should emit an error but got no output") } if err, ok := v.(error); ok { if expected := "variable defined but not bound: $x"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } v, ok = iter.Next() if ok { t.Errorf("should not emit a value but got: %v", v) } } func TestWithVariablesError2(t *testing.T) { query, err := gojq.Parse(".") if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithVariables([]string{"$x"}), ) if err != nil { t.Fatal(err) } iter := code.Run(nil, 1, 2) v, ok := iter.Next() if !ok { t.Fatal("should emit an error but got no output") } if err, ok := v.(error); ok { if expected := "too many variable values provided"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } v, ok = iter.Next() if ok { t.Errorf("should not emit a value but got: %v", v) } } func TestWithFunction(t *testing.T) { options := []gojq.CompilerOption{ gojq.WithFunction("f", 0, 0, func(x any, _ []any) any { if x, ok := x.(int); ok { return x * 2 } return fmt.Errorf("f cannot be applied to: %v", x) }), gojq.WithFunction("g", 1, 1, func(x any, xs []any) any { if x, ok := x.(int); ok { if y, ok := xs[0].(int); ok { return x + y } } return fmt.Errorf("g cannot be applied to: %v, %v", x, xs) }), } query, err := gojq.Parse(".[] | f | g(3)") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, options...) if err != nil { t.Fatal(err) } iter := code.Run([]any{0, 1, 2, 3, 4}) n := 0 for { v, ok := iter.Next() if !ok { break } if expected := n*2 + 3; v != expected { t.Errorf("expected: %v, got: %v", expected, v) } n++ } if expected := 5; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } query, err = gojq.Parse( `("f/0", "f/1", "g/0", "g/1") as $f | builtins | any(. == $f)`, ) if err != nil { t.Fatal(err) } code, err = gojq.Compile(query, options...) if err != nil { t.Fatal(err) } iter = code.Run(nil) n = 0 for { v, ok := iter.Next() if !ok { break } if expected := n == 0 || n == 3; v != expected { t.Errorf("expected: %v, got: %v (n = %d)", expected, v, n) } n++ } if expected := 4; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestWithFunctionDuplicateName(t *testing.T) { options := []gojq.CompilerOption{ gojq.WithFunction("f", 0, 0, func(x any, _ []any) any { if x, ok := x.(int); ok { return x * 2 } return fmt.Errorf("f cannot be applied to: %v", x) }), gojq.WithFunction("f", 1, 1, func(x any, xs []any) any { if x, ok := x.(int); ok { if y, ok := xs[0].(int); ok { return x + y } } return fmt.Errorf("f cannot be applied to: %v, %v", x, xs) }), gojq.WithFunction("f", 0, 0, func(x any, _ []any) any { if x, ok := x.(int); ok { return x * 4 } return fmt.Errorf("f cannot be applied to: %v", x) }), gojq.WithFunction("f", 2, 2, func(x any, xs []any) any { if x, ok := x.(int); ok { if y, ok := xs[0].(int); ok { if z, ok := xs[1].(int); ok { return x * y * z } } } return fmt.Errorf("f cannot be applied to: %v, %v", x, xs) }), } query, err := gojq.Parse(".[] | f | f(3) | f(4; 5)") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, options...) if err != nil { t.Fatal(err) } iter := code.Run([]any{0, 1, 2, 3, 4}) n := 0 for { v, ok := iter.Next() if !ok { break } if expected := (n*4 + 3) * 4 * 5; v != expected { t.Errorf("expected: %v, got: %v", expected, v) } n++ } if expected := 5; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } query, err = gojq.Parse( `("f/0", "f/1", "f/2", "f/3") as $f | builtins | any(. == $f)`, ) if err != nil { t.Fatal(err) } code, err = gojq.Compile(query, options...) if err != nil { t.Fatal(err) } iter = code.Run(nil) n = 0 for { v, ok := iter.Next() if !ok { break } if expected := n < 3; v != expected { t.Errorf("expected: %v, got: %v (n = %d)", expected, v, n) } n++ } if expected := 4; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestWithFunctionMultipleArities(t *testing.T) { options := []gojq.CompilerOption{ gojq.WithFunction("f", 0, 4, func(x any, xs []any) any { if x, ok := x.(int); ok { x *= 2 for _, y := range xs { if y, ok := y.(int); ok { x += y } } return x } return fmt.Errorf("f cannot be applied to: %v, %v", x, xs) }), gojq.WithFunction("f", 2, 3, func(x any, xs []any) any { if x, ok := x.(int); ok { for _, y := range xs { if y, ok := y.(int); ok { x *= y } } return x } return fmt.Errorf("f cannot be applied to: %v, %v", x, xs) }), gojq.WithFunction("g", 0, 30, func(x any, xs []any) any { return nil }), } query, err := gojq.Parse(".[] | f | f(1) | f(2; 3) | f(4; 5; 6) | f(7; 8; 9; 10)") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, options...) if err != nil { t.Fatal(err) } iter := code.Run([]any{0, 1, 2, 3, 4}) n := 0 for { v, ok := iter.Next() if !ok { break } if expected := (((n*2*2+1)*2*3)*4*5*6)*2 + 7 + 8 + 9 + 10; v != expected { t.Errorf("expected: %v, got: %v", expected, v) } n++ } if expected := 5; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } query, err = gojq.Parse( `("f/0", "f/1", "f/2", "f/3", "f/4", "f/5") as $f | builtins | any(. == $f)`, ) if err != nil { t.Fatal(err) } code, err = gojq.Compile(query, options...) if err != nil { t.Fatal(err) } iter = code.Run(nil) n = 0 for { v, ok := iter.Next() if !ok { break } if expected := n < 5; v != expected { t.Errorf("expected: %v, got: %v (n = %d)", expected, v, n) } n++ } if expected := 6; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } type valueError struct { v any } func (err *valueError) Error() string { return "error: " + fmt.Sprint(err.v) } func (err *valueError) Value() any { return err.v } func TestWithFunctionValueError(t *testing.T) { expected := map[string]any{"foo": 42} query, err := gojq.Parse("try f catch .") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithFunction("f", 0, 0, func(x any, _ []any) any { return &valueError{expected} }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { v, ok := iter.Next() if !ok { break } if !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestWithFunctionCompileArgsError(t *testing.T) { query, err := gojq.Parse("f(g)") if err != nil { t.Fatal(err) } _, err = gojq.Compile(query, gojq.WithFunction("f", 0, 1, func(any, []any) any { return 0 }), ) if got, expected := err.Error(), "function not defined: g/0"; got != expected { t.Errorf("expected: %v, got: %v", expected, got) } } func TestWithFunctionArityError(t *testing.T) { query, err := gojq.Parse("f") if err != nil { t.Fatal(err) } for _, tc := range []struct{ min, max int }{{3, 2}, {-1, 3}, {0, 31}, {-1, 31}} { func() { defer func() { expected := fmt.Sprintf(`invalid arity for "f": %d, %d`, tc.min, tc.max) if got := recover(); got != expected { t.Errorf("expected: %v, got: %v", expected, got) } }() t.Fatal(gojq.Compile(query, gojq.WithFunction("f", tc.min, tc.max, func(any, []any) any { return 0 }), )) }() } } func TestWithIterFunction(t *testing.T) { query, err := gojq.Parse("f * g(5; 10), h, 10") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter(1, 2, 3) }), gojq.WithIterFunction("g", 2, 2, func(_ any, xs []any) gojq.Iter { if x, ok := xs[0].(int); ok { if y, ok := xs[1].(int); ok { return &rangeIter{x, y} } } return gojq.NewIter(fmt.Errorf("g cannot be applied to: %v", xs)) }), gojq.WithIterFunction("h", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter() }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) n := 0 for { v, ok := iter.Next() if !ok { break } if expected := (1 + n%3) * (5 + n/3); v != expected { t.Errorf("expected: %v, got: %v", expected, v) } n++ } if expected := 16; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestWithIterFunctionError(t *testing.T) { query, err := gojq.Parse("range(3) * (f, 0), f") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter(1, errors.New("error"), 3) }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) n := 0 for { v, ok := iter.Next() if !ok { break } switch n { case 0, 1, 2: if expected := n; v != expected { t.Errorf("expected: %v, got: %v", expected, v) } case 3, 5: if expected := "error"; v.(error).Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } default: if expected := n - 3; v != expected { t.Errorf("expected: %v, got: %v", expected, v) } } n++ } if expected := 7; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestWithIterFunctionPathIndexing(t *testing.T) { query, err := gojq.Parse(".[f] = 1") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter(0, 1, 2) }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { v, ok := iter.Next() if !ok { break } if expected := []any{1, 1, 1}; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestWithIterFunctionPathInputValue(t *testing.T) { query, err := gojq.Parse("{x: 0} | (f|.x) = 1") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(v any, _ []any) gojq.Iter { return gojq.NewIter(v, v, v) }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { v, ok := iter.Next() if !ok { break } if expected := map[string]any{"x": 1}; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestWithIterFunctionPathEmpty(t *testing.T) { query, err := gojq.Parse("{x: 0} | (f|.x) = 1") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter() }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) for { v, ok := iter.Next() if !ok { break } if expected := map[string]any{"x": 0}; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestWithIterFunctionInvalidPathError(t *testing.T) { query, err := gojq.Parse("{x: 0} | (f|.x) = 1") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter(map[string]any{"x": 1}) }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) v, ok := iter.Next() if !ok { t.Fatal("should emit an error but got no output") } if err, ok := v.(error); ok { if expected := `invalid path against: object ({"x":1})`; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } } func TestWithIterFunctionPathError(t *testing.T) { query, err := gojq.Parse("{x: 0} | (f|.x) = 1") if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter(errors.New("error")) }), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) v, ok := iter.Next() if !ok { t.Fatal("should emit an error but got no output") } if err, ok := v.(error); ok { if expected := "error"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } } func TestWithIterFunctionDefineError(t *testing.T) { query, err := gojq.Parse("f") if err != nil { t.Fatal(err) } defer func() { expected := `cannot define both iterator and non-iterator functions for "f"` if got := recover(); got != expected { t.Errorf("expected: %v, got: %v", expected, got) } }() t.Fatal(gojq.Compile(query, gojq.WithFunction("f", 0, 0, func(any, []any) any { return 0 }), gojq.WithIterFunction("f", 0, 0, func(any, []any) gojq.Iter { return gojq.NewIter() }), )) } type moduleLoader2 struct{} func (*moduleLoader2) LoadModule(name string) (*gojq.Query, error) { if name == "module1" { return gojq.Parse(` def g: def h: f * 3; h * 4; `) } return nil, fmt.Errorf("module not found: %q", name) } func TestWithFunctionWithModuleLoader(t *testing.T) { query, err := gojq.Parse(`include "module1"; .[] | g`) if err != nil { t.Fatal(err) } code, err := gojq.Compile(query, gojq.WithFunction("f", 0, 0, func(x any, _ []any) any { if x, ok := x.(int); ok { return x * 2 } return fmt.Errorf("f cannot be applied to: %v", x) }), gojq.WithModuleLoader(&moduleLoader2{}), ) if err != nil { t.Fatal(err) } iter := code.Run([]any{0, 1, 2, 3, 4}) n := 0 for { v, ok := iter.Next() if !ok { break } if expected := n * 2 * 3 * 4; v != expected { t.Errorf("expected: %v, got: %v", expected, v) } n++ } if expected := 5; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestWithInputIter(t *testing.T) { query, err := gojq.Parse("range(10) | input") if err != nil { t.Fatal(err) } code, err := gojq.Compile( query, gojq.WithInputIter(gojq.NewIter(0, 1, 2, 3, 4)), ) if err != nil { t.Fatal(err) } iter := code.Run(nil) n := 0 for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { if expected := "break"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else if v != n { t.Errorf("expected: %v, got: %v", n, v) } n++ } if expected := 10; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } gojq-0.12.13/option_variables_test.go000066400000000000000000000010241443612442000175410ustar00rootroot00000000000000package gojq_test import ( "fmt" "log" "github.com/itchyny/gojq" ) func ExampleWithVariables() { query, err := gojq.Parse("$x * 100 + $y, $z") if err != nil { log.Fatalln(err) } code, err := gojq.Compile( query, gojq.WithVariables([]string{ "$x", "$y", "$z", }), ) if err != nil { log.Fatalln(err) } iter := code.Run(nil, 12, 42, 128) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { log.Fatalln(err) } fmt.Printf("%#v\n", v) } // Output: // 1242 // 128 } gojq-0.12.13/parser.go000066400000000000000000001254731443612442000144550ustar00rootroot00000000000000// Code generated by goyacc -o parser.go parser.go.y. DO NOT EDIT. //line parser.go.y:2 package gojq import __yyfmt__ "fmt" // Parse a query string, and returns the query struct. // // If parsing failed, the returned error has the method Token() (string, int), // which reports the invalid token and the byte offset in the query string. The // token is empty if the error occurred after scanning the entire query string. // The byte offset is the scanned bytes when the error occurred. // //line parser.go.y:2 func Parse(src string) (*Query, error) { l := newLexer(src) if yyParse(l) > 0 { return nil, l.err } return l.result, nil } 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 } //line parser.go.y:33 type yySymType struct { yys int value any token string operator Operator } const tokAltOp = 57346 const tokUpdateOp = 57347 const tokDestAltOp = 57348 const tokOrOp = 57349 const tokAndOp = 57350 const tokCompareOp = 57351 const tokModule = 57352 const tokImport = 57353 const tokInclude = 57354 const tokDef = 57355 const tokAs = 57356 const tokLabel = 57357 const tokBreak = 57358 const tokNull = 57359 const tokTrue = 57360 const tokFalse = 57361 const tokIdent = 57362 const tokVariable = 57363 const tokModuleIdent = 57364 const tokModuleVariable = 57365 const tokIndex = 57366 const tokNumber = 57367 const tokFormat = 57368 const tokString = 57369 const tokStringStart = 57370 const tokStringQuery = 57371 const tokStringEnd = 57372 const tokIf = 57373 const tokThen = 57374 const tokElif = 57375 const tokElse = 57376 const tokEnd = 57377 const tokTry = 57378 const tokCatch = 57379 const tokReduce = 57380 const tokForeach = 57381 const tokRecurse = 57382 const tokFuncDefPost = 57383 const tokTermPost = 57384 const tokEmptyCatch = 57385 const tokInvalid = 57386 const tokInvalidEscapeSequence = 57387 const tokUnterminatedString = 57388 var yyToknames = [...]string{ "$end", "error", "$unk", "tokAltOp", "tokUpdateOp", "tokDestAltOp", "tokOrOp", "tokAndOp", "tokCompareOp", "tokModule", "tokImport", "tokInclude", "tokDef", "tokAs", "tokLabel", "tokBreak", "tokNull", "tokTrue", "tokFalse", "tokIdent", "tokVariable", "tokModuleIdent", "tokModuleVariable", "tokIndex", "tokNumber", "tokFormat", "tokString", "tokStringStart", "tokStringQuery", "tokStringEnd", "tokIf", "tokThen", "tokElif", "tokElse", "tokEnd", "tokTry", "tokCatch", "tokReduce", "tokForeach", "tokRecurse", "tokFuncDefPost", "tokTermPost", "tokEmptyCatch", "tokInvalid", "tokInvalidEscapeSequence", "tokUnterminatedString", "'|'", "','", "'+'", "'-'", "'*'", "'/'", "'%'", "'.'", "'?'", "'['", "';'", "':'", "'('", "')'", "']'", "'{'", "'}'", } var yyStatenames = [...]string{} const yyEofCode = 1 const yyErrCode = 2 const yyInitialStackSize = 16 //line parser.go.y:693 //line yacctab:1 var yyExca = [...]int16{ -1, 1, 1, -1, -2, 0, -1, 97, 55, 0, -2, 104, -1, 130, 5, 0, -2, 32, -1, 133, 9, 0, -2, 35, -1, 194, 58, 114, -2, 54, } const yyPrivate = 57344 const yyLast = 1127 var yyAct = [...]int16{ 86, 214, 174, 112, 12, 203, 9, 175, 111, 31, 190, 6, 156, 140, 117, 47, 95, 97, 93, 94, 89, 141, 49, 7, 179, 180, 181, 240, 246, 264, 239, 103, 177, 106, 178, 227, 164, 119, 107, 108, 105, 245, 102, 75, 76, 113, 77, 78, 79, 123, 226, 163, 211, 225, 259, 210, 142, 179, 180, 181, 158, 159, 143, 182, 122, 177, 224, 178, 219, 7, 235, 234, 104, 127, 243, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 72, 74, 80, 81, 82, 83, 84, 147, 73, 88, 182, 196, 73, 229, 195, 145, 7, 150, 228, 161, 166, 165, 157, 126, 125, 124, 144, 88, 258, 167, 80, 81, 82, 83, 84, 206, 73, 44, 242, 91, 90, 92, 183, 184, 82, 83, 84, 154, 73, 153, 267, 186, 49, 173, 42, 43, 100, 91, 90, 92, 99, 191, 120, 197, 256, 257, 200, 192, 201, 202, 188, 75, 76, 207, 77, 78, 79, 198, 199, 209, 42, 43, 216, 92, 215, 215, 218, 213, 113, 98, 75, 76, 185, 77, 78, 79, 204, 205, 101, 221, 222, 170, 155, 171, 169, 3, 28, 27, 230, 96, 220, 232, 176, 46, 223, 11, 80, 81, 82, 83, 84, 11, 73, 78, 79, 157, 241, 110, 8, 152, 237, 255, 236, 72, 74, 80, 81, 82, 83, 84, 85, 73, 79, 278, 160, 191, 277, 121, 189, 253, 254, 192, 248, 247, 187, 139, 249, 250, 208, 262, 260, 261, 215, 263, 80, 81, 82, 83, 84, 149, 73, 268, 269, 10, 270, 5, 4, 2, 1, 88, 272, 273, 80, 81, 82, 83, 84, 0, 73, 279, 0, 0, 271, 280, 51, 52, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 115, 116, 91, 90, 92, 0, 0, 42, 43, 0, 87, 63, 64, 65, 66, 67, 68, 69, 70, 71, 88, 0, 20, 0, 17, 37, 24, 25, 26, 38, 40, 39, 41, 23, 29, 30, 42, 43, 0, 114, 15, 0, 0, 212, 0, 16, 0, 13, 14, 22, 91, 90, 92, 0, 0, 0, 0, 0, 33, 34, 0, 0, 0, 21, 0, 36, 0, 148, 32, 0, 146, 35, 51, 52, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 115, 116, 0, 0, 0, 0, 0, 42, 43, 0, 0, 63, 64, 65, 66, 67, 68, 69, 70, 71, 18, 19, 20, 0, 17, 37, 24, 25, 26, 38, 40, 39, 41, 23, 29, 30, 42, 43, 0, 114, 15, 0, 0, 109, 0, 16, 0, 13, 14, 22, 0, 0, 0, 0, 0, 0, 0, 0, 33, 34, 0, 0, 0, 21, 0, 36, 0, 0, 32, 0, 20, 35, 17, 37, 24, 25, 26, 38, 40, 39, 41, 23, 29, 30, 42, 43, 0, 0, 15, 0, 0, 0, 0, 16, 0, 13, 14, 22, 0, 0, 0, 0, 0, 0, 0, 0, 33, 34, 0, 0, 0, 21, 0, 36, 0, 0, 32, 0, 231, 35, 20, 0, 17, 37, 24, 25, 26, 38, 40, 39, 41, 23, 29, 30, 42, 43, 0, 0, 15, 0, 0, 0, 0, 16, 0, 13, 14, 22, 0, 0, 0, 0, 0, 0, 0, 0, 33, 34, 0, 0, 0, 21, 0, 36, 0, 0, 32, 0, 118, 35, 20, 0, 17, 37, 24, 25, 26, 38, 40, 39, 41, 23, 29, 30, 42, 43, 0, 0, 15, 0, 77, 78, 79, 16, 0, 13, 14, 22, 0, 0, 0, 0, 0, 0, 0, 0, 33, 34, 0, 0, 0, 21, 0, 36, 0, 0, 32, 51, 52, 35, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 48, 0, 80, 81, 82, 83, 84, 50, 73, 0, 0, 63, 64, 65, 66, 67, 68, 69, 70, 71, 51, 52, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 48, 0, 0, 0, 0, 0, 0, 50, 0, 0, 172, 63, 64, 65, 66, 67, 68, 69, 70, 71, 51, 52, 0, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 115, 194, 0, 0, 0, 0, 0, 42, 43, 0, 45, 63, 64, 65, 66, 67, 68, 69, 70, 71, 37, 24, 25, 26, 38, 40, 39, 41, 23, 29, 30, 42, 43, 75, 76, 0, 77, 78, 79, 193, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 33, 34, 0, 0, 0, 21, 0, 36, 0, 0, 32, 75, 76, 35, 77, 78, 79, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 0, 75, 76, 252, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 0, 75, 76, 233, 77, 78, 79, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 0, 75, 76, 168, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 75, 76, 281, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 75, 76, 276, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 75, 76, 251, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 75, 76, 244, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 75, 76, 217, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 0, 75, 76, 162, 77, 78, 79, 0, 0, 0, 0, 0, 75, 76, 0, 77, 78, 79, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 275, 75, 76, 0, 77, 78, 79, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 266, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 265, 75, 76, 0, 77, 78, 79, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 0, 238, 0, 0, 0, 75, 76, 0, 77, 78, 79, 274, 0, 0, 75, 76, 0, 77, 78, 79, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 151, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72, 74, 80, 81, 82, 83, 84, 0, 73, 72, 74, 80, 81, 82, 83, 84, 0, 73, } var yyPact = [...]int16{ 181, -1000, -1000, -39, -1000, 387, 66, 621, -1000, 1071, -1000, 535, 289, 678, 678, 535, 535, 154, 119, 115, 164, 113, -1000, -1000, -1000, -1000, -1000, 13, -1000, -1000, 139, -1000, 535, 678, 678, 358, 485, 127, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 1, -1000, 53, 52, 51, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 535, -1000, 535, 535, 535, 535, 535, 535, 535, 535, 535, 535, 535, -1000, 1071, 0, -1000, -1000, -1000, 113, 302, 241, 89, 1062, 535, 98, 86, 174, -39, 2, -1000, -1000, 535, -1000, 921, 71, 71, -1000, -12, -1000, 49, 48, 535, -1000, -1000, -1000, -1000, 758, -1000, 160, -1000, 588, 40, 40, 40, 1071, 153, 153, 561, 201, 219, 67, 79, 79, 43, 43, 43, 131, -1000, -1000, 0, 654, -1000, -1000, -1000, 39, 535, 0, 0, 535, -1000, 535, 535, 162, 64, -1000, 535, 162, -5, 1071, -1000, -1000, 273, 678, 678, 897, -1000, -1000, -1000, 535, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, -1000, 7, -1000, -1000, 535, 0, 5, -1000, -13, -1000, 46, 41, 535, -1000, -1000, 435, 734, 12, 11, 1071, -1000, 1071, -39, -1000, -1000, -1000, 1005, -30, -1000, -1000, 535, -1000, -1000, 77, 71, 77, 16, 867, -1000, -20, -1000, 1071, -1000, -1000, 0, -1000, 654, 0, 0, 843, -1000, 703, -1000, 535, 535, 117, 57, -1000, -4, 162, 1071, 678, 678, -1000, -1000, 40, -1000, -1000, -1000, -1000, -29, -1000, 986, 975, 101, 535, 535, -1000, 535, -1000, 71, 77, -1000, 0, 535, 535, -1000, 1040, 1071, 951, -1000, 813, 172, 535, -1000, -1000, -1000, 535, 1071, 789, -1000, } var yyPgo = [...]int16{ 0, 264, 263, 262, 261, 259, 12, 214, 195, 244, 0, 241, 13, 240, 234, 10, 4, 9, 233, 20, 230, 218, 217, 215, 213, 8, 1, 2, 7, 199, 15, 198, 196, 5, 193, 192, 14, 3, } var yyR1 = [...]int8{ 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 33, 33, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 18, 18, 18, 34, 34, 35, 35, 19, 19, 19, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 25, 25, 25, 37, 37, 37, 26, 26, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 29, 29, 30, 30, 30, 31, 31, 32, 32, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, } var yyR2 = [...]int8{ 0, 2, 0, 3, 2, 2, 0, 2, 6, 4, 0, 1, 0, 2, 5, 8, 1, 3, 1, 1, 2, 3, 5, 9, 9, 11, 7, 3, 4, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 3, 1, 3, 3, 1, 3, 1, 3, 3, 3, 5, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 4, 1, 1, 1, 2, 1, 3, 2, 2, 2, 3, 4, 2, 3, 2, 2, 2, 2, 3, 3, 1, 3, 0, 2, 4, 1, 1, 1, 1, 2, 3, 4, 4, 5, 1, 3, 0, 5, 0, 2, 0, 2, 1, 3, 3, 3, 5, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 1, 3, 3, 3, 3, 2, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, } var yyChk = [...]int16{ -1000, -1, -2, 10, -3, -4, -28, 62, -7, -10, -5, -8, -16, 38, 39, 31, 36, 15, 11, 12, 13, 54, 40, 24, 17, 18, 19, -34, -35, 25, 26, -17, 59, 49, 50, 62, 56, 16, 20, 22, 21, 23, 27, 28, 57, 63, -29, -30, 20, -36, 27, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 31, 32, 33, 34, 35, 36, 37, 38, 39, 47, 55, 48, 4, 5, 7, 8, 9, 49, 50, 51, 52, 53, -7, -10, 14, 24, -19, 55, 54, 56, -16, -16, -10, -8, -10, 21, 27, 27, 20, -19, -17, 59, -17, -10, -16, -16, 63, -24, -25, -37, -17, 59, 20, 21, -36, 61, -10, 21, -18, 63, 48, 58, 58, 58, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -11, -12, 21, 56, 62, -19, -17, 61, -10, 58, 14, 14, 32, -23, 37, 47, 14, -6, -28, 58, 59, -20, -10, 60, 63, 48, 58, 58, -10, 61, 30, 27, 29, 63, -30, -27, -28, -31, 25, 27, 17, 18, 19, 56, -27, -27, 47, 6, -13, -12, -14, -15, -37, -17, 59, 21, 61, 58, -10, -12, -12, -10, -10, -10, -33, 20, 21, 57, -10, -9, -33, 60, 57, 63, -25, -26, -16, -26, 60, -10, 61, -32, -27, -10, -12, 61, 48, 63, 48, 58, 58, -10, 61, -10, 61, 59, 59, -21, -6, 57, 60, 57, -10, 47, 58, 60, 61, 48, -12, -15, -12, -12, 60, 61, -10, -10, -22, 33, 34, 57, 58, -33, -16, -26, -27, 58, 57, 57, 35, -10, -10, -10, -12, -10, -10, 32, 57, 60, 60, 57, -10, -10, 60, } var yyDef = [...]int16{ 2, -2, 6, 0, 1, 12, 0, 0, 4, 5, 7, 12, 41, 0, 0, 0, 0, 0, 0, 0, 0, 55, 56, 57, 60, 61, 62, 63, 65, 66, 67, 69, 0, 0, 0, 0, 0, 0, 89, 90, 91, 92, 84, 86, 3, 125, 0, 128, 0, 0, 0, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 20, 0, 79, 80, 81, 0, 0, 0, 0, 0, 0, -2, 0, 0, 10, 0, 58, 59, 0, 68, 0, 71, 72, 73, 0, 106, 111, 112, 0, 113, 114, 115, 76, 0, 78, 0, 126, 0, 0, 0, 0, 21, 30, 31, -2, 33, 34, -2, 36, 37, 38, 39, 40, 0, 42, 44, 0, 0, 82, 83, 93, 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 11, 0, 0, 0, 98, 70, 74, 0, 0, 0, 0, 77, 85, 87, 0, 127, 129, 130, 118, 119, 120, 121, 122, 123, 124, 0, 131, 132, 0, 0, 0, 47, 0, 49, 0, 0, 0, -2, 94, 0, 0, 0, 0, 100, 105, 28, 10, 18, 19, 9, 0, 0, 16, 64, 0, 75, 107, 108, 116, 109, 0, 0, 133, 0, 135, 22, 43, 45, 0, 46, 0, 0, 0, 0, 95, 0, 96, 0, 0, 102, 0, 14, 0, 0, 99, 0, 0, 88, 134, 0, 48, 50, 51, 52, 0, 97, 0, 0, 0, 0, 0, 8, 0, 17, 117, 110, 136, 0, 0, 0, 26, 0, 103, 0, 53, 0, 0, 0, 15, 23, 24, 0, 101, 0, 25, } var yyTok1 = [...]int8{ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 53, 3, 3, 59, 60, 51, 49, 48, 50, 54, 52, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 58, 57, 3, 3, 3, 55, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 56, 3, 61, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 62, 47, 63, } var yyTok2 = [...]int8{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, } var yyTok3 = [...]int8{ 0, } var yyErrorMessages = [...]struct { state int token int msg string }{} //line yaccpar:1 /* parser for yacc output */ var ( yyDebug = 0 yyErrorVerbose = false ) type yyLexer interface { Lex(lval *yySymType) int Error(s string) } type yyParser interface { Parse(yyLexer) int Lookahead() int } type yyParserImpl struct { lval yySymType stack [yyInitialStackSize]yySymType char int } func (p *yyParserImpl) Lookahead() int { return p.char } func yyNewParser() yyParser { return &yyParserImpl{} } const yyFlag = -1000 func yyTokname(c int) string { if c >= 1 && c-1 < len(yyToknames) { if yyToknames[c-1] != "" { return yyToknames[c-1] } } return __yyfmt__.Sprintf("tok-%v", c) } func yyStatname(s int) string { if s >= 0 && s < len(yyStatenames) { if yyStatenames[s] != "" { return yyStatenames[s] } } return __yyfmt__.Sprintf("state-%v", s) } func yyErrorMessage(state, lookAhead int) string { const TOKSTART = 4 if !yyErrorVerbose { return "syntax error" } for _, e := range yyErrorMessages { if e.state == state && e.token == lookAhead { return "syntax error: " + e.msg } } res := "syntax error: unexpected " + yyTokname(lookAhead) // To match Bison, suggest at most four expected tokens. expected := make([]int, 0, 4) // Look for shiftable tokens. base := int(yyPact[state]) for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { if len(expected) == cap(expected) { return res } expected = append(expected, tok) } } if yyDef[state] == -2 { i := 0 for yyExca[i] != -1 || int(yyExca[i+1]) != state { i += 2 } // Look for tokens that we accept or reduce. for i += 2; yyExca[i] >= 0; i += 2 { tok := int(yyExca[i]) if tok < TOKSTART || yyExca[i+1] == 0 { continue } if len(expected) == cap(expected) { return res } expected = append(expected, tok) } // If the default action is to accept or reduce, give up. if yyExca[i+1] != 0 { return res } } for i, tok := range expected { if i == 0 { res += ", expecting " } else { res += " or " } res += yyTokname(tok) } return res } func yylex1(lex yyLexer, lval *yySymType) (char, token int) { token = 0 char = lex.Lex(lval) if char <= 0 { token = int(yyTok1[0]) goto out } if char < len(yyTok1) { token = int(yyTok1[char]) goto out } if char >= yyPrivate { if char < yyPrivate+len(yyTok2) { token = int(yyTok2[char-yyPrivate]) goto out } } for i := 0; i < len(yyTok3); i += 2 { token = int(yyTok3[i+0]) if token == char { token = int(yyTok3[i+1]) goto out } } out: if token == 0 { token = int(yyTok2[1]) /* unknown char */ } if yyDebug >= 3 { __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) } return char, token } func yyParse(yylex yyLexer) int { return yyNewParser().Parse(yylex) } func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { var yyn int var yyVAL yySymType var yyDollar []yySymType _ = yyDollar // silence set and not used yyS := yyrcvr.stack[:] Nerrs := 0 /* number of errors */ Errflag := 0 /* error recovery flag */ yystate := 0 yyrcvr.char = -1 yytoken := -1 // yyrcvr.char translated into internal numbering defer func() { // Make sure we report no lookahead when not parsing. yystate = -1 yyrcvr.char = -1 yytoken = -1 }() yyp := -1 goto yystack ret0: return 0 ret1: return 1 yystack: /* put a state and value onto the stack */ if yyDebug >= 4 { __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) } yyp++ if yyp >= len(yyS) { nyys := make([]yySymType, len(yyS)*2) copy(nyys, yyS) yyS = nyys } yyS[yyp] = yyVAL yyS[yyp].yys = yystate yynewstate: yyn = int(yyPact[yystate]) if yyn <= yyFlag { goto yydefault /* simple state */ } if yyrcvr.char < 0 { yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) } yyn += yytoken if yyn < 0 || yyn >= yyLast { goto yydefault } yyn = int(yyAct[yyn]) if int(yyChk[yyn]) == yytoken { /* valid shift */ yyrcvr.char = -1 yytoken = -1 yyVAL = yyrcvr.lval yystate = yyn if Errflag > 0 { Errflag-- } goto yystack } yydefault: /* default state action */ yyn = int(yyDef[yystate]) if yyn == -2 { if yyrcvr.char < 0 { yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) } /* look through exception table */ xi := 0 for { if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { break } xi += 2 } for xi += 2; ; xi += 2 { yyn = int(yyExca[xi+0]) if yyn < 0 || yyn == yytoken { break } } yyn = int(yyExca[xi+1]) if yyn < 0 { goto ret0 } } if yyn == 0 { /* error ... attempt to resume parsing */ switch Errflag { case 0: /* brand new error */ yylex.Error(yyErrorMessage(yystate, yytoken)) Nerrs++ if yyDebug >= 1 { __yyfmt__.Printf("%s", yyStatname(yystate)) __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) } fallthrough case 1, 2: /* incompletely recovered error ... try again */ Errflag = 3 /* find a state where "error" is a legal shift action */ for yyp >= 0 { yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode if yyn >= 0 && yyn < yyLast { yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ if int(yyChk[yystate]) == yyErrCode { goto yystack } } /* the current p has no shift on "error", pop stack */ if yyDebug >= 2 { __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) } yyp-- } /* there is no state on the stack with an error shift ... abort */ goto ret1 case 3: /* no shift yet; clobber input char */ if yyDebug >= 2 { __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) } if yytoken == yyEofCode { goto ret1 } yyrcvr.char = -1 yytoken = -1 goto yynewstate /* try again in the same state */ } } /* reduction by production yyn */ if yyDebug >= 2 { __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) } yynt := yyn yypt := yyp _ = yypt // guard against "declared and not used" yyp -= int(yyR2[yyn]) // yyp is now the index of $0. Perform the default action. Iff the // reduced production is ε, $1 is possibly out of range. if yyp+1 >= len(yyS) { nyys := make([]yySymType, len(yyS)*2) copy(nyys, yyS) yyS = nyys } yyVAL = yyS[yyp+1] /* consult goto table to find next state */ yyn = int(yyR1[yyn]) yyg := int(yyPgo[yyn]) yyj := yyg + yyS[yyp].yys + 1 if yyj >= yyLast { yystate = int(yyAct[yyg]) } else { yystate = int(yyAct[yyj]) if int(yyChk[yystate]) != -yyn { yystate = int(yyAct[yyg]) } } // dummy call; replaced with literal code switch yynt { case 1: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:73 { if yyDollar[1].value != nil { yyDollar[2].value.(*Query).Meta = yyDollar[1].value.(*ConstObject) } yylex.(*lexer).result = yyDollar[2].value.(*Query) } case 2: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:80 { yyVAL.value = nil } case 3: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:84 { yyVAL.value = yyDollar[2].value } case 4: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:90 { yyVAL.value = &Query{Imports: yyDollar[1].value.([]*Import), FuncDefs: reverseFuncDef(yyDollar[2].value.([]*FuncDef)), Term: &Term{Type: TermTypeIdentity}} } case 5: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:94 { if yyDollar[1].value != nil { yyDollar[2].value.(*Query).Imports = yyDollar[1].value.([]*Import) } yyVAL.value = yyDollar[2].value } case 6: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:101 { yyVAL.value = []*Import(nil) } case 7: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:105 { yyVAL.value = append(yyDollar[1].value.([]*Import), yyDollar[2].value.(*Import)) } case 8: yyDollar = yyS[yypt-6 : yypt+1] //line parser.go.y:111 { yyVAL.value = &Import{ImportPath: yyDollar[2].token, ImportAlias: yyDollar[4].token, Meta: yyDollar[5].value.(*ConstObject)} } case 9: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:115 { yyVAL.value = &Import{IncludePath: yyDollar[2].token, Meta: yyDollar[3].value.(*ConstObject)} } case 10: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:121 { yyVAL.value = (*ConstObject)(nil) } case 11: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:124 { } case 12: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:128 { yyVAL.value = []*FuncDef(nil) } case 13: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:132 { yyVAL.value = append(yyDollar[2].value.([]*FuncDef), yyDollar[1].value.(*FuncDef)) } case 14: yyDollar = yyS[yypt-5 : yypt+1] //line parser.go.y:138 { yyVAL.value = &FuncDef{Name: yyDollar[2].token, Body: yyDollar[4].value.(*Query)} } case 15: yyDollar = yyS[yypt-8 : yypt+1] //line parser.go.y:142 { yyVAL.value = &FuncDef{yyDollar[2].token, yyDollar[4].value.([]string), yyDollar[7].value.(*Query)} } case 16: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:148 { yyVAL.value = []string{yyDollar[1].token} } case 17: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:152 { yyVAL.value = append(yyDollar[1].value.([]string), yyDollar[3].token) } case 18: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:157 { } case 19: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:158 { } case 20: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:162 { yyDollar[2].value.(*Query).FuncDefs = prependFuncDef(yyDollar[2].value.(*Query).FuncDefs, yyDollar[1].value.(*FuncDef)) yyVAL.value = yyDollar[2].value } case 21: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:167 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpPipe, Right: yyDollar[3].value.(*Query)} } case 22: yyDollar = yyS[yypt-5 : yypt+1] //line parser.go.y:171 { yyDollar[1].value.(*Term).SuffixList = append(yyDollar[1].value.(*Term).SuffixList, &Suffix{Bind: &Bind{yyDollar[3].value.([]*Pattern), yyDollar[5].value.(*Query)}}) yyVAL.value = &Query{Term: yyDollar[1].value.(*Term)} } case 23: yyDollar = yyS[yypt-9 : yypt+1] //line parser.go.y:176 { yyVAL.value = &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{yyDollar[2].value.(*Term), yyDollar[4].value.(*Pattern), yyDollar[6].value.(*Query), yyDollar[8].value.(*Query)}}} } case 24: yyDollar = yyS[yypt-9 : yypt+1] //line parser.go.y:180 { yyVAL.value = &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{yyDollar[2].value.(*Term), yyDollar[4].value.(*Pattern), yyDollar[6].value.(*Query), yyDollar[8].value.(*Query), nil}}} } case 25: yyDollar = yyS[yypt-11 : yypt+1] //line parser.go.y:184 { yyVAL.value = &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{yyDollar[2].value.(*Term), yyDollar[4].value.(*Pattern), yyDollar[6].value.(*Query), yyDollar[8].value.(*Query), yyDollar[10].value.(*Query)}}} } case 26: yyDollar = yyS[yypt-7 : yypt+1] //line parser.go.y:188 { yyVAL.value = &Query{Term: &Term{Type: TermTypeIf, If: &If{yyDollar[2].value.(*Query), yyDollar[4].value.(*Query), yyDollar[5].value.([]*IfElif), yyDollar[6].value.(*Query)}}} } case 27: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:192 { yyVAL.value = &Query{Term: &Term{Type: TermTypeTry, Try: &Try{yyDollar[2].value.(*Query), yyDollar[3].value.(*Query)}}} } case 28: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:196 { yyVAL.value = &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{yyDollar[2].token, yyDollar[4].value.(*Query)}}} } case 29: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:200 { if t := yyDollar[1].value.(*Query).Term; t != nil { t.SuffixList = append(t.SuffixList, &Suffix{Optional: true}) } else { yyVAL.value = &Query{Term: &Term{Type: TermTypeQuery, Query: yyDollar[1].value.(*Query), SuffixList: []*Suffix{{Optional: true}}}} } } case 30: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:208 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpComma, Right: yyDollar[3].value.(*Query)} } case 31: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:212 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: yyDollar[2].operator, Right: yyDollar[3].value.(*Query)} } case 32: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:216 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: yyDollar[2].operator, Right: yyDollar[3].value.(*Query)} } case 33: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:220 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpOr, Right: yyDollar[3].value.(*Query)} } case 34: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:224 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpAnd, Right: yyDollar[3].value.(*Query)} } case 35: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:228 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: yyDollar[2].operator, Right: yyDollar[3].value.(*Query)} } case 36: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:232 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpAdd, Right: yyDollar[3].value.(*Query)} } case 37: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:236 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpSub, Right: yyDollar[3].value.(*Query)} } case 38: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:240 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpMul, Right: yyDollar[3].value.(*Query)} } case 39: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:244 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpDiv, Right: yyDollar[3].value.(*Query)} } case 40: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:248 { yyVAL.value = &Query{Left: yyDollar[1].value.(*Query), Op: OpMod, Right: yyDollar[3].value.(*Query)} } case 41: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:252 { yyVAL.value = &Query{Term: yyDollar[1].value.(*Term)} } case 42: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:258 { yyVAL.value = []*Pattern{yyDollar[1].value.(*Pattern)} } case 43: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:262 { yyVAL.value = append(yyDollar[1].value.([]*Pattern), yyDollar[3].value.(*Pattern)) } case 44: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:268 { yyVAL.value = &Pattern{Name: yyDollar[1].token} } case 45: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:272 { yyVAL.value = &Pattern{Array: yyDollar[2].value.([]*Pattern)} } case 46: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:276 { yyVAL.value = &Pattern{Object: yyDollar[2].value.([]*PatternObject)} } case 47: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:282 { yyVAL.value = []*Pattern{yyDollar[1].value.(*Pattern)} } case 48: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:286 { yyVAL.value = append(yyDollar[1].value.([]*Pattern), yyDollar[3].value.(*Pattern)) } case 49: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:292 { yyVAL.value = []*PatternObject{yyDollar[1].value.(*PatternObject)} } case 50: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:296 { yyVAL.value = append(yyDollar[1].value.([]*PatternObject), yyDollar[3].value.(*PatternObject)) } case 51: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:302 { yyVAL.value = &PatternObject{Key: yyDollar[1].token, Val: yyDollar[3].value.(*Pattern)} } case 52: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:306 { yyVAL.value = &PatternObject{KeyString: yyDollar[1].value.(*String), Val: yyDollar[3].value.(*Pattern)} } case 53: yyDollar = yyS[yypt-5 : yypt+1] //line parser.go.y:310 { yyVAL.value = &PatternObject{KeyQuery: yyDollar[2].value.(*Query), Val: yyDollar[5].value.(*Pattern)} } case 54: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:314 { yyVAL.value = &PatternObject{Key: yyDollar[1].token} } case 55: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:320 { yyVAL.value = &Term{Type: TermTypeIdentity} } case 56: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:324 { yyVAL.value = &Term{Type: TermTypeRecurse} } case 57: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:328 { yyVAL.value = &Term{Type: TermTypeIndex, Index: &Index{Name: yyDollar[1].token}} } case 58: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:332 { if yyDollar[2].value.(*Suffix).Iter { yyVAL.value = &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{yyDollar[2].value.(*Suffix)}} } else { yyVAL.value = &Term{Type: TermTypeIndex, Index: yyDollar[2].value.(*Suffix).Index} } } case 59: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:340 { yyVAL.value = &Term{Type: TermTypeIndex, Index: &Index{Str: yyDollar[2].value.(*String)}} } case 60: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:344 { yyVAL.value = &Term{Type: TermTypeNull} } case 61: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:348 { yyVAL.value = &Term{Type: TermTypeTrue} } case 62: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:352 { yyVAL.value = &Term{Type: TermTypeFalse} } case 63: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:356 { yyVAL.value = &Term{Type: TermTypeFunc, Func: &Func{Name: yyDollar[1].token}} } case 64: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:360 { yyVAL.value = &Term{Type: TermTypeFunc, Func: &Func{Name: yyDollar[1].token, Args: yyDollar[3].value.([]*Query)}} } case 65: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:364 { yyVAL.value = &Term{Type: TermTypeFunc, Func: &Func{Name: yyDollar[1].token}} } case 66: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:368 { yyVAL.value = &Term{Type: TermTypeNumber, Number: yyDollar[1].token} } case 67: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:372 { yyVAL.value = &Term{Type: TermTypeFormat, Format: yyDollar[1].token} } case 68: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:376 { yyVAL.value = &Term{Type: TermTypeFormat, Format: yyDollar[1].token, Str: yyDollar[2].value.(*String)} } case 69: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:380 { yyVAL.value = &Term{Type: TermTypeString, Str: yyDollar[1].value.(*String)} } case 70: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:384 { yyVAL.value = &Term{Type: TermTypeQuery, Query: yyDollar[2].value.(*Query)} } case 71: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:388 { yyVAL.value = &Term{Type: TermTypeUnary, Unary: &Unary{OpAdd, yyDollar[2].value.(*Term)}} } case 72: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:392 { yyVAL.value = &Term{Type: TermTypeUnary, Unary: &Unary{OpSub, yyDollar[2].value.(*Term)}} } case 73: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:396 { yyVAL.value = &Term{Type: TermTypeObject, Object: &Object{}} } case 74: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:400 { yyVAL.value = &Term{Type: TermTypeObject, Object: &Object{yyDollar[2].value.([]*ObjectKeyVal)}} } case 75: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:404 { yyVAL.value = &Term{Type: TermTypeObject, Object: &Object{yyDollar[2].value.([]*ObjectKeyVal)}} } case 76: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:408 { yyVAL.value = &Term{Type: TermTypeArray, Array: &Array{}} } case 77: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:412 { yyVAL.value = &Term{Type: TermTypeArray, Array: &Array{yyDollar[2].value.(*Query)}} } case 78: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:416 { yyVAL.value = &Term{Type: TermTypeBreak, Break: yyDollar[2].token} } case 79: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:420 { yyDollar[1].value.(*Term).SuffixList = append(yyDollar[1].value.(*Term).SuffixList, &Suffix{Index: &Index{Name: yyDollar[2].token}}) } case 80: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:424 { yyDollar[1].value.(*Term).SuffixList = append(yyDollar[1].value.(*Term).SuffixList, yyDollar[2].value.(*Suffix)) } case 81: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:428 { yyDollar[1].value.(*Term).SuffixList = append(yyDollar[1].value.(*Term).SuffixList, &Suffix{Optional: true}) } case 82: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:432 { yyDollar[1].value.(*Term).SuffixList = append(yyDollar[1].value.(*Term).SuffixList, yyDollar[3].value.(*Suffix)) } case 83: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:436 { yyDollar[1].value.(*Term).SuffixList = append(yyDollar[1].value.(*Term).SuffixList, &Suffix{Index: &Index{Str: yyDollar[3].value.(*String)}}) } case 84: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:442 { yyVAL.value = &String{Str: yyDollar[1].token} } case 85: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:446 { yyVAL.value = &String{Queries: yyDollar[2].value.([]*Query)} } case 86: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:452 { yyVAL.value = []*Query{} } case 87: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:456 { yyVAL.value = append(yyDollar[1].value.([]*Query), &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: yyDollar[2].token}}}) } case 88: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:460 { yylex.(*lexer).inString = true yyVAL.value = append(yyDollar[1].value.([]*Query), &Query{Term: &Term{Type: TermTypeQuery, Query: yyDollar[3].value.(*Query)}}) } case 89: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:466 { } case 90: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:467 { } case 91: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:470 { } case 92: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:471 { } case 93: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:475 { yyVAL.value = &Suffix{Iter: true} } case 94: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:479 { yyVAL.value = &Suffix{Index: &Index{Start: yyDollar[2].value.(*Query)}} } case 95: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:483 { yyVAL.value = &Suffix{Index: &Index{Start: yyDollar[2].value.(*Query), IsSlice: true}} } case 96: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:487 { yyVAL.value = &Suffix{Index: &Index{End: yyDollar[3].value.(*Query), IsSlice: true}} } case 97: yyDollar = yyS[yypt-5 : yypt+1] //line parser.go.y:491 { yyVAL.value = &Suffix{Index: &Index{Start: yyDollar[2].value.(*Query), End: yyDollar[4].value.(*Query), IsSlice: true}} } case 98: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:497 { yyVAL.value = []*Query{yyDollar[1].value.(*Query)} } case 99: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:501 { yyVAL.value = append(yyDollar[1].value.([]*Query), yyDollar[3].value.(*Query)) } case 100: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:507 { yyVAL.value = []*IfElif(nil) } case 101: yyDollar = yyS[yypt-5 : yypt+1] //line parser.go.y:511 { yyVAL.value = append(yyDollar[1].value.([]*IfElif), &IfElif{yyDollar[3].value.(*Query), yyDollar[5].value.(*Query)}) } case 102: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:517 { yyVAL.value = (*Query)(nil) } case 103: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:521 { yyVAL.value = yyDollar[2].value } case 104: yyDollar = yyS[yypt-0 : yypt+1] //line parser.go.y:527 { yyVAL.value = (*Query)(nil) } case 105: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:531 { yyVAL.value = yyDollar[2].value } case 106: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:537 { yyVAL.value = []*ObjectKeyVal{yyDollar[1].value.(*ObjectKeyVal)} } case 107: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:541 { yyVAL.value = append(yyDollar[1].value.([]*ObjectKeyVal), yyDollar[3].value.(*ObjectKeyVal)) } case 108: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:547 { yyVAL.value = &ObjectKeyVal{Key: yyDollar[1].token, Val: yyDollar[3].value.(*ObjectVal)} } case 109: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:551 { yyVAL.value = &ObjectKeyVal{KeyString: yyDollar[1].value.(*String), Val: yyDollar[3].value.(*ObjectVal)} } case 110: yyDollar = yyS[yypt-5 : yypt+1] //line parser.go.y:555 { yyVAL.value = &ObjectKeyVal{KeyQuery: yyDollar[2].value.(*Query), Val: yyDollar[5].value.(*ObjectVal)} } case 111: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:559 { yyVAL.value = &ObjectKeyVal{Key: yyDollar[1].token} } case 112: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:563 { yyVAL.value = &ObjectKeyVal{KeyString: yyDollar[1].value.(*String)} } case 113: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:568 { } case 114: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:569 { } case 115: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:570 { } case 116: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:574 { yyVAL.value = &ObjectVal{[]*Query{{Term: yyDollar[1].value.(*Term)}}} } case 117: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:578 { yyVAL.value = &ObjectVal{append(yyDollar[1].value.(*ObjectVal).Queries, &Query{Term: yyDollar[3].value.(*Term)})} } case 118: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:584 { yyVAL.value = &ConstTerm{Object: yyDollar[1].value.(*ConstObject)} } case 119: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:588 { yyVAL.value = &ConstTerm{Array: yyDollar[1].value.(*ConstArray)} } case 120: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:592 { yyVAL.value = &ConstTerm{Number: yyDollar[1].token} } case 121: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:596 { yyVAL.value = &ConstTerm{Str: yyDollar[1].token} } case 122: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:600 { yyVAL.value = &ConstTerm{Null: true} } case 123: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:604 { yyVAL.value = &ConstTerm{True: true} } case 124: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:608 { yyVAL.value = &ConstTerm{False: true} } case 125: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:614 { yyVAL.value = &ConstObject{} } case 126: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:618 { yyVAL.value = &ConstObject{yyDollar[2].value.([]*ConstObjectKeyVal)} } case 127: yyDollar = yyS[yypt-4 : yypt+1] //line parser.go.y:622 { yyVAL.value = &ConstObject{yyDollar[2].value.([]*ConstObjectKeyVal)} } case 128: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:628 { yyVAL.value = []*ConstObjectKeyVal{yyDollar[1].value.(*ConstObjectKeyVal)} } case 129: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:632 { yyVAL.value = append(yyDollar[1].value.([]*ConstObjectKeyVal), yyDollar[3].value.(*ConstObjectKeyVal)) } case 130: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:638 { yyVAL.value = &ConstObjectKeyVal{Key: yyDollar[1].token, Val: yyDollar[3].value.(*ConstTerm)} } case 131: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:642 { yyVAL.value = &ConstObjectKeyVal{Key: yyDollar[1].token, Val: yyDollar[3].value.(*ConstTerm)} } case 132: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:646 { yyVAL.value = &ConstObjectKeyVal{KeyString: yyDollar[1].token, Val: yyDollar[3].value.(*ConstTerm)} } case 133: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:652 { yyVAL.value = &ConstArray{} } case 134: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:656 { yyVAL.value = &ConstArray{yyDollar[2].value.([]*ConstTerm)} } case 135: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:662 { yyVAL.value = []*ConstTerm{yyDollar[1].value.(*ConstTerm)} } case 136: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:666 { yyVAL.value = append(yyDollar[1].value.([]*ConstTerm), yyDollar[3].value.(*ConstTerm)) } case 137: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:671 { } case 138: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:672 { } case 139: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:673 { } case 140: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:674 { } case 141: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:675 { } case 142: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:676 { } case 143: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:677 { } case 144: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:678 { } case 145: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:679 { } case 146: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:680 { } case 147: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:681 { } case 148: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:682 { } case 149: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:683 { } case 150: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:684 { } case 151: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:685 { } case 152: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:686 { } case 153: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:687 { } case 154: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:688 { } case 155: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:689 { } case 156: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:690 { } case 157: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:691 { } } goto yystack /* stack new state and value */ } gojq-0.12.13/parser.go.y000066400000000000000000000370251443612442000147170ustar00rootroot00000000000000%{ package gojq // Parse a query string, and returns the query struct. // // If parsing failed, the returned error has the method Token() (string, int), // which reports the invalid token and the byte offset in the query string. The // token is empty if the error occurred after scanning the entire query string. // The byte offset is the scanned bytes when the error occurred. func Parse(src string) (*Query, error) { l := newLexer(src) if yyParse(l) > 0 { return nil, l.err } return l.result, nil } 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 program moduleheader programbody imports import metaopt funcdefs funcdef funcdefargs query %type bindpatterns pattern arraypatterns objectpatterns objectpattern %type term string stringparts suffix args ifelifs ifelse trycatch %type objectkeyvals objectkeyval objectval %type constterm constobject constobjectkeyvals constobjectkeyval constarray constarrayelems %type tokIdentVariable tokIdentModuleIdent tokVariableModuleVariable tokKeyword objectkey %token tokAltOp tokUpdateOp tokDestAltOp tokOrOp tokAndOp tokCompareOp %token tokModule tokImport tokInclude tokDef tokAs tokLabel tokBreak %token tokNull tokTrue tokFalse %token tokIdent tokVariable tokModuleIdent tokModuleVariable %token tokIndex tokNumber tokFormat %token tokString tokStringStart tokStringQuery tokStringEnd %token tokIf tokThen tokElif tokElse tokEnd %token tokTry tokCatch tokReduce tokForeach %token tokRecurse tokFuncDefPost tokTermPost tokEmptyCatch %token tokInvalid tokInvalidEscapeSequence tokUnterminatedString %nonassoc tokFuncDefPost tokTermPost %right '|' %left ',' %right tokAltOp %nonassoc tokUpdateOp %left tokOrOp %left tokAndOp %nonassoc tokCompareOp %left '+' '-' %left '*' '/' '%' %nonassoc tokAs tokIndex '.' '?' tokEmptyCatch %nonassoc '[' tokTry tokCatch %% program : moduleheader programbody { if $1 != nil { $2.(*Query).Meta = $1.(*ConstObject) } yylex.(*lexer).result = $2.(*Query) } moduleheader : { $$ = nil } | tokModule constobject ';' { $$ = $2; } programbody : imports funcdefs { $$ = &Query{Imports: $1.([]*Import), FuncDefs: reverseFuncDef($2.([]*FuncDef)), Term: &Term{Type: TermTypeIdentity}} } | imports query { if $1 != nil { $2.(*Query).Imports = $1.([]*Import) } $$ = $2 } imports : { $$ = []*Import(nil) } | imports import { $$ = append($1.([]*Import), $2.(*Import)) } import : tokImport tokString tokAs tokIdentVariable metaopt ';' { $$ = &Import{ImportPath: $2, ImportAlias: $4, Meta: $5.(*ConstObject)} } | tokInclude tokString metaopt ';' { $$ = &Import{IncludePath: $2, Meta: $3.(*ConstObject)} } metaopt : { $$ = (*ConstObject)(nil) } | constobject {} funcdefs : { $$ = []*FuncDef(nil) } | funcdef funcdefs { $$ = append($2.([]*FuncDef), $1.(*FuncDef)) } funcdef : tokDef tokIdent ':' query ';' { $$ = &FuncDef{Name: $2, Body: $4.(*Query)} } | tokDef tokIdent '(' funcdefargs ')' ':' query ';' { $$ = &FuncDef{$2, $4.([]string), $7.(*Query)} } funcdefargs : tokIdentVariable { $$ = []string{$1} } | funcdefargs ';' tokIdentVariable { $$ = append($1.([]string), $3) } tokIdentVariable : tokIdent {} | tokVariable {} query : funcdef query %prec tokFuncDefPost { $2.(*Query).FuncDefs = prependFuncDef($2.(*Query).FuncDefs, $1.(*FuncDef)) $$ = $2 } | query '|' query { $$ = &Query{Left: $1.(*Query), Op: OpPipe, Right: $3.(*Query)} } | term tokAs bindpatterns '|' query { $1.(*Term).SuffixList = append($1.(*Term).SuffixList, &Suffix{Bind: &Bind{$3.([]*Pattern), $5.(*Query)}}) $$ = &Query{Term: $1.(*Term)} } | tokReduce term tokAs pattern '(' query ';' query ')' { $$ = &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{$2.(*Term), $4.(*Pattern), $6.(*Query), $8.(*Query)}}} } | tokForeach term tokAs pattern '(' query ';' query ')' { $$ = &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{$2.(*Term), $4.(*Pattern), $6.(*Query), $8.(*Query), nil}}} } | tokForeach term tokAs pattern '(' query ';' query ';' query ')' { $$ = &Query{Term: &Term{Type: TermTypeForeach, Foreach: &Foreach{$2.(*Term), $4.(*Pattern), $6.(*Query), $8.(*Query), $10.(*Query)}}} } | tokIf query tokThen query ifelifs ifelse tokEnd { $$ = &Query{Term: &Term{Type: TermTypeIf, If: &If{$2.(*Query), $4.(*Query), $5.([]*IfElif), $6.(*Query)}}} } | tokTry query trycatch { $$ = &Query{Term: &Term{Type: TermTypeTry, Try: &Try{$2.(*Query), $3.(*Query)}}} } | tokLabel tokVariable '|' query { $$ = &Query{Term: &Term{Type: TermTypeLabel, Label: &Label{$2, $4.(*Query)}}} } | query '?' { if t := $1.(*Query).Term; t != nil { t.SuffixList = append(t.SuffixList, &Suffix{Optional: true}) } else { $$ = &Query{Term: &Term{Type: TermTypeQuery, Query: $1.(*Query), SuffixList: []*Suffix{{Optional: true}}}} } } | query ',' query { $$ = &Query{Left: $1.(*Query), Op: OpComma, Right: $3.(*Query)} } | query tokAltOp query { $$ = &Query{Left: $1.(*Query), Op: $2, Right: $3.(*Query)} } | query tokUpdateOp query { $$ = &Query{Left: $1.(*Query), Op: $2, Right: $3.(*Query)} } | query tokOrOp query { $$ = &Query{Left: $1.(*Query), Op: OpOr, Right: $3.(*Query)} } | query tokAndOp query { $$ = &Query{Left: $1.(*Query), Op: OpAnd, Right: $3.(*Query)} } | query tokCompareOp query { $$ = &Query{Left: $1.(*Query), Op: $2, Right: $3.(*Query)} } | query '+' query { $$ = &Query{Left: $1.(*Query), Op: OpAdd, Right: $3.(*Query)} } | query '-' query { $$ = &Query{Left: $1.(*Query), Op: OpSub, Right: $3.(*Query)} } | query '*' query { $$ = &Query{Left: $1.(*Query), Op: OpMul, Right: $3.(*Query)} } | query '/' query { $$ = &Query{Left: $1.(*Query), Op: OpDiv, Right: $3.(*Query)} } | query '%' query { $$ = &Query{Left: $1.(*Query), Op: OpMod, Right: $3.(*Query)} } | term %prec tokTermPost { $$ = &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 { if $2.(*Suffix).Iter { $$ = &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{$2.(*Suffix)}} } else { $$ = &Term{Type: TermTypeIndex, Index: $2.(*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}} } | tokNumber { $$ = &Term{Type: TermTypeNumber, Number: $1} } | tokFormat { $$ = &Term{Type: TermTypeFormat, Format: $1} } | tokFormat string { $$ = &Term{Type: TermTypeFormat, Format: $1, Str: $2.(*String)} } | string { $$ = &Term{Type: TermTypeString, Str: $1.(*String)} } | '(' query ')' { $$ = &Term{Type: TermTypeQuery, Query: $2.(*Query)} } | '+' term { $$ = &Term{Type: TermTypeUnary, Unary: &Unary{OpAdd, $2.(*Term)}} } | '-' term { $$ = &Term{Type: TermTypeUnary, Unary: &Unary{OpSub, $2.(*Term)}} } | '{' '}' { $$ = &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)}} } | tokBreak tokVariable { $$ = &Term{Type: TermTypeBreak, Break: $2} } | 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 query { $$ = $2 } objectkeyvals : objectkeyval { $$ = []*ObjectKeyVal{$1.(*ObjectKeyVal)} } | objectkeyvals ',' objectkeyval { $$ = append($1.([]*ObjectKeyVal), $3.(*ObjectKeyVal)) } objectkeyval : objectkey ':' objectval { $$ = &ObjectKeyVal{Key: $1, Val: $3.(*ObjectVal)} } | string ':' objectval { $$ = &ObjectKeyVal{KeyString: $1.(*String), Val: $3.(*ObjectVal)} } | '(' query ')' ':' objectval { $$ = &ObjectKeyVal{KeyQuery: $2.(*Query), Val: $5.(*ObjectVal)} } | objectkey { $$ = &ObjectKeyVal{Key: $1} } | string { $$ = &ObjectKeyVal{KeyString: $1.(*String)} } objectkey : tokIdent {} | tokVariable {} | tokKeyword {} objectval : term { $$ = &ObjectVal{[]*Query{{Term: $1.(*Term)}}} } | objectval '|' term { $$ = &ObjectVal{append($1.(*ObjectVal).Queries, &Query{Term: $3.(*Term)})} } 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 {} %% gojq-0.12.13/preview.go000066400000000000000000000033671443612442000146370ustar00rootroot00000000000000package 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, 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] } gojq-0.12.13/preview_test.go000066400000000000000000000041421443612442000156660ustar00rootroot00000000000000package gojq_test import ( "fmt" "math" "math/big" "testing" "github.com/itchyny/gojq" ) func TestPreview(t *testing.T) { testCases := []struct { value any expected string }{ { nil, "null", }, { false, "false", }, { true, "true", }, { 0, "0", }, { 3.14, "3.14", }, { math.NaN(), "null", }, { math.Inf(1), "1.7976931348623157e+308", }, { math.Inf(-1), "-1.7976931348623157e+308", }, { big.NewInt(9223372036854775807), "9223372036854775807", }, { new(big.Int).SetBytes([]byte("\x0c\x9f\x2c\x9c\xd0\x46\x74\xed\xea\x3f\xff\xff\xff")), "999999999999999999999999999999", }, { new(big.Int).SetBytes([]byte("\x0c\x9f\x2c\x9c\xd0\x46\x74\xed\xea\x40\x00\x00\x00")), "10000000000000000000000000 ...", }, { "0 1 2 3 4 5 6 7 8 9 10 11 12", `"0 1 2 3 4 5 6 7 8 9 10 11 12"`, }, { "0 1 2 3 4 5 6 7 8 9 10 11 12 13", `"0 1 2 3 4 5 6 7 8 9 10 1 ..."`, }, { "0123456789", `"01234567 ..."`, }, { []any{}, "[]", }, { []any{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, "[0,1,2,3,4,5,6,7,8,9,10,11,12]", }, { []any{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, "[0,1,2,3,4,5,6,7,8,9,10,1 ...]", }, { []any{[]any{[]any{[]any{[]any{[]any{[]any{[]any{nil, nil, nil}}}}}}}}, "[[[[[[[[null,null,null]]]]]]]]", }, { []any{[]any{[]any{[]any{[]any{[]any{[]any{[]any{nil, nil, nil, nil}}}}}}}}, "[[[[[[[[null,null,null,nu ...]", }, { map[string]any{}, "{}", }, { map[string]any{"0": map[string]any{"1": map[string]any{"2": map[string]any{"3": []any{nil}}}}}, `{"0":{"1":{"2":{"3":[null]}}}}`, }, { map[string]any{"0": map[string]any{"1": map[string]any{"2": map[string]any{"3": map[string]any{"4": map[string]any{}}}}}}, `{"0":{"1":{"2":{"3":{"4": ...}`, }, } for _, tc := range testCases { t.Run(fmt.Sprintf("%v", tc.value), func(t *testing.T) { got := gojq.Preview(tc.value) if got != tc.expected { t.Errorf("Preview(%v): got %s, expected %s", tc.value, got, tc.expected) } }) } } gojq-0.12.13/query.go000066400000000000000000000461611443612442000143220ustar00rootroot00000000000000package gojq import ( "context" "strings" ) // Query represents the abstract syntax tree of a jq query. type Query struct { Meta *ConstObject Imports []*Import FuncDefs []*FuncDef Term *Term Left *Query Op Operator Right *Query Func string } // Run the query. // // It is safe to call this method in goroutines, to reuse a parsed [*Query]. // But for arguments, do not give values sharing same data between goroutines. 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 i, fd := range e.FuncDefs { if i > 0 { s.WriteByte(' ') } fd.writeTo(s) } if len(e.FuncDefs) > 0 { s.WriteByte(' ') } if e.Func != "" { s.WriteString(e.Func) } else if e.Term != nil { e.Term.writeTo(s) } else if e.Right != nil { e.Left.writeTo(s) if e.Op == OpComma { s.WriteString(", ") } else { s.WriteByte(' ') s.WriteString(e.Op.String()) s.WriteByte(' ') } e.Right.writeTo(s) } } func (e *Query) minify() { for _, e := range e.FuncDefs { e.Minify() } if e.Term != nil { if name := e.Term.toFunc(); name != "" { e.Term = nil e.Func = name } else { e.Term.minify() } } else if e.Right != nil { e.Left.minify() e.Right.minify() } } 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(';') } // Minify ... func (e *FuncDef) Minify() { e.Body.minify() } // 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) minify() { switch e.Type { case TermTypeIndex: e.Index.minify() case TermTypeFunc: e.Func.minify() case TermTypeObject: e.Object.minify() case TermTypeArray: e.Array.minify() case TermTypeUnary: e.Unary.minify() case TermTypeFormat: if e.Str != nil { e.Str.minify() } case TermTypeString: e.Str.minify() case TermTypeIf: e.If.minify() case TermTypeTry: e.Try.minify() case TermTypeReduce: e.Reduce.minify() case TermTypeForeach: e.Foreach.minify() case TermTypeLabel: e.Label.minify() case TermTypeQuery: e.Query.minify() } for _, e := range e.SuffixList { e.minify() } } func (e *Term) toFunc() string { if len(e.SuffixList) != 0 { return "" } // ref: compiler#compileQuery switch e.Type { case TermTypeIdentity: return "." case TermTypeRecurse: return ".." case TermTypeNull: return "null" case TermTypeTrue: return "true" case TermTypeFalse: return "false" case TermTypeFunc: return e.Func.toFunc() default: return "" } } 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) minify() { e.Term.minify() } 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) minify() { if e.Str != nil { e.Str.minify() } if e.Start != nil { e.Start.minify() } if e.End != nil { e.End.minify() } } 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(')') } } func (e *Func) minify() { for _, x := range e.Args { x.minify() } } func (e *Func) toFunc() string { if len(e.Args) != 0 { return "" } return e.Name } // 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('"') } func (e *String) minify() { for _, e := range e.Queries { e.minify() } } // 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(" }") } func (e *Object) minify() { for _, e := range e.KeyVals { e.minify() } } // ObjectKeyVal ... type ObjectKeyVal struct { Key string KeyString *String KeyQuery *Query Val *ObjectVal } 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) } } func (e *ObjectKeyVal) minify() { if e.KeyString != nil { e.KeyString.minify() } else if e.KeyQuery != nil { e.KeyQuery.minify() } if e.Val != nil { e.Val.minify() } } // ObjectVal ... type ObjectVal struct { Queries []*Query } func (e *ObjectVal) String() string { var s strings.Builder e.writeTo(&s) return s.String() } func (e *ObjectVal) writeTo(s *strings.Builder) { for i, e := range e.Queries { if i > 0 { s.WriteString(" | ") } e.writeTo(s) } } func (e *ObjectVal) minify() { for _, e := range e.Queries { e.minify() } } // 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(']') } func (e *Array) minify() { if e.Query != nil { e.Query.minify() } } // Suffix ... type Suffix struct { Index *Index Iter bool Optional bool Bind *Bind } 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('?') } else if e.Bind != nil { e.Bind.writeTo(s) } } func (e *Suffix) minify() { if e.Index != nil { e.Index.minify() } else if e.Bind != nil { e.Bind.minify() } } 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) } // Bind ... type Bind struct { Patterns []*Pattern Body *Query } func (e *Bind) String() string { var s strings.Builder e.writeTo(&s) return s.String() } func (e *Bind) writeTo(s *strings.Builder) { for i, p := range e.Patterns { if i == 0 { s.WriteString(" as ") p.writeTo(s) s.WriteByte(' ') } else { s.WriteString("?// ") p.writeTo(s) s.WriteByte(' ') } } s.WriteString("| ") e.Body.writeTo(s) } func (e *Bind) minify() { e.Body.minify() } // 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") } func (e *If) minify() { e.Cond.minify() e.Then.minify() for _, x := range e.Elif { x.minify() } if e.Else != nil { e.Else.minify() } } // 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) } func (e *IfElif) minify() { e.Cond.minify() e.Then.minify() } // 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) } } func (e *Try) minify() { e.Body.minify() if e.Catch != nil { e.Catch.minify() } } // Reduce ... type Reduce struct { Term *Term 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.Term.writeTo(s) s.WriteString(" as ") e.Pattern.writeTo(s) s.WriteString(" (") e.Start.writeTo(s) s.WriteString("; ") e.Update.writeTo(s) s.WriteByte(')') } func (e *Reduce) minify() { e.Term.minify() e.Start.minify() e.Update.minify() } // Foreach ... type Foreach struct { Term *Term 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.Term.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(')') } func (e *Foreach) minify() { e.Term.minify() e.Start.minify() e.Update.minify() if e.Extract != nil { e.Extract.minify() } } // 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) } func (e *Label) minify() { e.Body.minify() } // 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 } } // 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 { s.WriteString(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 } gojq-0.12.13/query_test.go000066400000000000000000000171031443612442000153530ustar00rootroot00000000000000package gojq_test import ( "context" "encoding/json" "fmt" "log" "math" "math/big" "os" "reflect" "strconv" "strings" "sync" "testing" "time" "github.com/itchyny/gojq" ) func ExampleQuery_Run() { query, err := gojq.Parse(".foo | ..") if err != nil { log.Fatalln(err) } input := map[string]any{"foo": []any{1, 2, 3}} iter := query.Run(input) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { log.Fatalln(err) } fmt.Printf("%#v\n", v) } // Output: // []interface {}{1, 2, 3} // 1 // 2 // 3 } func ExampleQuery_RunWithContext() { query, err := gojq.Parse("def f: f; f, f") if err != nil { log.Fatalln(err) } ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() iter := query.RunWithContext(ctx, nil) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { fmt.Println(err) continue } _ = v } // Output: // context deadline exceeded } func TestQueryRun_Errors(t *testing.T) { query, err := gojq.Parse(".[] | error") if err != nil { t.Fatal(err) } iter := query.Run([]any{0, 1, 2, 3, 4}) n := 0 for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { if expected := "error: " + strconv.Itoa(n); err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } n++ } if expected := 5; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } } func TestQueryRun_ObjectError(t *testing.T) { query, err := gojq.Parse(".[] | {(.): 1}") if err != nil { t.Fatal(err) } iter := query.Run([]any{0, "x", []any{}}) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { expected := "expected a string for object key but got" if !strings.Contains(err.Error(), expected) { t.Errorf("expected: %v, got: %v", expected, err) } } else if expected := map[string]any{"x": 1}; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestQueryRun_IndexError(t *testing.T) { query, err := gojq.Parse(".foo") if err != nil { t.Fatal(err) } iter := query.Run([]any{0}) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { expected := "expected an object but got: array ([0])" if !strings.Contains(err.Error(), expected) { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } } } func TestQueryRun_InvalidPathError(t *testing.T) { query, err := gojq.Parse(". + 1, path(. + 1)") if err != nil { t.Fatal(err) } iter := query.Run(0) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { if expected := "invalid path against: number (1)"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else if expected := 1; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestQueryRun_IteratorError(t *testing.T) { query, err := gojq.Parse(".[]") if err != nil { t.Fatal(err) } iter := query.Run(nil) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { if expected := "cannot iterate over: null"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } } } func TestQueryRun_Strings(t *testing.T) { query, err := gojq.Parse( "[\"\x00\\\\\", \"\x1f\\\"\", \"\n\\n\n\\(\"\\n\")\n\\n\", " + "\"\\/\", \"\x7f\", \"\x80\", \"\\ud83d\\ude04\" | explode[]]", ) if err != nil { t.Fatal(err) } iter := query.Run(nil) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { t.Fatal(err) } if expected := []any{ 0x00, int('\\'), 0x1f, int('"'), int('\n'), int('\n'), int('\n'), int('\n'), int('\n'), int('\n'), int('/'), 0x7f, 0xfffd, 128516, }; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestQueryRun_NumericTypes(t *testing.T) { query, err := gojq.Parse(".[] + 0 != 0") if err != nil { t.Fatal(err) } iter := query.Run([]any{ int64(1), int32(1), int16(1), int8(1), uint64(1), uint32(1), uint16(1), uint8(1), uint(math.MaxUint), int64(math.MaxInt64), int64(math.MinInt64), uint64(math.MaxUint64), uint32(math.MaxUint32), new(big.Int).SetUint64(math.MaxUint64), new(big.Int).SetUint64(math.MaxUint32), json.Number(fmt.Sprint(uint64(math.MaxInt64))), json.Number(fmt.Sprint(uint64(math.MaxInt32))), float64(1.0), float32(1.0), }) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { t.Fatal(err) } if expected := true; expected != v { t.Errorf("expected: %v, got: %v", expected, v) } } } func TestQueryRun_Input(t *testing.T) { query, err := gojq.Parse("input") if err != nil { t.Fatal(err) } iter := query.Run(nil) v, ok := iter.Next() if !ok { t.Fatal("should emit an error but got no output") } if err, ok := v.(error); ok { if expected := "input(s)/0 is not allowed"; err.Error() != expected { t.Errorf("expected: %v, got: %v", expected, err) } } else { t.Errorf("should emit an error but got: %v", v) } } func TestQueryRun_NonNilSlice(t *testing.T) { for _, f := range []string{"keys", "map(.)", "to_entries", "arrays", "reverse", "flatten", "sort", "sort_by(.)", "group_by(.)", "unique", "unique_by(.)", "transpose", "nth(.)", "indices([])", "path(.)"} { t.Run(f, func(t *testing.T) { query, err := gojq.Parse("[] | " + f) if err != nil { t.Fatal(err) } iter := query.Run(nil) for { v, ok := iter.Next() if !ok { break } if err, ok := v.(error); ok { t.Fatal(err) } if expected := []any{}; !reflect.DeepEqual(v, expected) { t.Errorf("expected: %#v, got: %#v", expected, v) } } }) } } func TestQueryRun_Race(t *testing.T) { query, err := gojq.Parse("range(10)") if err != nil { t.Fatal(err) } var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() iter := query.Run(nil) n := 0 for { got, ok := iter.Next() if !ok { break } if got != n { t.Errorf("expected: %v, got: %v", n, got) } n++ } if expected := 10; n != expected { t.Errorf("expected: %v, got: %v", expected, n) } }() } wg.Wait() } func TestQueryString(t *testing.T) { cnt, err := os.ReadFile("builtin.jq") if err != nil { t.Fatal(err) } q, err := gojq.Parse(string(cnt)) if err != nil { t.Fatal(err) } r, err := gojq.Parse(q.String()) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(q, r) { t.Errorf("\n%v\n%v", q, r) } } func BenchmarkRun(b *testing.B) { query, err := gojq.Parse("range(1000)") if err != nil { b.Fatal(err) } for i := 0; i < b.N; i++ { iter := query.Run(nil) for { _, ok := iter.Next() if !ok { break } } } } func BenchmarkParse(b *testing.B) { cnt, err := os.ReadFile("builtin.jq") if err != nil { b.Fatal(err) } src := string(cnt) for i := 0; i < b.N; i++ { _, err := gojq.Parse(src) if err != nil { b.Fatal(err) } } } func FuzzQueryRun(f *testing.F) { f.Fuzz(func(t *testing.T, src string) { if len(src) > 16 { t.SkipNow() } q, err := gojq.Parse(src) if err != nil { t.SkipNow() } code, err := gojq.Compile(q) if err != nil { t.SkipNow() } ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) t.Cleanup(cancel) iter := code.RunWithContext(ctx, nil) for { if _, ok := iter.Next(); !ok { break } } }) } gojq-0.12.13/release.go000066400000000000000000000004431443612442000145660ustar00rootroot00000000000000//go:build !gojq_debug // +build !gojq_debug package gojq type codeinfo struct{} func (c *compiler) appendCodeInfo(any) {} func (c *compiler) deleteCodeInfo(string) {} func (env *env) debugCodes() {} func (env *env) debugState(int, bool) {} func (env *env) debugForks(int, string) {} gojq-0.12.13/scope_stack.go000066400000000000000000000014551443612442000154500ustar00rootroot00000000000000package 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} i := s.index + 1 if i <= s.limit { i = s.limit + 1 } s.index = i if i < len(s.data) { s.data[i] = 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 } gojq-0.12.13/stack.go000066400000000000000000000014501443612442000142520ustar00rootroot00000000000000package 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} i := s.index + 1 if i <= s.limit { i = s.limit + 1 } s.index = i if i < len(s.data) { s.data[i] = 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 } gojq-0.12.13/term_type.go000066400000000000000000000030051443612442000151530ustar00rootroot00000000000000package 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) } } gojq-0.12.13/type.go000066400000000000000000000011051443612442000141230ustar00rootroot00000000000000package gojq import ( "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, 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: 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)) } } gojq-0.12.13/type_test.go000066400000000000000000000017001443612442000151630ustar00rootroot00000000000000package gojq_test import ( "fmt" "math" "math/big" "testing" "github.com/itchyny/gojq" ) func TestTypeOf(t *testing.T) { testCases := []struct { value any expected string }{ {nil, "null"}, {false, "boolean"}, {true, "boolean"}, {0, "number"}, {3.14, "number"}, {math.NaN(), "number"}, {math.Inf(1), "number"}, {math.Inf(-1), "number"}, {big.NewInt(10), "number"}, {"string", "string"}, {[]any{}, "array"}, {map[string]any{}, "object"}, } for _, tc := range testCases { t.Run(fmt.Sprintf("%v", tc.value), func(t *testing.T) { got := gojq.TypeOf(tc.value) if got != tc.expected { t.Errorf("TypeOf(%v): got %s, expected %s", tc.value, got, tc.expected) } }) } func() { v := []int{0} defer func() { if got, expected := recover(), "invalid type: []int ([0])"; got != expected { t.Errorf("TypeOf(%v) should panic: got %v, expected %v", v, got, expected) } }() _ = gojq.TypeOf(v) }() }