This is a learning project, with the following high-level goals:
You need to have NodeJS installed (version 16 or above).
npm install
npm run spago-install
npm run build-watch
npm run dev
There are two phases: compiling and interpreting.
The compile phase takes jq source code and turns it into an abstract representation (Expression
).
The interpreter step takes the Expression, along with input json and returns json.
.. compile ('.foo') -> Select "foo"
String -> Either ParseError AST
.. interpret {"foo": 32} -> (Select "foo") -> 32
JSON -> Expression -> Array JSON
f(x) | g(f(x)) | h(g(f(x)))
, whereas if it was
right-associative it would be like h(g(f(x))) | f(x) | g(f(x))
[1,2,3][]
has 3 separate
outputs: 1
, 2
adn 3
, which is different from the array we start with ([1,2,3]
). I am sure there are good reasons
for this, but I don't know what those are yet.
Maybe the answer is that this allows iteration, so that if you wanted to map the items in the example above to increment
by one, then you could do [ [1,2,3][] | .+1 ]
When I started adding support for Pipe
(|
), I ran into the left recursion problem, which took me a while to even
realise what the problem was. This page describes the issue really well
and helped me climb out of that hole!
The page above mentions this page, which is a mathematical explanation of how the problem is solved. I'd love to be able to understand the math version.
I ran into this when looking up the associativity of the ,
and |
operators. Intuitively, the |
operator seems to be
left associative (see my thinking in the questions section), however the language specification says it's right associative
(see here).
It turns out that the pipe operator will give the same result, regardless of how it's associated. This property is called associative property and described in this wiki like so:
Within an expression containing two or more occurrences in a row of the same associative operator, the order in which the operations are performed does not matter as long as the sequence of the operands is not changed.
Addition is an example of this property, where it doesn't matter how you parenthesise, it will always yield the same result.
Adding a second infix operator (,
), raised the issue of operator precedence.
This is fully described in this issue including how
I decided to implement a Pratt Parser to solve the problem.
I had no idea how 'big' a language JQ could be, including functions, variables and modules.
I would love to see real-world examples where all these features are used for some very complex transformation, but I personally feel like perhaps some of the more esoteric features are born from wanting to stretch the language rather than a need coming from its users; this is just a hunch and I'd love to be wrong and see examples.