# jq.php — jq implemented in PHP 5.2.0

A single-file, dependency-free implementation of a large subset of the
[jq](https://jqlang.org/) query language, written in PHP using only
syntax and functions available in **PHP 5.2.0** (released Nov 2006).

## Why PHP 5.2.0 is a real constraint

PHP 5.2.0 predates several features most modern PHP relies on:

| Feature                          | Added in | Used here? |
|-----------------------------------|----------|------------|
| Closures / anonymous functions    | 5.3      | No |
| Short array syntax `[]`           | 5.4      | No (`array()` everywhere) |
| Ternary shorthand `?:`            | 5.3      | No |
| Null coalescing `??`              | 7.0      | No |
| Namespaces / traits               | 5.3      | No |
| `JSON_PRETTY_PRINT`               | 5.4      | No (hand-written pretty printer) |
| `JSON_UNESCAPED_SLASHES/UNICODE`  | 5.4      | No (hand-written string encoder) |

`json_encode`/`json_decode` themselves *are* available (bundled as of
PHP 5.2.0), so they're used for parsing input, but **not** for producing
output, since their default escaping (`\/` for slashes, `\uXXXX` for all
non-ASCII) doesn't match jq's actual output and the flags to fix that
don't exist yet in 5.2.

## Usage

```
php jq.php [options] 'filter' [file.json]
cat file.json | php jq.php [options] 'filter'
```

Options:
- `-r`, `--raw-output` — print strings without surrounding quotes
- `-c`, `--compact-output` — single-line JSON output
- `-n`, `--null-input` — don't read stdin/file; input is `null`
- `-s`, `--slurp` — collect all inputs into one array, run filter once

Examples:
```
echo '{"a":1,"b":2}' | php jq.php '.a'
echo '[1,2,3]' | php jq.php -c 'map(. * 2)'
php jq.php -c '.users[] | select(.age > 26) | .name' data.json
php jq.php -n -c '[range(5)]'
```

## Design

- **Values**: `null`→PHP `null`, booleans→PHP bool, numbers→PHP
  int/float, strings→PHP string, JSON arrays→PHP indexed arrays, JSON
  objects→PHP `stdClass` (not associative arrays — this is what lets
  empty `{}` and empty `[]` stay distinguishable, since `json_decode()`
  without the `assoc` flag already returns objects this way).
- **Pipeline**: hand-written lexer → recursive-descent parser → AST →
  tree-walking evaluator. Because every jq filter can produce zero, one,
  or many outputs for a single input, `jq_eval($node, $input, $env)`
  always returns a PHP array of results (there's no generator support
  in PHP 5.2, so multiple outputs are simply collected eagerly).

## Supported language

`.` `..` `.foo` `.foo.bar` `."odd key"` `.foo?` `.[]` `.[expr]` `.[a:b]`
(all with optional `?`), `|`, `,`, `(...)`, `[...]` (array construction),
`{...}` (object construction incl. `{foo}`, `{(expr): v}`, `{$x}`
shorthand), string interpolation `"\(expr)"`, `$var` / `expr as $var |
body`, arithmetic `+ - * / %`, comparisons `== != < <= > >=`, `and or
not`, `//` (alternative), `if/then/elif/else/end`, `try/catch` and
trailing `?`, `reduce ... as $x (init; update)`, `foreach ... as $x
(init; update; extract)`.

Builtins: `length utf8bytelength keys keys_unsorted has in add any all
flatten min max min_by max_by sort sort_by group_by unique unique_by
reverse type arrays/objects/booleans/numbers/strings/nulls/
iterables/scalars tostring tonumber tojson fromjson ascii_downcase
ascii_upcase explode implode split join ltrimstr rtrimstr startswith
endswith contains inside select map map_values recurse recurse(f) paths
paths(f) leaf_paths getpath first last nth range floor ceil round sqrt
pow fabs log/log2/log10/exp/exp2/exp10 to_entries from_entries
with_entries walk limit until while test(basic regex) isnan
isinfinite empty error not`.

## Known limitations (out of scope)

These require a path-tracking assignment engine, a real regex engine
binding, or a streaming input model, and were left out to keep this to
a single dependency-free file:

- Update/assignment operators: `=`, `|=`, `+=`, `-=`, `*=`, `/=`,
  `//=`, `del(...)`, `path(...)`, `setpath(...)`, `delpaths(...)`
- Regex builtins beyond basic `test/1`: `match`, `capture`, `sub`,
  `gsub`, `scan`, `splits`
- `@base64`, `@csv`, `@tsv`, `@html`, `@sh`, `@json` format strings
- `input`, `inputs`, `$__prog_name__`, `env`/`$ENV`, `now`
- `label`/`break`, `def` (user-defined functions), modules
  (`import`/`include`)
- Destructuring patterns beyond a single `$var` in `as` bindings
- `repeat(f)` (would need lazy evaluation to be safe)

`try EXPR catch EXPR` binds at the same (fairly tight, "postfix")
precedence as trailing `?` — wrap either side in parentheses if you
need a wider expression, e.g. `try (1/.) catch ("err: " + .)`.
