Compare commits

...
522 Commits
Author SHA1 Message Date
Nicolas Williams e73951f3d1 Add lib.h to dist file list 2014-06-09 18:58:46 -05:00
Nicolas Williams fdf843c08b Make the note about shell quoting appear on site 2014-06-09 17:43:17 -05:00
Nicolas Williams cf145ec65e Add note about cmd.exe quoting 2014-06-09 10:22:55 -05:00
Nicolas Williams 4889844d0c Building docs fails on powerpc (#349)
Thanks @shym!!
2014-06-09 09:39:02 -05:00
Nicolas Williams dc7857c585 Update site news 2014-06-08 20:45:06 -05:00
Nicolas Williams d65dbf89c9 Also fix configure.ac to use git describe --tags 2014-06-08 19:36:06 -05:00
Nicolas Williams ed2b3e26fb Fix scripts/version: use git describe --tags ...
After tagging as 1.4 scripts/version was still producing jq-1.3-....
2014-06-08 19:31:21 -05:00
Nicolas Williams 46436bd870 Add test of indices with short input 2014-06-08 02:27:27 -05:00
Nicolas Williams 1dbe9317bc Add indices(s), improve index(s), rindex(s)
Now these deal with arrays as input and `s` being an array or a scalar.
2014-06-08 02:01:44 -05:00
Nicolas Williams ef4f3a54fe Improve index and rindex examples 2014-06-08 02:01:21 -05:00
Nicolas Williams 9af03c2562 Remove reference to getpath from docs 2014-06-08 01:35:03 -05:00
Nicolas Williams c10e5f4e73 Document index and rindex (#389) 2014-06-08 00:00:17 -05:00
Santiago Lapresta 01f676041d Added join function
Signed-off-by: Nicolas Williams <nico@cryptonector.com>
2014-06-07 21:53:03 -05:00
Nicolas Williams 6856ab8a69 String * number should be commutative 2014-06-07 21:43:30 -05:00
Nicolas Williams 8212750fe9 Add cross-compilation notes to README
A detailed set of instruction as to how to setup a cross-compilation
environment for OS X and Win32/64 would be nice.
2014-06-04 18:36:30 -05:00
Nicolas Williams 35b6b949e3 Add -j option to scripts/crosscompile 2014-06-04 18:36:30 -05:00
Nicolas Williams d1ea3ab89d Add flags argument to jv_parser_new()
For extensibility.  We might add streaming parser options, even binary
JSON encoding options.
2014-06-04 18:35:30 -05:00
Nicolas Williams ae625d0de7 Revert "Add -I / --online-input for huge top-level arrays"
This reverts commit 77936a594d.

There are too many odd bugs in this mode, and it turns out to be a bad
idea anyways.  Instead, in the future a better option will be to pursue
alternative parsers, such as:

 - streaming parser that outputs only when a new leaf value is added or
   an array/object is opened/closed; options here include whether to
   include a path in each output;

 - parsers for binary JSON encodings (there's a variety of them).

Then one might run jq with a streaming parser and use `reduce` to
coalesce inputs from some depth down (instead of from one level down as
the reverted commit had intended).

Besides, a fully streaming parser is desirable in some cases, therefore
we should have such a thing as an option.

I've explored modifying the current parser to support a streaming
option, but it only makes the code very difficult to follow, which is
one reason that alternate parsers makes sense.  At any rate, this is all
for the future.  For now there's no streaming of individual texts, just
text sequences.
2014-06-04 18:15:58 -05:00
Nicolas Williams 5fb82d1018 Fix tests failures on Windows
And Solaris 8 and 9 too, no doubt.  The problem was that non-standard
vsnprintf()s that return -1 when the buffer is too small were not
properly supported.
2014-06-02 00:56:01 -05:00
Nicolas Williams f9d1b94496 Update .gitignore and config/.gitignore 2014-06-01 22:32:53 -05:00
Nico Williams 854ff5643a Merge pull request #367 from slapresta/del-docs
Documented `del` command
2014-05-20 22:11:01 -05:00
Santiago Lapresta 072ebb911b Documented del command 2014-05-21 01:23:28 +02:00
Nico Williams d46f5d29fa Merge pull request #360 from slapresta/275-unique-by
unique_by(.foo) function
2014-05-12 00:30:55 -05:00
Santiago Lapresta 69aa0003af Added texts/examples to unique_by function 2014-05-12 01:44:53 +02:00
Santiago Lapresta 41a093c1f2 Added unique_by function 2014-05-12 01:37:27 +02:00
Nicolas Williams 022a418894 Make pthread tls configurable for Mingw build
For the Mingw build we don't want to pull in the pthread DLL just
because we can autodetect pthread support.  That would make the jq.exe
binary not self-contained.
2014-04-17 20:17:06 -05:00
Nicolas Williams d056668f4f Add autoconf checks for pthreads; fix #340 2014-04-16 18:42:36 -05:00
Nico Williams 917334d18a Merge pull request #332 from jingweno/master
Add link to jq playground
2014-03-20 23:47:40 -05:00
Jingwen Owen Ou 7a83f1c416 Add link to jqplay 2014-03-20 15:49:59 -07:00
Nico Williams 6f0a9521e1 Merge pull request #176 from pearkes/new-tutorial
Docs: Update the tutorial to use GitHub's API
2014-03-13 00:57:54 -05:00
Nicolas Williams aee5c3ecb0 Fix for #303 in the sources 2014-03-13 00:57:33 -05:00
Nico Williams da0d575649 Merge pull request #278 from polyester/master
work with newer versions of automake
2014-03-13 00:52:58 -05:00
Nicolas Williams 74d2216da2 Test arrays and friend filters 2014-03-13 00:29:53 -05:00
Nicolas Williams 769314a8a2 Document scalars 2014-03-13 00:29:53 -05:00
Nicolas Williams 831be04027 Also add scalars 2014-03-13 00:29:53 -05:00
Nicolas Williams 1a1adf73e8 Document arrays, objects, ... 2014-03-13 00:29:53 -05:00
Santiago Lapresta 68f41097b9 Added arrays and other filters
Arrays, objects, numbers, strings, booleans, nulls, values (non-nulls)
-- these builtins filter out those inputs that don't match the name of
the builtin.

This fixes #322 and #324.
2014-03-13 00:27:56 -05:00
Nicolas Williams 4402c6d806 One more recursive merge test 2014-03-12 23:44:21 -05:00
Filippo Valsorda 2aa8a43c5b Add a recursive object merge strategy and bind it to *
This commit adds a jv_object_merge_recursive function, that performs
recursive object merging, and binds it to multiply when applied to
two objects.

Added docs and tests.

Closes #320
2014-03-08 03:56:05 +01:00
Nicolas Williams 219bb9ecb7 Make libm tests more portable 2014-03-06 21:38:10 -06:00
Nico Williams 88fd2ee4f2 Merge pull request #310 from arodland/master
Make jq --raw-output --unbuffered work
2014-03-03 13:46:38 -06:00
Andrew Rodland 6c3683dfe9 Repair jv_show 2014-02-26 01:43:00 -06:00
Andrew Rodland 36e495da1e Make jq --raw-output --unbuffered work
--unbuffered was only affecting the normal output case, not the --raw-output case. Make the two of them play together.

This also makes sure that the output is flushed *after* printing the newline, so a consumer doesn't lag a line behind.
2014-02-26 01:42:29 -06:00
Nicolas Williams 15faf9dc3b Add cbrt (cube root) 2014-02-21 10:51:16 -06:00
Nicolas Williams 59302fd208 Add missing trig functions and barebones test 2014-02-21 10:49:21 -06:00
Nicolas Williams 24202d1ecf Remove non-standard exp10() 2014-02-21 10:49:10 -06:00
Mike McCabe 336f4e27e6 Initial add of math functions. 2014-02-21 08:46:56 +00:00
Nicolas Williams fe29d3d3fa Add ?, .[]?, and .. operators
Make XPath-like `//a/b` recursive structure traversal easier in jq,
which then becomes:

    ..|.a?.b?

The `?` operator suppresses errors about . not being an array or object.
The `..` operator is equivalent to calling the new `recurse_down`
built-in, which in turn is equivalent to

    recurse(.[]?)

Note that `..a` is not supported; neither is `...a`.  That could be add
added, but it doesn't seem worth the trouble of saving the need to type
a '|'.
2014-02-20 15:33:07 -06:00
Nico Williams 3d33412e9a Merge pull request #300 from lcd047/master
Fix building of the manual
2014-02-19 11:21:04 -06:00
LCD 47 fb9196021e manual.yml: fix indent for the new any and all commands. 2014-02-19 15:10:32 +02:00
Nico Williams b23fae9ee3 Merge pull request #296 from slapresta/all-any
Added `all` and `any` builtins
2014-02-16 22:45:49 -06:00
Santiago Lapresta 8b41415b07 Added all and any builtins 2014-02-17 04:01:32 +01:00
Nicolas Williams 1fa55a3fae Fix cut-n-paste in leaf_paths; doc and test 'em 2014-02-05 23:14:05 -06:00
Nicolas Williams a45c937f80 #285 add and builtins 2014-02-05 15:23:32 -06:00
polyester 6ed3adb091 work with newer versions of automake
when using a newer automake, the autoreconf step fails with warnings:
"linking libtool libraries using a non-POSIX archiver requires 'AM_PROG_AR' in 'configure.ac' "

This happens for instance on ubuntu 13.10. 
Doing just that, adding 'AM_PROG_AR' to configure.ac fixes the problem.
2014-01-25 18:55:58 +01:00
Nicolas Williams 106fdf5114 Fix #201; check that bison accepts --warnings 2014-01-01 23:10:00 -06:00
Nicolas Williams 99f170adc3 Fix version for make dist on master
- Re-remove VERSION file and all traces of it
 - Pass a decent version indicator to AC_INIT (using git describe)

When we tag 1.4 this will produce 1.4 as the version string when
building on the HEAD of that tag.
2013-12-31 17:19:40 -06:00
Joe Littlejohn f60d83c15f Fix rpm build (make rpm)
* Re-add VERSION as it's required for `./setup superclean`
  and `make rpm`.
* Add *.rpm to git ignore, we never want them under version control.
2013-12-27 13:01:14 -06:00
Filippo Giunchedi 36a47bae56 include additional files in jq.spec
this will probably need changing upon SONAME bump
2013-12-27 12:49:15 -06:00
Filippo Giunchedi dc8528af0f fix rpm Makefile target and prerequisites
depend on dist and the specfile, plus use automake's variables
2013-12-27 12:49:15 -06:00
Nicolas Williams e12d0ffd1e Document --version 2013-12-26 18:48:51 -06:00
Nicolas Williams a71138a43a Add jv_dumpf() and jv_show()
jv_dumpf() takes a FILE *.

jv_show() is intended for use in debuggers, so it dumps the jv to stderr
and it does not jv_free() the jv, so it's safe to
"call jv_show(some_jv, -1)" in a debugger.  If flags == -1 then the jv
will be shown pretty-printed and in color.
2013-12-26 18:40:34 -06:00
Nicolas Williams cb976b9a50 Document .foo.bar in manual 2013-12-26 18:25:31 -06:00
Nicolas Williams 060eb39bf9 Document exit numbers 2013-12-26 12:54:07 -06:00
Nicolas Williams ab363e9e50 Normalize errors for -e 2013-12-26 12:54:03 -06:00
Nicolas Williams 41a8ae7557 Fix doc typos (.[foo] wanted to be .["foo"]) 2013-12-25 17:09:27 -06:00
Nicolas Williams 1956abbedf Add note to jq.1 about shell quoting 2013-12-25 17:05:32 -06:00
Stephen Dolan 2003a0440f Merge pull request #251 from phihag/fix-uri-in-manual
Fix @uri example
2013-12-21 11:47:24 -08:00
Stephen Dolan a0274cc46e Merge pull request #252 from phihag/ignore-config-test-driver
Ignore the config/test-driver file
2013-12-21 11:46:53 -08:00
Philipp Hagemeister 3b384740b9 Ignore the config/test-driver file
This file is automatically generated and does not need to be committed.
2013-12-20 11:36:52 +01:00
Philipp Hagemeister 4d40a47eab Fix @uri example
Previously, the @uri example didn't match the actual behavior of the current jq, as exclamation marks do not need to be encoded in URIs.
Replace the example with an input that needs encoding, and is encoded by jq.
2013-12-20 11:32:53 +01:00
Stephen Dolan 18e828f17c Allow negated object values without parens. Fixes #247 2013-12-17 22:28:31 +00:00
Nicolas Williams 0966230ba8 Fix memmem() error 2013-12-17 08:40:03 -06:00
Stephen Dolan 669f4ab4c3 Merge pull request #242 from sieben/travis-ci
Adding a .travis.yml file to use the travis-ci.org
2013-12-15 12:54:17 -08:00
Rémy Léone 3455714410 Adding a .travis.yml file to use the travis-ci.org
From wikipedia:

Travis CI is a hosted, distributed continuous integration service used
to build and test projects hosted at GitHub.

Travis CI is configured by adding a file named .travis.yml, which is a
YAML format text file, to the root directory of the GitHub repository.

Travis CI automatically detects when a commit has been made and pushed
to a GitHub repository that is using Travis CI, and each time this
happens, it will try to build the project and run tests. This includes
commits to all branches, not just to the master branch. When that
process has completed, it will notify a developer in the way it has been
configured to do so — for example, by sending an email containing the
test results (showing success or failure), or by posting a message on an
IRC channel. It can be configured to run the tests on a range of
different machines, with different software installed (such as older
versions of a programming language, to test for compatibility).
2013-12-13 17:14:37 +01:00
Stephen Dolan 32ea50d336 Make the testsuite run on machines without valgrind 2013-12-13 15:22:17 +00:00
Stephen Dolan 5ac6a460fa Format more integers as integers, not scientific notation.
jq is now willing to put up to 15 zeros after an integer before
moving to scientific notation.
2013-12-13 15:22:17 +00:00
Stephen Dolan f147b7fae5 Merge pull request #239 from DRMacIver/overflows
Avoid undefined behaviour with large array indices
2013-12-12 16:38:14 -08:00
Nicolas Williams 474386a1d9 Complete more-arity feature not complete
And test
2013-12-11 14:14:26 -06:00
Stephen Dolan 7fd2c2d08b Merge pull request #235 from DRMacIver/prototypes
some functions were missing proper prototypes
2013-12-10 03:56:02 -08:00
David R. MacIver 5412f3f1c1 convert range bounds to integers in a way that avoids undefined behaviour 2013-12-10 09:20:13 +00:00
David R. MacIver 014b45b4a9 add checking of numeric indices to an array to see if they can reasonably be considered integers. Avoid undefined behaviour if out of bounds 2013-12-10 09:20:12 +00:00
David R. MacIver 30ea390b77 some functions were missing prototypes. Add them 2013-12-10 00:07:08 +00:00
Stephen Dolan 00f244385b Merge pull request #232 from DRMacIver/teardown_on_error
consistent use of goto out in main
2013-12-08 16:41:16 -08:00
David R. MacIver 7b671b95d2 don't need to check for NULL there 2013-12-08 23:52:07 +00:00
Stephen Dolan 1535f23455 Merge pull request #233 from DRMacIver/printf
Fix warnings about vprintf
2013-12-08 15:44:33 -08:00
Stephen Dolan 4ba0c8baef Merge pull request #231 from DRMacIver/fix_mem_leak_in_compile
args to jq_compile_args were not getting freed when there were errors in the compile
2013-12-08 15:17:20 -08:00
Stephen Dolan a7dd3ab793 Clean up string/object interactions in jv. 2013-12-08 22:49:28 +00:00
David R. MacIver f6f7d4d973 These vfprintfs are being used as if they were printfs. Fix that 2013-12-08 20:08:04 +00:00
David R. MacIver f4ae5c883b consistent use of goto out in main 2013-12-08 19:22:18 +00:00
Stephen Dolan 74a14f5de7 Refactor jv structure.
New structure layout is simpler and also faster.  In particular, it's
now small enough to be passed in registers on amd64.
2013-12-08 17:46:23 +00:00
Stephen Dolan fee21c959a Make testsuite not leak when compiled with -DNDEBUG. 2013-12-08 17:44:54 +00:00
David R. MacIver 4b48054f41 test for losing memory on compile errors 2013-12-08 16:19:12 +00:00
David R. MacIver 17a319d120 args to jq_compile_args were not getting freed when there were errors in the compile 2013-12-08 15:28:29 +00:00
Nicolas Williams 3a1dab5396 Fix double-free typo in print_error() 2013-12-06 23:25:06 -06:00
Nicolas Williams dac081e183 Fix manual.yml 2013-12-06 11:58:55 -06:00
Stephen Dolan 489fcdc089 Merge pull request #224 from nicowilliams/features
Features: more arity, string ops
2013-12-04 17:36:08 -08:00
Nicolas Williams 3ba7c97b2a Add self to AUTHORS 2013-12-04 18:21:42 -06:00
Nicolas Williams 54635000c5 Conditionally #define _GNU_SOURCE in compile.c 2013-12-04 18:21:42 -06:00
Nicolas Williams 426edff09c Add tests for string index by string and builtins 2013-12-04 18:21:42 -06:00
Nicolas Williams b37ce20c2a Add index and rindex builtins 2013-12-04 18:21:42 -06:00
Nicolas Williams cac14a531d Add index strings by string; return string indexes
% jq '.[","]'
    "a,bc,def,ghij,klmno"
    [1,4,8,13]
    %
2013-12-04 18:21:42 -06:00
Nicolas Williams ae7a042876 Make length return abs value of numeric inputs 2013-12-04 18:21:42 -06:00
Nicolas Williams eb165459aa Add callback interface for errors
Printing to stderr is not the right answer for a library.
2013-12-04 18:21:41 -06:00
Nicolas Williams 04bc2ef7cf Add jv_string_vfmt() 2013-12-04 18:21:41 -06:00
Nicolas Williams 09104ce531 Document ltrimstr and rtrimstr 2013-12-04 18:21:41 -06:00
Nicolas Williams 9fa2173825 Test ltrimstr and rtrimstr functions 2013-12-04 18:21:41 -06:00
Nicolas Williams 346da34432 Add ltrimstr and rtrimstr functions 2013-12-04 18:21:41 -06:00
Nicolas Williams fa316ac430 Document -u / --unslurp option 2013-12-04 18:21:41 -06:00
Nicolas Williams 77936a594d Add -I / --online-input for huge top-level arrays 2013-12-04 18:21:41 -06:00
Nicolas Williams 4e07eac356 Document -e / --exit-status argument 2013-12-04 18:21:40 -06:00
Nicolas Williams d018e43ddc Add -e | --exit-status CLI option 2013-12-04 18:21:40 -06:00
Nicolas Williams 8cee66e609 Document tojson and fromjson builtins 2013-12-04 18:21:40 -06:00
Nicolas Williams 3c4248192d Test tojson and fromjson 2013-12-04 18:21:40 -06:00
Nicolas Williams 255f8ef5be Add tojson and fromjson builtins 2013-12-04 18:21:40 -06:00
Nicolas Williams 20b32db5af Document split function 2013-12-04 18:21:40 -06:00
Nicolas Williams b634722c8a Document string multiplication and division 2013-12-04 18:21:40 -06:00
Nicolas Williams c9e8581f45 Document string functions and slicing 2013-12-04 18:21:39 -06:00
Nicolas Williams da129f6664 Test string slicing 2013-12-04 18:21:39 -06:00
Nicolas Williams 884e6c7d8b Add string slicing 2013-12-04 18:21:39 -06:00
Nicolas Williams 5aadaa79eb Add tests for string division/splitting 2013-12-04 18:21:39 -06:00
Nicolas Williams cf562961b7 Add string division by string (split on separator) 2013-12-04 18:21:39 -06:00
Nicolas Williams 63bed9bdf1 Test starts/endswith and string multiplication 2013-12-04 18:21:39 -06:00
Nicolas Williams 5989dbdfcf Add string multiplication by number 2013-12-04 18:21:39 -06:00
Nicolas Williams db19a11399 Add startswith/endswith 2013-12-04 18:21:38 -06:00
Nicolas Williams 6c734cb609 Add explode/implode jq functions to match jv API 2013-12-04 18:21:38 -06:00
Nicolas Williams ef41d1cb57 Use uint32_t for codepoint in jv_string_append_codepoint() 2013-12-04 18:21:38 -06:00
Nicolas Williams 08b4a34a8e Add jv string utility functions
jv_string_empty()
        -> return an empty string with given allocated length (for fast
           appends)
    jv_string_append_codepoint
        -> append a single codepoint (int) to the given string
    jv_string_explode
        -> return an array of codepoints making up a string
    jv_string_implode
        -> return the UTF-8 encoding of an array of codepoint numbers
2013-12-04 18:21:38 -06:00
Nicolas Williams 1fc5656762 Support more arguments for defs 2013-12-04 18:21:38 -06:00
Stephen Dolan 91cfc34ae4 Preserve insertion order in objects. Closes #169. 2013-12-04 22:30:39 +00:00
Nicolas Pouillard a0b942a7f5 Add a few more test cases (from the man page)
Signed-off-by: Nicolas Williams <nico@cryptonector.com>
2013-11-30 00:55:26 -06:00
Stephen Dolan 6a401c8262 Add a --unbuffered option. Closes #206 2013-11-08 12:21:45 +00:00
Stephen Dolan d235f32bc9 Merge pull request #210 from Habbie/patch-1
count should be length
2013-11-08 03:43:57 -08:00
Peter van Dijk bdbfa7e32c count should be length
Example refers to a count function, which does not exist. Replacing it with length works.
2013-11-07 16:39:44 +01:00
Stephen Dolan 78976b3504 Merge pull request #193 from ryo1kato/fix-docs
Docs: add description of --from-file option
2013-11-07 04:41:01 -08:00
Stephen Dolan 8d4748c8df Fix a crash on group_by of empty list. Fixes #208. 2013-11-07 12:18:48 +00:00
Ryoichi KATO da723d3e11 Docs: add description of --from-file option 2013-10-16 21:42:29 +09:00
Stephen Dolan 0feeb7d0d4 Merge pull request #188 from Juanxo/patch-1
Fix typo on error message
2013-10-07 06:59:50 -07:00
Juan Guerrero 9e53f36a74 Fix typo on error message 2013-10-06 17:52:04 +02:00
Stephen Dolan 7e7c08da47 Merge pull request #185 from qoelet/master
Add missing -i flag in build instructions
2013-09-20 06:05:42 -07:00
Kenny Shen 2f6b89ef70 Add missing -i flag in build instructions 2013-09-20 10:34:57 +08:00
Stephen Dolan 4e3024cb61 Merge pull request #182 from mdaines/sqrt-builtin
Add builtin for calculating square root
2013-09-14 16:34:52 -07:00
Michael Daines c427bf6a94 Add test showing calculation of standard deviation 2013-09-14 17:48:19 -04:00
Stephen Dolan dae2422fd1 Merge branch 'libjq'
Conflicts:
	Makefile.am
	configure.ac
2013-09-14 19:30:39 +01:00
Stephen Dolan f98e3e6c26 Merge pull request #181 from mdaines/patch-1
Fix typo
2013-09-13 15:24:22 -07:00
Mike Daines fe99532f8b Fix typo 2013-09-13 12:15:52 -04:00
Stephen Dolan 41a4968c90 Merge pull request #171 from joelittlejohn/fix-makefile
Fix Makefile after refactoring of stacks in 05d90517b0
2013-09-13 09:02:42 -07:00
Stephen Dolan 1cdaabf2dd Merge pull request #175 from lipidity/fix-build
Call AM_INIT_AUTOMAKE once only
2013-09-13 09:02:24 -07:00
Michael Daines 82d8253c19 Add sqrt operator 2013-09-11 20:24:41 -04:00
Jack Pearkes c1717d6e12 docs: update the tutorial to use GitHub's API 2013-09-04 11:42:40 +02:00
Ankur f0d1c39b5b Call AM_INIT_AUTOMAKE once only
Fixes build with automake-1.14
2013-09-01 18:58:26 +10:00
Joe Littlejohn 111aea3f0a Fix Makefile after refactoring of stacks in 05d90517b0 2013-08-19 13:40:08 +01:00
Stephen Dolan 37cfc912c1 Remove #includes from jv.h 2013-06-23 14:23:07 +01:00
Stephen Dolan 8c2e228c74 Fix the jv_parser interface. 2013-06-23 12:26:49 +01:00
Stephen Dolan 8630fb02bb Use libtool's built-in symbol exporting rather than a mapfile. 2013-06-23 12:10:58 +01:00
Stephen Dolan 5052ab4e2b Move gen_utf8_tables to scripts 2013-06-23 12:08:22 +01:00
Stephen Dolan a44f739356 Move libtool m4 junk to config/ and delete some autogenerated files. 2013-06-23 12:01:58 +01:00
Stephen Dolan f44ecf3872 Remove Autoconf-generated config.h. 2013-06-23 12:01:58 +01:00
Stephen Dolan 1c9e03f800 Merge branch 'header-cleanup' into libjq
Conflicts:
	Makefile.am
2013-06-22 23:27:16 +01:00
Stephen Dolan 5d9ec83805 Build libjq only once, and link it statically to ./jq
This means ./jq is a real binary rather than a libtool turd.
2013-06-22 20:53:28 +01:00
Stephen Dolan 3a295c659b Fix distcheck.
Update list of files to be distributed.
2013-06-22 20:53:03 +01:00
Stephen Dolan d253414783 Merge pull request #162 from stedolan/utf8-fixes
Utf8 fixes. Closes #161
2013-06-22 09:30:59 -07:00
Stephen Dolan 8b1b503609 Reject all overlong UTF8 sequences. 2013-06-22 13:43:46 +01:00
Stephen Dolan ff48bd6ec5 Fix various UTF8 parsing bugs.
In particular, parse bad UTF8 by replacing the broken bits with U+FFFD
and resychronise correctly after broken sequences.
2013-06-22 13:34:24 +01:00
Stephen Dolan 54b9c9bdb2 Fix example in manual for floor. See #155. 2013-06-22 12:17:28 +01:00
Nicolas Williams 3dcdc582ea Document floor 2013-06-21 15:27:34 -05:00
Nicolas Williams 7c4171d414 Add floor operator 2013-06-21 15:27:34 -05:00
Nicolas Williams 1d839ff9df Document mod 2013-06-21 15:27:34 -05:00
Nicolas Williams 3403d07912 Add mod (and setmod) operators 2013-06-21 15:27:34 -05:00
Nicolas Williams e8643787cf Update .gitignore 2013-06-21 12:41:57 -05:00
Nicolas Williams 298b2a6033 Add libjq autoconf goo 2013-06-21 11:57:12 -05:00
Nicolas Williams c791428905 Quiet setup.sh re: tmp dir 2013-06-21 11:57:11 -05:00
Stephen Dolan dc2fe63e42 Merge pull request #157 from nicowilliams/argfile-fixes
Argfile fixes
2013-06-21 09:40:32 -07:00
Stephen Dolan 7af88962ee Move cfunction invocation code to the interpreter loop. 2013-06-21 12:06:28 +01:00
Nicolas Williams 3216d7683a Fix serious bug in handling of --argfile 2013-06-18 19:05:52 -05:00
Nicolas Williams e4094c8664 Fix leaks in jv_load_file() 2013-06-18 19:05:52 -05:00
Stephen Dolan b49d65a276 Fold opcode.{c,h} into bytecode.{c,h} 2013-06-18 01:36:18 +01:00
Stephen Dolan 824ce76cec Simplify block functions for variables 2013-06-18 01:07:18 +01:00
Stephen Dolan 2a18f97d12 Saner build instructions in README.md
Closes #144
2013-06-18 00:29:05 +01:00
Stephen Dolan 8c4d29ee38 Remove some initialise-to-zero code.
This lets valgrind find more bugs - if a field isn't given a
well-defined value valgrind will now find it instead of seeing it
set to zero with memset.
2013-06-18 00:17:20 +01:00
Stephen Dolan 2c13f4fe72 Merge branch 'stack-refactor-merge' 2013-06-18 00:13:44 +01:00
Stephen Dolan 94931040a8 Merge branch 'stack-refactor'
Conflicts:
	execute.c
2013-06-18 00:06:00 +01:00
Stephen Dolan b78a4cdc93 Merge pull request #154 from nicowilliams/no-fopen-re
Remove accidentally introduced use of fopen "e"
2013-06-17 15:34:01 -07:00
Nicolas Williams da09544bb0 Remove accidentally introduced use of fopen "e" 2013-06-17 17:27:25 -05:00
Stephen Dolan ce17b6bd53 Merge pull request #114 from nicowilliams/nomem_handler
Add jv_nomem_handler()
2013-06-16 06:20:27 -07:00
Nicolas Williams fe03bea191 Remove last remnant of main.h 2013-06-16 08:06:52 -05:00
Nicolas Williams 0ed6347d47 Allow --run-tests to take a file argument 2013-06-15 17:37:15 -05:00
Nicolas Williams 3f86e97f70 Fixup API to get closer to a libjq 2013-06-15 17:37:15 -05:00
Stephen Dolan 69fa279e3c Merge pull request #150 from nicowilliams/jv_load_file
Move slurp_file() into library as jv_load_file()
2013-06-15 05:29:17 -07:00
Nicolas Williams bbf4e05fde Move slurp_file() into library as jv_load_file()
Needed as part of creating a libjq.
2013-06-15 00:08:59 -05:00
Stephen Dolan 05d90517b0 Clean up lots of stack and frame logic.
Move frame defs to execute.c
2013-06-14 22:08:18 +01:00
Stephen Dolan 5f5c1dc5a6 Simplify frame logic. 2013-06-14 01:20:24 +01:00
Stephen Dolan e0524644f8 Unify all stacks. Passes tests, but needs cleanup. 2013-06-13 21:50:32 +01:00
Stephen Dolan 81e2336ace Support ."foo" syntax for accessing fields. See #141. 2013-06-11 22:26:39 +01:00
Stephen Dolan 25bf1a0169 Unify frame and data stacks 2013-06-10 01:17:58 +01:00
Stephen Dolan c8ab67be27 Speed up cached configure (./configure -C) 2013-06-05 16:40:01 +01:00
Stephen Dolan 6c34288ebf Clean up flex lines in build 2013-06-05 13:43:23 +01:00
Stephen Dolan 2ffbb9d6b7 Lex and parse .foo better.
'.as' is now valid, '. foo' is now invalid. See #141.
2013-06-05 13:41:56 +01:00
Stephen Dolan dad608d5c0 Merge pull request #142 from lanthaler/master
Update README.md
2013-06-04 04:13:24 -07:00
Markus Lanthaler 04b939625b Update README.md
Update the link to the documentation. All GitHub pages are now using the github.io domain.
2013-06-04 13:04:42 +02:00
Stephen Dolan 5dac485458 Make jq --version print to stdout, not stderr 2013-06-03 23:22:59 +01:00
Stephen Dolan 2c4ccd122e Better error handling for .foo case in parser. See #141. 2013-06-03 23:12:31 +01:00
Stephen Dolan b90ae078b8 Let the parser rather than the lexer handle invalid characters. 2013-06-03 23:12:31 +01:00
Stephen Dolan cc8761dbde Add command-line option to sort object keys.
Closes #79.
2013-06-03 23:12:31 +01:00
Stephen Dolan 08cc591884 Clean up Makefile.am (distcheck, rebuild version.h less often) 2013-06-03 23:12:31 +01:00
Stephen Dolan 1bc4d04f43 Merge pull request #139 from 13ren/stopwarning
Stop warning on fgets, jq_test.c:42
2013-06-01 07:30:15 -07:00
Brendan Macmillan d4018192a9 Stop warning on fgets, simple version 2013-06-01 11:12:28 +10:00
Brendan Macmillan 19ddbfeca9 Stop warning on fgets, complex version 2013-06-01 11:03:15 +10:00
Stephen Dolan 4fed6763fc Squash a warning on some GCC versions 2013-05-31 12:15:58 +01:00
Stephen Dolan 73446a9cce Support for printing object keys in sorted order.
No command-line option to enable this yet. See #79.
2013-05-29 11:47:12 +01:00
Stephen Dolan dcf1ac0d1f Merge pull request #135 from 13ren/locfile_bugfix
Locfile bugfix
2013-05-29 03:38:15 -07:00
Stephen Dolan 364c58d13f Merge pull request #130 from 13ren/libload_2
libload_2 (update to pull request #126, for issue  #112)
2013-05-29 03:37:36 -07:00
Brendan Macmillan 8cfe11e4e2 Bugfix multiline off-by-one (locfile.c) 2013-05-29 15:23:08 +10:00
Brendan Macmillan 40378a18f7 locfile.h -> locfile.h + locfile.c
clean up includes of a few files
2013-05-29 15:17:01 +10:00
Brendan Macmillan 7fb5d8c973 Hack bugfix for multiline off-by-one (locfile.c) 2013-05-29 15:16:45 +10:00
Brendan Macmillan 6e373942e5 Load library from ~/.jq 2013-05-29 15:05:40 +10:00
Stephen Dolan f0b982795c Make jq --version report an actual git revision.
Closes #129.
2013-05-24 23:35:38 +01:00
Stephen Dolan cbd422d56e Merge pull request #133 from nicowilliams/option_argfile-rebase0
Add --argfile variant of --arg (issue #117)
2013-05-24 04:13:45 -07:00
Nicolas Williams 03598bb5d6 Add --argfile variant of --arg (issue #117)
This is useful when one has a database (in JSON form) to query using jq
input data.

    % echo '{"a":1, "c":5}' > db.json
    % echo '"c"'|./jq --argfile f /tmp/a '$f[.]'
    5
    % echo '"a"'|./jq --argfile f /tmp/a '$f[.]'
    1
    % echo '"b"'|./jq --argfile f /tmp/a '$f[.]'
    null
    %
2013-05-23 13:52:06 -05:00
Stephen Dolan 983a53a643 'make clean' won't delete jq.1 if it can't be rebuilt.
See #131
2013-05-23 19:20:19 +01:00
Stephen Dolan 0535e724a2 jq 1.3 release 2013-05-19 11:48:18 +01:00
Stephen Dolan fd1ac5dd79 EACH need not make a backtrack point on the last iteration 2013-05-17 22:49:08 +01:00
Stephen Dolan 9b91eafac4 Make array construction use LOADVN (slightly faster) 2013-05-16 15:09:59 +01:00
Stephen Dolan bc42812715 Remove the insane "fold" operation, replace with saner "reduce". 2013-05-16 15:07:53 +01:00
Stephen Dolan 8fbee891b3 Add LOADVN opcode.
Does a variable load, but sets the variable to be null afterwards.
2013-05-16 15:02:18 +01:00
Stephen Dolan 919292764a Reverse function. Closes #94. 2013-05-16 14:25:28 +01:00
Stephen Dolan a1e4dfa324 Better debug info for struct bytecode.
--debug-dump-disasm produces more comprehensible output now.
2013-05-16 00:51:17 +01:00
Stephen Dolan f8644c31a3 Only generate code for those builtin functions actually used.
Makes output of --debug-dump-disasm much simpler.
2013-05-15 01:37:15 +01:00
Stephen Dolan 1e2851cdb3 Remove the YIELD opcode (use RET instead) 2013-05-15 01:23:06 +01:00
Stephen Dolan e83e51eb56 'length' function now measures string length in codepoints, not bytes. 2013-05-15 00:37:38 +01:00
Stephen Dolan c496a924ce Bugfix for array slices. 2013-05-15 00:22:44 +01:00
Stephen Dolan 81be37b236 Add the range function 2013-05-14 16:09:10 +01:00
Stephen Dolan 4d540b7102 Fix some documentation bugs 2013-05-13 20:26:44 +01:00
Stephen Dolan 33901b74b1 Array slicing. Closes #2. 2013-05-13 20:16:19 +01:00
Stephen Dolan a47cfa4757 Delete unnecessary _POSIX_SOURCE. Closes #89 2013-05-13 16:41:11 +01:00
Stephen Dolan a625d2821e Add to_entries, from_entries and with_entries functions.
Closes #97.
2013-05-13 16:15:49 +01:00
Stephen Dolan d8706fd460 Fix the //= operator.
Closes #62.
2013-05-13 16:04:30 +01:00
Stephen Dolan e13f24ab45 Better error handling for INSERT opcode
jq 'null | {(.a): 1}' no longer crashes.
2013-05-13 15:42:57 +01:00
Stephen Dolan 34a63246ab Remove the is_backtrack_frame special case hack. 2013-05-13 15:37:57 +01:00
Stephen Dolan 8c708f3c7a Refactor path logic. 2013-05-13 15:00:05 +01:00
Stephen Dolan b0e65d149f Fix a bug triggered by jv_array_slice. 2013-05-13 14:10:39 +01:00
Stephen Dolan e29db8d272 Fix bug with path handling (used in assignments).
Closes #67
2013-05-11 18:10:21 +01:00
Stephen Dolan 7ca5127fcc Merge branch 'autotools' 2013-05-11 15:21:23 +01:00
Stephen Dolan 4b1b9c219a Delete some Autotools junk and update README.
Autotools autogenerated files that change frequently have been
removed, those that don't are still checked in.
2013-05-11 15:15:56 +01:00
Stephen Dolan 4a5405e4b8 Rake task to build website including source dist and binaries. 2013-05-11 14:57:58 +01:00
Stephen Dolan d03149f4df Rake task to build source distribution for website 2013-05-11 14:48:39 +01:00
Stephen Dolan 18cf4ad475 Add the docs to the distributed tarballs 2013-05-09 20:53:51 +01:00
Stephen Dolan ba4bb136bb Fix manpage build. 2013-05-09 20:43:26 +01:00
Stephen Dolan e6494857fc Add some build instructions for how to install doc dependencies.
jq can now build without Ruby, but you won't get a nice manpage.
2013-05-09 20:35:19 +01:00
Stephen Dolan 12d2d4d1f5 Don't build the manpage unless there's a working Ruby setup. 2013-05-09 15:22:38 +01:00
Stephen Dolan d75414bc94 support for out-of-tree tests 2013-05-09 13:40:00 +01:00
Stephen Dolan a592c98461 Fail properly when attempting to build manpage without rake 2013-05-09 13:10:09 +01:00
Stephen Dolan f6089a627a Autotools hackery so that "make dist" builds lexer and parser. 2013-05-09 11:31:17 +01:00
Stephen Dolan 898dc8978b Add a redundant intialisation to squash a gcc warning. 2013-05-09 11:30:49 +01:00
Stephen Dolan d43084086b Make manpage building more robust. 2013-05-09 02:38:57 +01:00
Stephen Dolan 124bda6993 Add a "binaries" target to the website Rakefile. 2013-05-09 02:18:28 +01:00
Stephen Dolan 7fbc50239c Minor docs fix to get rid of Bonsai warnings. 2013-05-09 02:01:01 +01:00
Stephen Dolan 282667bbd4 Script for cross-compiling jq binaries for other platforms. 2013-05-09 01:50:20 +01:00
Stephen Dolan 63b689bf54 Use the version number from configure.ac in the website. 2013-05-08 19:57:29 +01:00
Stephen Dolan c710f91257 Add the parser to BUILT_SOURCES 2013-05-08 19:57:05 +01:00
Stephen Dolan a511d03d85 Configure whether colour is displayed by default directly in source 2013-05-08 19:37:12 +01:00
Stephen Dolan 9d245c88c4 Check in a pile of Autotools junk, including the configure script. 2013-05-08 18:55:10 +01:00
Stephen Dolan 7813f363a2 Merge pull request #115 from nicowilliams/complex
Rename complex to nontrivial for safety (#113)
2013-05-08 07:07:31 -07:00
Stephen Dolan d121420757 Clean up autotools config.
Remove some of @stagrlee's hard work since jq's testsuite is
less silly than when he wrote the autotools config.
2013-05-08 02:42:23 +01:00
Stephen Dolan 1ebb382540 Check in jv_utf8_tables.gen.h, it is unlikely to change. 2013-05-08 01:33:04 +01:00
Stephen Dolan 2363246eea Merge remote-tracking branch 'stagrlee/master' into autotools
Conflicts:
	Makefile
	jq_test.c
2013-05-08 00:52:24 +01:00
Nicolas Williams fb3c124af1 Rename complex to nontrivial for safety (#113) 2013-05-07 18:46:13 -05:00
Stephen Dolan 46814da91a Merge pull request #119 from 13ren/master
tiny typo fix in manual
2013-05-07 03:57:47 -07:00
Nicolas Williams 65deaaacf5 Fix #111: exit(1) when inputs are not valid JSON 2013-05-06 12:36:53 -05:00
Stephen Dolan 74eb914aba Add trailing slashes to various internal doc links.
Fixes #110
2013-05-06 15:03:14 +01:00
Stephen Dolan 5be97463ec Add a --arg option to allow variables to be passed from the cmdline.
Closes #107
2013-05-06 14:21:00 +01:00
Stephen Dolan 48be23233a Add the "has" function. Closes #74. 2013-05-06 13:46:54 +01:00
Stephen Dolan 88a6dc5343 Merge pull request #77 from jkleint/patch-1
Clarify description of `as`
2013-05-05 18:39:59 -07:00
Stephen Dolan fee38afd8b Document behaviour of .[] on objects.
Closes #57.
2013-05-06 02:37:34 +01:00
Stephen Dolan cacda19e75 Add documentation for "type".
Closes #75.
2013-05-06 02:34:02 +01:00
Stephen Dolan 7f979cf1c7 Update gems and use HTTPS to get them. 2013-05-06 01:15:33 +01:00
Stephen Dolan 8bf4a73f3d Fix some bad escaping 2013-05-06 01:14:53 +01:00
Stephen Dolan d04bd897e3 Merge commit 'e45f93f85f22c73025830a6bd0b2807263068996'
See #87
2013-05-05 23:58:24 +01:00
Stephen Dolan 1741d8c161 Remove JQ_DEBUG #define and jq_test binary, simplifying build.
The debugging features previously available via JQ_DEBUG are now
command-line options.
2013-05-05 23:12:10 +01:00
Stephen Dolan c1748fa633 WIP 2013-05-05 22:59:53 +01:00
Stephen Dolan a49402c53a Combine the functionality of jq and jq_test
One binary is much simpler. ./jq --run-tests now runs the tests.
2013-05-05 22:59:53 +01:00
Stephen Dolan 47e015e946 Make a few constant globals actually const.
This means the .data and .bss sections are empty, a good thing
in a shared library (see #98).
2013-05-05 22:59:53 +01:00
Stephen Dolan ab1a97472c Merge pull request #109 from nicowilliams/no_mutable_globals-rebased
Remove globals/make jq_init/next/teardown() an API
2013-05-05 12:29:07 -07:00
Stephen Dolan 74df082283 Merge pull request #93 from shayel/master
Allow manpage to build
2013-05-05 12:13:36 -07:00
Nicolas Williams a136c3b7c9 Build and install libjq 2013-04-28 20:12:49 -05:00
Nicolas Williams 312ab754a2 Remove unnecessary #includes 2013-04-28 20:12:15 -05:00
Nicolas Williams 87e9c64003 Remove globals/make jq_init/next/teardown() an API 2013-04-28 18:46:21 -05:00
Shay Elkin 054e3f3795 Add missing require to Rakefile
Use bundle exec to make manpage
Don't set executable bit on manpage
2013-03-24 14:45:10 +02:00
Simon Elsbrock e45f93f85f fix typo in manual 2013-02-20 22:38:50 +01:00
Lee Thompson dc4d3d8cbe get man pages working 2013-02-04 13:35:42 -06:00
Lee Thompson 1d3d2efc89 add man page to release 2013-02-04 07:01:18 -06:00
Lee Thompson 43fd939f16 rework build to not distribute tests unless --enable-devel is specificied to autoconf 2013-02-03 14:49:03 -06:00
Lee Thompson d8a072ca01 fix email addresses 2013-02-03 00:35:56 -06:00
Lee Thompson f62184855b use AM_SILENT_RULES 2013-02-03 00:09:47 -06:00
Lee Thompson 0370d5b526 fix RPM build process 2013-02-02 23:50:43 -06:00
Lee Thompson 52f906ab43 fix jq_test so that it runs in the distcheck phase of autoconf 2013-02-02 23:05:00 -06:00
Lee Thompson c7725a8d4d merging upstream stedolan changes 2013-02-02 20:39:23 -06:00
Lee Thompson dd70eeb29d use built in macro to RPM 2013-01-29 19:13:22 -06:00
jkleint 115552f3a6 Clarify description of as
It's more of a foreach loop than an identity operation.

Addresses issue #72.
2013-01-25 12:17:56 -05:00
13ren d0cd26cd8e Update docs/content/3.manual/manual.yml
There's a , after map ; should be .
#65
2013-01-13 17:33:01 +11:00
Stephen Dolan 925ec3751f Fix negative number syntax. Add a unary '-' operator.
Closes #63.
2013-01-03 12:53:23 +00:00
Stephen Dolan c013b557a2 Change APPEND opcode to directly modify a variable.
Avoids a big O(n^2) loop in constructing arrays. Fixes #61.
2013-01-03 12:51:33 +00:00
Stephen Dolan fb84541e11 Clean up jv_object_foreach and add jv_array_foreach 2012-12-31 23:27:00 +00:00
Stephen Dolan 38fe6fc0cb Ignore some more stuff. 2012-12-31 23:25:12 +00:00
Stephen Dolan b3bad59dd7 Add the recurse function. See #37. 2012-12-29 22:59:07 +00:00
Stephen Dolan e0cda536f3 Make null + foo return foo, rather than an error.
This also allows 'add' to be implemented in jq rather than C.
2012-12-29 16:52:26 +00:00
Stephen Dolan d5fdf70434 Refactor assignment.
New version is much more elegant and probably slower. Uses
library functions implemented in jq rather than dedicated opcodes.
2012-12-29 16:43:36 +00:00
Stephen Dolan 465a4ec565 Improvements to del(foo).
del(foo,bar) is now very different from del(foo),del(bar).
See #37.
2012-12-29 16:13:06 +00:00
Stephen Dolan 3a5377e183 Fix a bug in URI-encoding of certain non-ASCII characters.
Oh how I hate C casting rules.
2012-12-29 01:37:22 +00:00
Stephen Dolan 79c9c418c6 Path manipulation (path/getpath/setpath/delpath) and docs.
del function should fix #37.
2012-12-28 16:08:29 +00:00
Stephen Dolan f1e23448e7 Support for two-argument functions.
Should probably support n args instead. This is a quick hack.
Also, ';' is a slightly ugly argument separator.
2012-12-28 15:07:27 +00:00
Stephen Dolan 417899f9a0 Fold operation (code/docs/test) 2012-12-28 15:05:34 +00:00
Stephen Dolan 9302b16247 Docs typos. 2012-12-28 14:23:28 +00:00
Stephen Dolan 89e26969ae @foo syntax for encoding of strings into various formats.
Fixes part of #47 and #48.
2012-12-28 01:23:09 +00:00
Stephen Dolan 52db8000c6 Fix an embarassing I/O bug.
Pulling some I/O out to a function meant that buf changed from
"char buf[4096]" to "char* buf", and "sizeof(buf)" got a lot
less interesting. The upshot of this is that jq read input eight
bytes at a time, which is not the fastest.
2012-12-27 01:57:09 +00:00
Stephen Dolan d327772dfc JSON parsing error messages now specify a location of the error.
Should help with #53.
2012-12-27 01:56:23 +00:00
Stephen Dolan cedda2084d Sneaky valgrind trick to detect stack memory issues.
After something is popped from a stack, we overwrite the memory
with uninitialised data (if JQ_DEBUG is on). This means that
valgrind reports use-after-pop as an uninitialised memory error.
2012-12-24 17:11:18 +00:00
Stephen Dolan 0fed03f35a Fix a bad memory leak when using --raw-output 2012-12-23 15:41:20 +00:00
Stephen Dolan 5578ec0f69 Give Windows builds a proper .exe suffix. 2012-12-20 15:12:14 +00:00
Stephen Dolan a05904b226 Version 1.2! 2012-12-20 12:26:15 +00:00
Stephen Dolan c181a9185a Add a few lines to the bottom of the manpage. 2012-12-20 12:25:50 +00:00
Stephen Dolan 6a597b41a6 Update download page and build system to handle more platforms. 2012-12-20 12:17:56 +00:00
Stephen Dolan c85d8d8b4e Don't enable coloured output by default on Windows. 2012-12-20 12:16:22 +00:00
Stephen Dolan dcdb996085 Merge pull request #50 from stesh/master
Add information about installing dev environment on OS X
2012-12-20 03:41:19 -08:00
Stephen Dolan 5968f54f55 Fix a bug in stack reallocation during deep recursion. 2012-12-18 17:36:24 +00:00
Stephen Dolan 8dca3ef10d Print an error message and abort in out-of-memory situations.
Closes #43.

Tested with:

    ulimit -v 5000
    ./jq -n -c 'def f(x): x,f([x,x]); f(0)'
2012-12-18 17:01:23 +00:00
Stephen Dolan 04daafbde3 Add wrappers for malloc/realloc/free. See #43. 2012-12-18 16:53:51 +00:00
Stephen Dolan 75421cbfe3 Add the ability to specify input files on the commandline.
See #21.
2012-12-16 19:46:41 +00:00
Stephen Dolan b0aa62f2b7 Improve manpage introduction, add examples to manpage.
Closes #19
2012-12-16 17:11:40 +00:00
Stephen Dolan 3ae8fb7371 Fix a bug uncovered by tests extracted from manual.
`null | length` should give 0, and now does.
2012-12-16 13:10:48 +00:00
Stephen Dolan 73c657cdc4 Lots of build system and docs improvements, including a manpage.
- Build binaries for multiple platforms
 - Make a manpage out of the manual (see #19)
 - Extract more tests from the documentation
 - Fix a few documentation bugs uncovered by above.
2012-12-16 13:06:03 +00:00
Stephen Dolan 2b48ee4a8f Add a Gemfile to make building the documentation easier.
Use "bundle install" in the docs folder to install ruby dependencies.
2012-12-12 23:18:43 +00:00
Stephen Dolan 8ba2cef913 Delete autogenerated source from the repository.
This was a horrible hack to fix build issues on OS X some time
ago. There are better ways to fix build issues on OS X :)

See #49.
2012-12-10 22:36:01 +00:00
Stephen Dolan dcc93042d3 some words explaining struct inst a little 2012-12-10 22:35:33 +00:00
Stephen Dolan 34ff993059 error() function 2012-12-10 22:30:09 +00:00
Stephen Dolan b660bd2be6 Fix Rack setup so that viewing documentation locally is less painful. 2012-12-10 22:25:11 +00:00
Stephen Shaw 1db51e66bc Remove old OS X 'instructions' 2012-12-07 14:40:29 +00:00
Stephen Shaw c15e51ffb1 Add information about installing dev environment on OS X 2012-12-07 14:31:59 +00:00
Stephen Dolan 84026f0513 Change order of evaluation for certain indexing operations.
'.foo[.bar]' used to parse like '.foo | .bar as $b | .[$b]',
now it parses like '.bar as $b | .foo | .[$b]'.
2012-12-07 00:28:11 +00:00
Lee Thompson a156fb19de get testing working in autoconf 2012-12-04 19:50:10 -06:00
Lee Thompson ca862a594c kill prereq on RPM spec file 2012-12-04 19:00:59 -06:00
Lee Thompson dfa9e61aeb fixes jq_test core dump 2012-12-04 18:12:53 -06:00
Lee Thompson 99353d8c84 remove obsolete line 2012-12-04 16:50:11 -06:00
Stephen Dolan c0a2f1ab47 Add min, max, min_by, max_by functions. 2012-12-04 22:45:03 +00:00
Lee Thompson 76c8706145 fix rpm mv command when rpm stashes the package in a subdirectory 2012-12-04 16:29:01 -06:00
Lee Thompson 277249ffe6 change rpmbuild target to rpm 2012-12-04 14:49:39 -06:00
Stephen Dolan 616e8f9924 Refactor function argument passing into what it always should have been.
Most visible change is that error messages when a function is called
with the wrong number of arguments are much better.
2012-12-04 00:39:21 +00:00
Lee Thompson 00e4feba81 get rpm building from tarball with autoconf 2012-12-03 17:27:40 -06:00
Lee Thompson 26bbed29f7 start work on getting RPM to work with tarball 2012-12-03 16:19:45 -06:00
Stephen Dolan a88d53d2fd Extend {foo} syntax to allow {"foo"} as well.
Useful when "foo" contains unusual characters. Should help with
the issues #7, #38, #40, #42.
2012-12-03 20:31:40 +00:00
Stephen Dolan 5f6a95c7b5 General cleanup - rename a few things, delete dead code. 2012-12-03 20:02:02 +00:00
Stephen Dolan fadeb66365 Add a unique function. 2012-12-03 02:02:12 +00:00
Stephen Dolan c8bbfed041 See, I told you colour was a bad idea! #11
Last commit broke the build, minor fix needed to testsuite.
2012-12-03 02:00:29 +00:00
Stephen Dolan e9c7548b82 Oh alright then, if you insist.
Colo(u)red output for jq. Enabled by default if isatty(stdout).
Closes #11.
2012-12-03 01:21:07 +00:00
Stephen Dolan 67f8ad9435 Ignore a UTF-8 BOM if one appears at the start of a JSON document.
Closes #45.
2012-12-02 23:53:55 +00:00
Stephen Dolan 5b45184a1a Make the -c output more compact. Closes #44. 2012-12-02 23:24:07 +00:00
Stephen Dolan 11965aaa2e sort_by and group_by functions, tests and docs. 2012-12-02 23:22:15 +00:00
Stephen Dolan ed7f95a492 Demote "contains" to a built-in function rather than an operator. 2012-12-02 22:52:38 +00:00
Stephen Dolan 63d10433c9 Fix a horrible scoping/associativity bug.
`1 as $x | . | $x` didn't compile, as it was parsed as

    (1 as $x | .) | $x

where `$x` is undefined, rather than as

    1 as $x | (. | $x)
2012-12-02 22:22:08 +00:00
Stephen Dolan ea9db414ed Clean up implementation of builtin functions. 2012-12-02 22:12:08 +00:00
Stephen Dolan b6f2fbbe62 Move all the includes one place to the left 2012-12-02 21:25:54 +00:00
Stephen Dolan 125e278502 Clean up calls to C functions, unify opcodes 2012-12-02 20:45:55 +00:00
Stephen Dolan 251f221b85 Refactor of function call codegen. Separate codegen for C and jq calls. 2012-12-02 20:45:06 +00:00
Stephen Dolan 9e743d517a Sort function and more general comparison operators. 2012-11-30 20:27:16 +00:00
Lee Thompson ddeec45b2e revert back to make invoking flex, fix a few bugs 2012-11-28 01:08:23 -06:00
Lee Thompson 006357f954 initial attempt at autoconf implementation, removed all generated code from git 2012-11-27 16:02:59 -06:00
Stephen Dolan 2dad2bdd97 Merge remote-tracking branch 'origin/master' 2012-11-26 22:24:04 +00:00
Stephen Dolan d56370f734 Move some higher-level JSON manipulation functions into jv_aux.{c,h} 2012-11-26 22:22:45 +00:00
Stephen Dolan 334a79b704 Define bytecoded builtins more concisely. 2012-11-26 18:53:47 +00:00
Stephen Dolan 4c65847568 Raise a proper error from the lexer on unmatched },),] 2012-11-26 01:40:35 +00:00
Stephen Dolan 4747f8681b Transparent handling for CBV arguments to C functions.
C function arguments closures are inlined before the call.
2012-11-26 01:36:55 +00:00
Stephen Dolan 3e3fe51959 Clean up function creation API and epilogue generation. 2012-11-26 00:39:01 +00:00
Stephen Dolan 924aeda504 Add some preprocessor junk to make codegen cleaner. 2012-11-25 23:49:57 +00:00
Stephen Dolan f19ba32fdd Minor refactor of function call setup to allow for multiple arguments. 2012-11-25 23:07:43 +00:00
Anthony Shortland a33c6f3df9 Fixed to work under CentOS 5.4 2012-10-30 14:30:36 -07:00
Anthony Shortland 04640aeaeb Added initial support for packaging jq as an RPM 2012-10-30 10:26:08 -07:00
Stephen Dolan 0923c79fee Merge pull request #36 from stroan/contains-operator
Implemented contains operator
2012-10-25 11:51:55 -07:00
Stephen Roantree 50bcbc2271 Merge branch 'master' of https://github.com/stedolan/jq into contains-operator
Conflicts:
	lexer.gen.c
	parser.gen.c
	parser.gen.h
	parser.gen.info
	parser.y
2012-10-25 10:02:02 -07:00
Stephen Roantree 1b556315af Remove redundant code from jv_object_contains 2012-10-25 06:29:23 -07:00
Stephen Roantree ecc8998d38 Restructure contains methods to use public jv methods 2012-10-24 18:29:33 -07:00
Stephen Dolan 72691b4909 Fix a bug in string parsing. Closes #35 2012-10-25 00:13:39 +01:00
Stephen Roantree 5e25c2a259 Implemented contains operator 2012-10-24 13:50:26 -07:00
Stephen Dolan 033d9b2fd5 Merge pull request #34 from dgryski/master
Add != (not equal) to list of supported binops
2012-10-24 02:43:54 -07:00
Damian Gryski 0adf4638d1 Update generated lexer.gen.* and parser.gen.* files 2012-10-24 09:31:21 +02:00
Damian Gryski e40778727b Replace yyscan_t with another pointer type that we control.
This prevents the circuluar dependency between parser.gen.h and
lexer.gen.h.  Newer versions of bison add a prototype for yyparse() to
parser.gen.h that include the as-yet-undeclared yyscan_t type.
2012-10-24 09:28:27 +02:00
Stephen Dolan 5a64a29774 Remove .gitignore from tarballs. 2012-10-23 21:58:50 +01:00
Damian Gryski 45b6fc8148 Add some in/equality test cases 2012-10-23 21:12:47 +02:00
Stephen Dolan 8ad3b6f9b9 Merge pull request #22 from alexchamberlain/patch-1
Arch Packaging docs
2012-10-23 11:24:12 -07:00
Damian Gryski 409456f581 Regenerate lex/yacc files 2012-10-23 17:02:19 +02:00
Damian Gryski 553de39b2a Implement 'not equal' (!=) as a binop 2012-10-23 17:01:39 +02:00
Damian Gryski b193bca563 yyscan_t isn't exposed by some versions of flex. 2012-10-23 17:00:42 +02:00
Alex Chamberlain 56aa4cf1df Add docs for installing on Arch Linux. 2012-10-23 14:46:44 +01:00
Stephen Dolan 2a610da2d1 Allow the 'keys' function to take arrays. 2012-10-22 23:31:07 +01:00
Stephen Dolan 93f80b2726 I should probably document select and empty :) (#29, #5) 2012-10-22 23:14:34 +01:00
Stephen Dolan 794d29a430 Docs fixes.
- Mention homebrew on download page (#25)
 - Document 'keys' and string interpolation
2012-10-22 22:56:56 +01:00
Stephen Dolan 3c8f5ea251 Merge pull request #14 from merriam/patch-2
Update docs/content/1.tutorial/default.yml
2012-10-22 14:31:04 -07:00
Stephen Dolan c0f1c7dc1a Delete a badly-thought-out bit of a Makefile. 2012-10-22 19:23:57 +01:00
Stephen Dolan a737a67eda updated autogenerated files 2012-10-22 19:18:35 +01:00
Stephen Dolan 48fb7c61b3 Support a -f option to load from a file, and # comments
This means '#!/usr/local/bin/jq -f' is now a sensible way
to start a file.

Closes #13
2012-10-22 19:18:30 +01:00
Stephen Dolan f2601ab4f0 Fix logic for "slurp" option. Fixes #18 2012-10-22 18:55:56 +01:00
Stephen Dolan 2620e21dcc Make the gen_utf8_tables script Python3 compatible (#20) 2012-10-22 18:47:52 +01:00
Charles Merriam ce3328f11e Update docs/content/1.tutorial/default.yml 2012-10-22 02:43:53 -07:00
Stephen Dolan c53e001973 Version number tracking. 2012-10-21 23:26:31 +01:00
Stephen Dolan 976e4ff711 Fix a mistake in the docs. Fixes #10 2012-10-21 22:16:23 +01:00
Stephen Dolan 6e64cdcbfc Merge pull request #8 from maxme/master
add install and uninstall rules in Makefile
2012-10-21 12:23:22 -07:00
Stephen Dolan 73e479e171 Update website: binaries + version bump. 2012-10-21 20:16:26 +01:00
Maxime Biais 758da604cd add install and uninstall rules in Makefile 2012-10-21 20:55:50 +02:00
Stephen Dolan 95a0074edf Check in a bunch of autogenerated files.
These seem not to build with the version of bison, etc. on OS X.
This is a hack for now, I'll fix the OS X build when I get access
to a mac.
2012-10-21 19:52:16 +01:00
Stephen Dolan 32e1b114df Add a 'keys' function. Fixes #4. 2012-10-20 00:26:37 +01:00
Stephen Dolan afec2544f1 A string interpolation syntax that I don't hate. Also tests.
You can interpolate values into strings using \(this syntax), e.g.

    "best \("str" + "ing") ever"
2012-10-20 00:09:20 +01:00
Stephen Dolan 6828ec9852 Merge pull request #6 from stroan/numeric-comparison-binops
Add <,>,<=,>= binops
2012-10-11 07:47:42 -07:00
Stephen Roantree b3557429eb Make comp binops nonassoc 2012-10-10 23:00:24 +01:00
Stephen Roantree 0618e61e0b Add <,>,<=,>= binops 2012-10-07 22:34:12 +01:00
Stephen Dolan a2643cc0d5 Allow underscores in IDENT tokens. Fixes #3.
IDENT syntax now includes ASCII letters and underscores, so
'.foo_bar' now works. Non-ASCII letters won't work in IDENT
tokens (it's impossible to tell which non-ascii characters are
"letters" without full unicode tables), so '.données' is still
a syntax error (the workaround is '.["données"]', since you can
put anything you like in a string).
2012-09-22 14:03:46 +01:00
Stephen Dolan 359d5f33bd Add a bunch of commandline options for controlling I/O format.
Also docs for the above. Closes #1.
2012-09-20 19:31:14 +01:00
Stephen Dolan fcd7cf4ddd More broken links :( 2012-09-19 10:54:58 +01:00
Stephen Dolan 1f4a5d8d9f Make URLs relative to not break on github-pages 2012-09-19 10:48:23 +01:00
Stephen Dolan 92ab193369 Make the <title>s of the doc pages less messy. 2012-09-19 01:15:49 +01:00
Stephen Dolan fc57cf702c No HTTPS for me, sadly. 2012-09-19 01:12:33 +01:00
Stephen Dolan f51eca20f5 Fix some issues with relative URLs on Github Pages. 2012-09-19 01:10:47 +01:00
Stephen Dolan 9887cb57b2 Add a rake task to compile docs into static html. 2012-09-19 01:03:05 +01:00
Stephen Dolan 040604b7db Use a theme from Bootswatch for docs. 2012-09-19 01:00:25 +01:00
Stephen Dolan 26c7132237 Ensure 'make tarball' creates the required directories. 2012-09-19 00:36:37 +01:00
Stephen Dolan 483f6f2134 Add some release stuff to Makefile. 2012-09-19 00:34:49 +01:00
Stephen Dolan c463877e17 Add binaries and update download docs. 2012-09-19 00:08:02 +01:00
Stephen Dolan 71ab537ee9 Initialise some variables to keep gcc happy. 2012-09-18 23:46:23 +01:00
Stephen Dolan 134b062be7 Fix a parsing bug for \uXXXX escapes (some invalid escapes were accepted).
Found by gcc -O -Wall identifying a use of uninitialised variables.
2012-09-18 23:45:30 +01:00
Stephen Dolan 3898f744dc Add a README 2012-09-18 23:40:19 +01:00
Stephen Dolan 305059ff9f Usage messages if jq is run with no arguments.
Beats segfaulting.
2012-09-18 23:37:46 +01:00
Stephen Dolan 5863160112 Implement the 'add' builtin promised by the docs' examples. 2012-09-18 23:32:24 +01:00
Stephen Dolan fbf7bc835b More docs + docs cleanup 2012-09-18 23:17:27 +01:00
Stephen Dolan 04e85e11de Print Unicode characters unescaped by default. 2012-09-18 22:47:19 +01:00
Stephen Dolan 20e45f363c Separate the tests and the main program. 2012-09-18 22:17:13 +01:00
Stephen Dolan 46af5238ce Add a COPYING file with too many words in it. 2012-09-18 17:59:09 +01:00
Stephen Dolan cf134909fd Documentation. Copious. 2012-09-18 17:51:53 +01:00
Stephen Dolan a4eea165bb Move everything around - delete old Haskell code, clean up build. 2012-09-18 17:44:43 +01:00
Stephen Dolan 25cbab056b Slightly better string interpolation. 2012-09-18 13:22:22 +01:00
Stephen Dolan 8041ce3119 Add the builtin 'select' function. 2012-09-18 12:58:39 +01:00
Stephen Dolan 8f0c91c032 Add a missing file from prev commit 2012-09-18 12:58:29 +01:00
Stephen Dolan 940f8c4270 Builtin functions defined in jq. 2012-09-18 10:17:38 +01:00
Stephen Dolan 5b9bb64a88 -Werror was a little overzealous.
Older versions of gcc warn about random things. -Werror makes the
built fail on such compilers.
2012-09-18 09:43:44 +01:00
Stephen Dolan 7d8d97933f Builtin function 'type'. 2012-09-17 23:06:48 +01:00
Stephen Dolan f13865d7a1 Fix a precedence bug.
a == b or c == d should obviously parse as (a==b) or (c==d).
2012-09-17 22:37:51 +01:00
Stephen Dolan 49cf2e67fe Recursive functions + bugfix for stack reallocation. 2012-09-17 22:32:06 +01:00
Stephen Dolan cbdeddbab8 Make the code compile with warnings-as-errors.
-Wextra found a bona-fide bug: signed/unsigned comparison in a
stack overflow check.
2012-09-17 22:04:58 +01:00
Stephen Dolan eab9c4e587 Fix debugging code in execute.c so that debug jq compiles again. 2012-09-17 21:43:10 +01:00
Stephen Dolan 830610cef8 Make "not" a builtin function rather than syntax.
Fixes a really awkward grammar issue.
2012-09-17 21:08:43 +01:00
Stephen Dolan 297a0679d1 Redefine true and false. Define empty, distict from null.
This might be the most philosophical commit. I shall ponder it.

true, false, null, empty now defined as bytecoded builtins rather
than calls out to C functions.
2012-09-17 20:59:34 +01:00
Stephen Dolan fe33150b7f Merge branch 'master' of github:stedolan/jq
Conflicts:
	c/builtin.c
2012-09-17 20:49:41 +01:00
Stephen Dolan 3db27b01a1 First pass at string interpolation.
Requires a lexer hack, but a surprisingly un-hideous one. The lexer
maintains a stack of bracket characters so that it can match
parens/brackets/braces to tell where a subexp nested in a string
actually ends.
2012-09-17 20:41:57 +01:00
Stephen Dolan 326771f4b4 Add a "tostring" function. 2012-09-17 20:14:07 +01:00
Stephen Dolan 9c15ad2021 Support rendering a JSON value to a string buffer. 2012-09-17 20:11:12 +01:00
Stephen Dolan 84ea129f26 Better support for appending strings in JV. 2012-09-17 19:41:41 +01:00
Stephen Dolan 50ebb036c4 Bind builtin functions in a slightly less ugly way. 2012-09-16 17:08:56 +01:00
Stephen Dolan 539156430a Builtin function 'length', for arrays/objects/strings. 2012-09-16 11:08:42 +01:00
Stephen Dolan cc2fb20ca0 Hrm. Update operators (//=, +=, etc.) aren't very well thought out.
In complex cases, their behaviour is kinda weird. Here's a failing
test for what I think they should do.
2012-09-16 11:07:36 +01:00
Stephen Dolan df195b3187 Add update operators (+=, -=, *=, /= and //=) 2012-09-11 23:55:59 +01:00
Stephen Dolan f6c6ba95ad Make .[] capable of iteration over objects as well as arrays.
Also change an assert to a proper error message if it's given
something silly like a number.
2012-09-11 16:10:55 +01:00
Stephen Dolan d9d6f43407 Pretty-printing of JSON values. 2012-09-11 15:53:37 +01:00
Stephen Dolan 51a44edc63 JSON stream parser.
Allow multiple values on input as concatenated JSON objects,
possibly separated by whitespace.
2012-09-11 14:52:10 +01:00
Stephen Dolan 062a6aa6d7 More error handling - locations of refs to undefined symbols. 2012-09-11 12:24:54 +01:00
Stephen Dolan 4c7bede5a4 Better error handling and messages for invalid index/assign operations. 2012-09-11 10:37:44 +01:00
Stephen Dolan 0ce437ea97 More error handling in the parser.
Add a special case in the lexer to detect unterminated strings.
Add some error recovery in the parser for more copious error spam.
2012-09-11 10:18:18 +01:00
Stephen Dolan ac3f8bcc52 Proper error messages from lexer errors (e.g. bad characters). 2012-09-11 09:13:20 +01:00
Stephen Dolan 95dd64b0f0 Much, much better error reporting from the parser.
Getting proper locations through flex/bison was more effort than
was advertised.
2012-09-11 00:04:47 +01:00
Stephen Dolan 4d47f8f1e1 '==' operator, tests for equality and operator precedence. 2012-09-10 18:08:00 +01:00
Stephen Dolan 3895bbf856 String -> number conversions with "tonumber". 2012-09-10 17:08:13 +01:00
Stephen Dolan aa3ebdfe9b Multiplication and division operators.
It is an error to use these on anything but numbers. Maybe later
implementing python/ruby-style ("a" * 100) might be handy.
2012-09-10 16:57:17 +01:00
Stephen Dolan 6f3abbac62 Subtraction - as expected for numbers, ruby-style set diff for arrays. 2012-09-10 16:49:25 +01:00
Stephen Dolan 65ce73deb4 String concatenation. 2012-09-10 16:16:39 +01:00
Stephen Dolan e718bd50b6 Sensible error messages when a silly addition is performed.
Fix a test to no longer perform an invalid addition.
2012-09-10 16:15:05 +01:00
Stephen Dolan a3e509034b jv_string_fmt (create printf-formatted JSON strings) 2012-09-10 16:01:56 +01:00
Stephen Dolan 0159c0ecc5 Plug a memory leak in jv_object_merge 2012-09-10 15:08:58 +01:00
Stephen Dolan 649cac791a Make jv_invalid() first-class values capable of holding an error. 2012-09-10 15:04:19 +01:00
Stephen Dolan 5c25c90657 When comparing two objects for equality, we don't need to recurse
into e.g. array elements if the two objects are the same array.
2012-09-10 13:06:46 +01:00
Stephen Dolan c2593dc417 Make a main program that doesn't spam debugging info. 2012-09-10 10:23:15 +01:00
Stephen Dolan e258d20ba2 Make the '+' operator merge objects. 2012-09-09 19:17:07 +01:00
Stephen Dolan a4ff3d4670 Bugfix for expanding objects. 2012-09-09 18:09:05 +01:00
Stephen Dolan e6a85737da Print bad number values (Inf,NaN) "correctly". 2012-09-09 15:35:34 +01:00
Stephen Dolan 0dfaebcdcb null and string literals now exist, so add tests 2012-09-06 21:44:46 +01:00
Stephen Dolan 0fd7d4b30c UTF8 coding utilities and unicode escaping in jv_dump()'d strings.
Beyond the Basic Multilingual Plane, dead Cthulhu waits dreaming.
2012-09-06 21:39:00 +01:00
Stephen Dolan 58a9b0cfe1 Make the test harness not segfault when a test fails (!) 2012-09-04 20:56:44 +01:00
Stephen Dolan da5d653de8 Add a Boolean "not" operator.
Not wholly convinced this is a good idea, maybe "not" should be a
builtin function instead? 'not (.a == .b)' vs. '.a == .b | not'
2012-09-04 20:43:40 +01:00
Stephen Dolan 6e6ea50763 Short-circuiting Boolean "and" and "or" operators. 2012-09-04 20:38:59 +01:00
Stephen Dolan c5ab3b2336 Move some unicode handling stuff to a separate file. 2012-09-04 20:38:47 +01:00
Stephen Dolan 328c4a13f1 Add "elif" to if-then-else constructs. 2012-09-04 16:05:24 +01:00
Stephen Dolan 5add2c2ceb if-then-else and defined-or operators 2012-09-04 15:34:34 +01:00
Stephen Dolan 520c7bb15e Fix some confusion between "null" and "invalid". 2012-09-03 17:26:52 +01:00
Stephen Dolan 32e324a283 Fix a memory leak in JV and add more tests 2012-09-03 17:18:10 +01:00
Stephen Dolan eea05ff924 Perl-style autovivification.
.foo.bar = 1 will cause objects to be created if they don't exist.
2012-09-03 16:50:38 +01:00
Stephen Dolan c9dbe1aff8 squash a debug printf 2012-09-03 16:42:58 +01:00
Stephen Dolan a8c3648ca5 JV_KIND_INVALID values to represent failed lookups, etc + various tests. 2012-09-03 16:16:14 +01:00
Stephen Dolan d895d39ba9 Support "null" in JQ programs 2012-09-03 16:14:52 +01:00
Stephen Dolan 8fe9c8a22a Make assignment work again: = and |= operators. 2012-09-03 15:33:59 +01:00
Stephen Dolan d8fad1ed9b Disallow a + before numbers (makes parsing easier, agrees with JSON) 2012-09-03 15:32:50 +01:00
Stephen Dolan cf004c2b3a Modify number formatting so that 1e-3 renders as 0.001, not .001 2012-09-03 13:57:53 +01:00
Stephen Dolan 6041fd7661 Literal strings and better literal numbers. 2012-09-03 13:57:47 +01:00
Stephen Dolan 52487ff812 Remove globals from parser, use explicit structure. 2012-09-03 01:12:42 +01:00
Stephen Dolan 22ffc0fdfc Mark a constant array const in jvp_dtoa.c 2012-09-02 22:24:27 +01:00
Stephen Dolan e3710c0d1f Fix behaviour of stack at program termination. 2012-09-02 22:11:06 +01:00
Stephen Dolan 4f747e6fff Move from Jansson to JV - proper freeing of memory
A few more tests, now passes valgrind.
2012-09-02 21:45:52 +01:00
Stephen Dolan 9da3e0d373 Clean up build a little and add .gitignore. 2012-09-02 17:24:17 +01:00
Stephen Dolan f72c5ffac0 Move from Jansson to JV - interpreter loop
Passes tests, but leaks some memory.
2012-09-02 17:20:13 +01:00
Stephen Dolan 2cb9a6e61d Move from Jansson to JV - everything but the interpreter loop
Passes valgrind --leak-check=full.
2012-09-02 16:31:59 +01:00
Stephen Dolan 22f8aed31e Validation for empty objects
Now (correctly) fails to parse {,}
2012-09-02 00:24:23 +01:00
Stephen Dolan e98624e5a9 First pass at a JSON parser 2012-09-01 19:16:43 +01:00
Stephen Dolan 92654f8204 Fix a memory leak when inserting into an object with an already-present key 2012-09-01 18:24:17 +01:00
Stephen Dolan 66fd702363 Minor changes to dtoa.c to make it easier to work with.
- Change memory allocation to thread a context structure through
   the code rather than using globals (which would require a
   -lpthread and some locking to become threadsafe).

 - Remove a few configuration options (the "private memory" space,
   and K&R style headers).

 - Prefix exported symbols with jvp_ to prevent libc conflicts.

 - Add jvp_dtoa.h

 - Include netlib's g_fmt.c into dtoa.c

 - Add a few not-strictly-necessary initialisations in order to
   compile cleanly with gcc -Wall
2012-09-01 18:01:35 +01:00
Stephen Dolan 9e544c55dc Import a copy of David Gay's dtoa.c 2012-09-01 16:50:25 +01:00
Stephen Dolan f25cb16e6b Fix a bug in lexical scoping, add tests 2012-08-28 18:38:30 +01:00
Stephen Dolan 463b175cdb Reference-counted copy-on-write JSON library.
Comes with tests and, presumably, bugs.
2012-08-28 18:09:43 +01:00
Stephen Dolan dc6cf2fb58 Remove syntax distinction between builtin and user calls 2012-08-27 11:19:42 +01:00
Stephen Dolan 6c8b55793a first pass at assignment 2012-08-27 10:12:02 +01:00
Stephen Dolan a847d2250f 2nd order functions 2012-08-26 14:25:56 +01:00
Stephen Dolan befb103a12 Fix a revolting hack 2012-08-22 19:21:35 +01:00
Stephen Dolan 2086e59ab1 bugfix for backtracking over RET insns, and a truly evil testcase 2012-08-22 19:05:53 +01:00
Stephen Dolan 79d81f4bee Closures over variables 2012-08-21 18:24:38 +01:00
Stephen Dolan c128c2029e First pass at functions + tests 2012-08-21 18:14:13 +01:00
Stephen Dolan dd0d340eba frame layout refactor 2012-08-21 12:35:44 +01:00
Stephen 15ad640202 Stacks grow down now 2012-08-20 15:53:20 +01:00
Stephen fd738bface an addition operator, of sorts 2012-08-16 01:16:08 +01:00
Stephen 2002dc1a2f Most of a C implementation of jq 2012-08-16 01:00:30 +01:00
88 changed files with 24520 additions and 358 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
.gitattributes export-ignore
.gitignore export-ignore
+51
View File
@@ -0,0 +1,51 @@
*.o
*.a
*.lo
*.la
*.lai
*.so
*.so.*
*~
.*.sw[a-p]
tags
jq
jq.1
# Something delightfully recursive happens otherwise
docs/content/2.download/source/*
# Autogenerated
lexer.c
lexer.h
parser.c
parser.h
# Autotools junk
.libs
.deps
libtool
*.log
stamp-h1
config.log
config.status
autom4te.cache
INSTALL
Makefile
jq-*.tar.gz
configure
aclocal.m4
Makefile.in
version.h
.remake-version-h
config.cache
*.rpm
m4/libtool.m4
m4/ltoptions.m4
m4/ltsugar.m4
m4/ltversion.m4
m4/lt~obsolete.m4
cscope.in.out
cscope.out
cscope.po.out
+17
View File
@@ -0,0 +1,17 @@
language: c
before_script:
- sudo apt-get install -qq valgrind
- bundle install --gemfile=docs/Gemfile
- autoreconf -i
script:
- ./setup.sh
- ./configure && make
compiler:
- gcc
- clang
notifications:
email: false
+7
View File
@@ -0,0 +1,7 @@
Created By:
Stephen Dolan <mu@netsoc.tcd.ie>
Contributions by
Anthony Shortland <anthony.shortland@me.com> - rpmbuild target
Lee Thompson <stagr.lee@gmail.com> - autoconf stuff
Nicolas Williams <nico@cryptonector.com> - library-fication, autoconf stuff, misc
+70
View File
@@ -0,0 +1,70 @@
jq is copyright (C) 2012 Stephen Dolan
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.
jq's documentation (everything found under the docs/ subdirectory in
the source tree) is licensed under the Creative Commons CC BY 3.0
license, which can be found at:
http://creativecommons.org/licenses/by/3.0/
The documentation website includes a copy of Twitter's Boostrap and
relies on Bonsai, Liquid templates and various other projects, look
them up for detailed licensing conditions.
jq incorporates David M. Gay's dtoa.c and g_fmt.c, which bear the
following notices:
dtoa.c:
The author of this software is David M. Gay.
Copyright (c) 1991, 2000, 2001 by Lucent Technologies.
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
g_fmt.c:
The author of this software is David M. Gay.
Copyright (c) 1991, 1996 by Lucent Technologies.
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
-157
View File
@@ -1,157 +0,0 @@
module JQ where
import Text.JSON
import Text.JSON.String
import Data.Maybe
import Data.List (sortBy,sort,groupBy)
import Data.Function (on)
import Data.Ord (comparing)
import Control.Monad
import Control.Monad.Writer
import Control.Monad.List
import Control.Monad.Reader
type Path = [Either Int String]
type Program = JSValue -> [(JSValue, Path)]
type JQ = ReaderT JSValue (WriterT Path [])
runJQ :: JQ a -> JSValue -> [a]
runJQ prog val = map fst $ runWriterT $ runReaderT prog val
(>|) :: JQ JSValue -> JQ a -> JQ a
a >| b = do
val <- a
local (const val) b
collect :: JQ a -> JQ [a]
collect prog = do
arg <- ask
return $ runJQ prog arg
collectPaths :: JQ a -> JQ [(a,Path)]
collectPaths prog = do
arg <- ask
return $ runWriterT $ runReaderT prog arg
insert :: JSValue -> (JSValue, Path) -> JSValue
insert base (replace, []) = replace
insert (JSArray values) (replace, ((Left n):rest)) = JSArray values'
where
(left, (_:right)) = splitAt n values
values' = left ++ [replace] ++ right
insert (JSObject obj) (replace, ((Right k):rest))= JSObject $ toJSObject obj'
where
withoutK = filter ((/= k) . fst) $ fromJSObject obj
obj' = (k, replace):withoutK
eqj a b = JSBool $ a == b
liftp :: (JSValue -> JSValue) -> JQ JSValue
liftp f = liftM f ask
idp = undefined
failp t = []
constp :: JSValue -> Program
constp t t' = idp t
anyj :: [JSValue] -> Bool
anyj values = any isTrue values
where
isTrue (JSBool False) = False
isTrue (JSNull) = False
isTrue _ = True
selectp prog = do
match <- collect prog
guard $ anyj match
ask
constStr :: String -> JQ JSValue
constStr = return . JSString . toJSString
constInt :: Int -> JQ JSValue
constInt = return . JSRational False . toRational
updatep p = do
t <- ask
liftM (foldl insert t) $ collectPaths p
arrayp prog = liftM JSArray $ collect prog
childp' :: JSValue -> JQ JSValue
childp' (JSArray values) = msum [tell [Left i] >> return v | (v,i) <- zip values [0..]]
childp' (JSObject obj) = msum [tell [Right k] >> return v | (k,v) <- fromJSObject obj]
childp' _ = mzero
childp = ask >>= childp'
--findp :: Program -> Program
findp prog = do
found <- collect prog
if anyj found then ask else childp >| findp prog
groupp prog = do
list <- ask
case list of
JSArray values -> do
marked <- forM values $ \v -> do
m <- collect (return v >| prog)
return (m,v)
msum $
map (return . JSArray . map snd) $
groupBy ((==) `on` fst) $
sortBy (comparing fst) $
marked
_ -> return JSNull
withArray f (JSArray values) = JSArray $ f values
withArray f x = x
callp "select" [p] = selectp p
callp "find" [p] = findp p
callp "set" [p] = updatep p
callp "sort" [] = liftp (withArray sort)
callp "group" [p] = groupp p
lookupj :: JSValue -> JSValue -> JQ JSValue
lookupj (JSArray values) (JSRational _ n) = do
let idx = round n
tell [Left idx]
if idx >= 0 && idx < length values
then return $ values !! idx
else return $ JSNull
lookupj (JSObject obj) (JSString s) = do
tell [Right (fromJSString s)]
case (lookup (fromJSString s) (fromJSObject obj)) of
Just x -> return x
Nothing -> return JSNull
lookupj _ _ = mzero
plusj (JSRational _ n1) (JSRational _ n2) = JSRational True (n1 + n2)
plusj (JSString s1) (JSString s2) = JSString $ toJSString (fromJSString s1 ++ fromJSString s2)
plusj (JSArray a1) (JSArray a2) = JSArray $ a1 ++ a2
js :: JSON a => a -> JSValue
js = showJSON
index s = do
v <- ask
lookupj v (js s)
dictp progs = do
liftM (JSObject . toJSObject) $ forM progs $ \(k,v) -> do
JSString k' <- k
v' <- v
return (fromJSString k', v')
-101
View File
@@ -1,101 +0,0 @@
{
module Lexer where
import Control.Monad.Error
}
%wrapper "monadUserState"
$digit = 0-9
$alpha = [a-zA-Z_]
@reserved = "."|"["|"]"|","|":"|"("|")"|"{"|"}"|"|"|"=="|"+"
@ident = $alpha [$alpha $digit]*
@string = \" ($printable)* \"
tokens :-
<0> $white+ ;
<0> @reserved { tok TRes }
<0> @ident { tok TIdent }
<0> $digit+ { tok $ TInt . read }
<0> \" { enterString }
<string> \" { leaveString }
<string> ($printable # [\"\\]) { pushString id }
<string> \\ [\"\\\/] { pushString (drop 1) }
<string> \\ [nrt] { pushString (escape . drop 1) }
--<string> \\ 'u' [0-9a-fA-F]{4}
-- { pushString (parseUnicode . drop 2) }
-- @string { \s -> TString $ init $ tail s}
{
escape :: String -> String
escape "r" = "\r"
escape "n" = "\n"
escape "t" = "\t"
getState :: Alex AlexState
getState = Alex $ \s -> Right (s, s)
getUserState :: Alex AlexUserState
getUserState = liftM alex_ust getState
setUserState :: AlexUserState -> Alex ()
setUserState s' = Alex $ \s -> Right (s{alex_ust = s'}, ())
alexEOF = return $ Nothing
enterString input len = do
setUserState []
alexSetStartCode string
skip input len
pushString f i@(p, _, s) len = do
buf <- getUserState
setUserState (buf ++ [f $ take len s])
skip i len
leaveString input len = do
s <- getUserState
alexSetStartCode 0
return $ Just $ TString $ concat s
tok f (p,_,s) len = return $ Just $ f (take len s)
data Token = TRes String | TString String | TIdent String | TInt Int
instance Show Token where
show (TRes t) = "token " ++ t
show (TString t) = "string " ++ t
show (TIdent t) = "identifier " ++ t
show (TInt t) = "integer " ++ show t
type AlexUserState = [String]
alexInitUserState = undefined
wrapError (Alex scanner) = Alex $ \s -> case scanner s of
Left message -> Left (message ++ " at " ++ showpos (alex_pos s))
where
showpos (AlexPn off line col) = "line " ++ show line ++ ", column " ++ show col
x -> x
scanner = do
tok <- wrapError alexMonadScan
case tok of
Nothing -> do
s <- getState
case alex_scd s of
0 -> return []
string -> alexError "Unterminated string literal"
Just tok -> liftM (tok:) scanner
runLexer :: String -> Either String [Token]
runLexer input = runAlex input scanner
}
-22
View File
@@ -1,22 +0,0 @@
import Parser
import Lexer
import JQ
import Text.JSON
import Text.JSON.String
import System.Environment
import Control.Monad
import System.IO
parseJS :: String -> JSValue
parseJS s = case runGetJSON readJSValue s of
Left err -> error err
Right val -> val
main = do
[program] <- getArgs
json <- liftM parseJS $ hGetContents stdin
case runLexer program >>= runParser of
Left err -> putStrLn err
Right program -> mapM_ (putStrLn . encode) (runJQ program json)
+144
View File
@@ -0,0 +1,144 @@
### C source files to be built and distributed.
LIBJQ_INCS = jq_parser.h builtin.h bytecode.h compile.h exec_stack.h \
libm.h jv_alloc.h jv_dtoa.h jv_unicode.h locfile.h \
opcode_list.h parser.y jv_utf8_tables.h lexer.l
LIBJQ_SRC = locfile.c bytecode.c compile.c execute.c builtin.c jv.c \
jv_parse.c jv_print.c jv_dtoa.c jv_unicode.c jv_aux.c jv_file.c \
jv_alloc.c jq_test.c ${LIBJQ_INCS}
### C build options
AM_CFLAGS = -Wextra -Wall -Wno-missing-field-initializers \
-Wno-unused-parameter -Wno-unused-function
ACLOCAL_AMFLAGS = -I config/m4
### Generating the lexer and parser
# While there is some autoconf macro support for lex/flex, it doesn't support
# header file creation so we'll use good old make
BUILT_SOURCES = lexer.h lexer.c parser.h parser.c version.h
lexer.c: lexer.l
$(AM_V_LEX) flex -o lexer.c --header-file=lexer.h $<
lexer.h: lexer.c
# Tell YACC (bison) autoconf macros that you want a header file created.
# If the --warnings=all fails, you probably have an old version of bison
# OSX ships an old bison, so update with homebrew or macports
AM_YFLAGS = --warnings=all -d
### libjq
lib_LTLIBRARIES = libjq.la
libjq_la_SOURCES = ${LIBJQ_SRC}
libjq_la_LIBADD = -lm
libjq_la_LDFLAGS = -export-symbols-regex '^j[qv]_' -version-info 1:4:0
include_HEADERS = jv.h jq.h
### Building the jq binary
# Remake the version.h header file if, and only if, the git ID has changed
.PHONY: .FORCE
.FORCE:
generate_ver = ver="`{ $(srcdir)/scripts/version || echo '$(VERSION)' ; } | sed 's/.*/\#define JQ_VERSION \"&\"/'`"
.remake-version-h: .FORCE
@ $(generate_ver); test "x`cat version.h 2>/dev/null`" = "x$$ver" || touch .remake-version-h
version.h: .remake-version-h
$(AM_V_GEN) $(generate_ver); echo "$$ver" > $@
bin_PROGRAMS = jq
jq_SOURCES = main.c
jq_LDFLAGS = -static-libtool-libs
jq_LDADD = libjq.la -lm
### Tests (make check)
TESTS = tests/all.test
TEST_LOG_COMPILER = ${srcdir}/tests/run
### Building the manpage
# If ENABLE_DOCS is not set, just copy jq.1.default to jq.1
# The real_docs target succeeds (possibly after building jq.1) only if ENABLE_DOCS is set
# Thus, making "dist" depend on "real_docs" ensures that we never build a tarball with
# a stub manpage.
man_MANS = jq.1
.PHONY: real_docs
if ENABLE_DOCS
jq.1: $(srcdir)/docs/content/3.manual/manual.yml
$(AM_V_GEN) ( cd ${abs_srcdir}/docs; '$(BUNDLER)' exec rake manpage ) > $@ || { rm -f $@; false; }
jq.1.default: $(srcdir)/docs/default_manpage.md
$(AM_V_GEN) ( cd ${abs_srcdir}/docs; '$(BUNDLER)' exec rake manpage_default ) > $@ || { rm -f $@; false; }
real_docs: jq.1
if cmp jq.1 $(srcdir)/jq.1.default > /dev/null; then\
rm -f jq.1; $(MAKE) $(AM_MAKEFLAGS) jq.1;\
fi
else
jq.1: $(srcdir)/jq.1.default
$(AM_V_GEN) cp $^ $@
real_docs:
@echo "Ruby dependencies not found, cannot build manpage." > /dev/stderr
@echo "Follow the instructions in docs/README.md to install them" > /dev/stderr
@echo "and then rerun ./configure" > /dev/stderr
false
endif
### Packaging
docs/site.yml: configure.ac
sed 's/^jq_version: .*/jq_version: "$(VERSION)"/' $@ > $@.new
mv $@.new $@
install-binaries: $(BUILT_SOURCES)
$(MAKE) $(AM_MAKEFLAGS) install-exec
# Ensure "make dist" fails when we can't build the real manpage
dist-hook: real_docs
DOC_FILES = docs/content docs/public docs/templates docs/site.yml \
docs/Gemfile docs/Gemfile.lock docs/Rakefile docs/README.md \
docs/default_manpage.md jq.1.default
# setup is only used by distribution developers, not package developers.
# Still, as a matter of allowing patching, its not a bad idea to distribute
# the developer setup script in the tarball.
EXTRA_DIST = $(man_MANS) $(TESTS) $(TEST_LOG_COMPILER) jq.spec \
$(DOC_FILES) scripts/version parser.h parser.c lexer.h \
lexer.c
# README.md is expected in Github projects, good stuff in it, so we'll
# distribute it and install it with the package in the doc directory.
docdir = ${datadir}/doc/${PACKAGE}
dist_doc_DATA = README.md COPYING AUTHORS README
RELEASE ?= 1
rpm: dist jq.spec
@echo "Packaging jq as an RPM ..."
mkdir -p rpm/SOURCES rpm/BUILD rpm/BUILDROOT rpm/RPMS rpm/SPECS
cp jq-$(VERSION).tar.gz rpm/SOURCES/
rpmbuild -tb --define "_topdir ${PWD}/rpm" --define "_prefix /usr" --define "myver $(VERSION)" --define "myrel ${RELEASE}" rpm/SOURCES/jq-$(VERSION).tar.gz
find rpm/RPMS/ -name "*.rpm" -exec mv {} ./ \;
rm -rf rpm
dist-clean-local:
rm -f ${BUILT_SOURCES}
# Not sure why this doesn't get cleaned up automatically, guess
# automake used to man pages which are hand coded?
# 'make clean' doesn't delete the manpage if it can't be rebuilt
.PHONY: clean-local-docs
clean-local-docs:
if ENABLE_DOCS
rm -f jq.1
endif
clean-local: clean-local-docs
rm -f version.h .remake-version-h
-78
View File
@@ -1,78 +0,0 @@
{
module Parser where
import Lexer
import JQ
import Text.JSON
import Debug.Trace
import Data.List
import Control.Monad.Error
import Control.Monad.Reader
}
%name runParser Exp
%tokentype { Token }
%monad { Either String }
%error { \t -> fail $ "parse error: unexpected " ++ if null t then [] else (show $ head t) }
%token
'|' { TRes "|" }
'.' { TRes "." }
'[' { TRes "[" }
']' { TRes "]" }
'{' { TRes "{" }
'}' { TRes "}" }
'(' { TRes "(" }
')' { TRes ")" }
',' { TRes "," }
':' { TRes ":" }
'==' { TRes "==" }
'+' { TRes "+" }
Ident { TIdent $$ }
String { TString $$ }
Int { TInt $$ }
%left '|'
%left ','
%nonassoc '=='
%left '+'
%%
Exp
: Exp '|' Exp { $1 >| $3 }
| Exp ',' Exp { $1 `mplus` $3 }
| Exp '==' Exp { liftM2 eqj $1 $3 }
| Exp '+' Exp { liftM2 plusj $1 $3 }
| Term { $1 }
ExpD
: ExpD '|' ExpD { $1 >| $3 }
| ExpD '==' ExpD { liftM2 eqj $1 $3 }
| Term { $1 }
Term
: '.' { ask }
| Term '.' Ident { $1 >| index $3 }
| '.' Ident { index $2 }
| String { constStr $1 }
| Term '[' Exp ']' { do {t <- $1; i <- $3; lookupj t i} }
| Term '[' ']' { $1 >| childp }
| '(' Exp ')' { $2 }
| '[' Exp ']' { arrayp $2 }
| Int { constInt $1 }
| '{' MkDict '}' { dictp $2 }
| Ident '(' Exp ')' { callp $1 [$3] }
| Ident { callp $1 [] }
MkDict
: { [] }
| MkDictPair { [$1] }
| MkDictPair ',' MkDict { $1:$3 }
MkDictPair
: Ident ':' ExpD { (constStr $1, $3) }
| Ident { (constStr $1, index $1) }
| String ':' ExpD { (constStr $1, $3) }
| '(' Exp ')' ':' ExpD{ ($2, $5) }
Symlink
+1
View File
@@ -0,0 +1 @@
README.md
+35
View File
@@ -0,0 +1,35 @@
jq
==
jq is a command-line JSON processor.
If you want to learn to use jq, read the documentation at
[http://stedolan.github.io/jq](http://stedolan.github.io/jq). This
documentation is generated from the docs/ folder of this repository.
You can also try it online at [http://jqplay.herokuapp.com](http://jqplay.herokuapp.com).
If you want to hack on jq, feel free, but be warned that its internals
are not well-documented at the moment. Bring a hard hat and a
shovel. Also, read the wiki: http://github.com/stedolan/jq/wiki
If you're building directly from the latest git, you'll need flex and
bison installed. To build, run:
autoreconf -i
./configure
make -j8
make check
After make finishes, you'll be able to use `./jq`. You can also
install it using:
sudo make install
If you're not using the latest git version but instead building a
released tarball (available on the website), then you won't need to
run `autoreconf` (and shouldn't), and you won't need flex or bison.
To cross-compile for OS X and Windows, see docs/Rakefile's build task
and scripts/crosscompile. You'll need a cross-compilation environment,
such as Mingw for cross-compiling for Windows.
+1
View File
@@ -0,0 +1 @@
*
+752
View File
@@ -0,0 +1,752 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "builtin.h"
#include "compile.h"
#include "jq_parser.h"
#include "bytecode.h"
#include "locfile.h"
#include "jv_unicode.h"
static jv type_error(jv bad, const char* msg) {
jv err = jv_invalid_with_msg(jv_string_fmt("%s %s",
jv_kind_name(jv_get_kind(bad)),
msg));
jv_free(bad);
return err;
}
static jv type_error2(jv bad1, jv bad2, const char* msg) {
jv err = jv_invalid_with_msg(jv_string_fmt("%s and %s %s",
jv_kind_name(jv_get_kind(bad1)),
jv_kind_name(jv_get_kind(bad2)),
msg));
jv_free(bad1);
jv_free(bad2);
return err;
}
static jv f_plus(jv input, jv a, jv b) {
jv_free(input);
if (jv_get_kind(a) == JV_KIND_NULL) {
jv_free(a);
return b;
} else if (jv_get_kind(b) == JV_KIND_NULL) {
jv_free(b);
return a;
} else if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
return jv_number(jv_number_value(a) +
jv_number_value(b));
} else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) {
return jv_string_concat(a, b);
} else if (jv_get_kind(a) == JV_KIND_ARRAY && jv_get_kind(b) == JV_KIND_ARRAY) {
return jv_array_concat(a, b);
} else if (jv_get_kind(a) == JV_KIND_OBJECT && jv_get_kind(b) == JV_KIND_OBJECT) {
return jv_object_merge(a, b);
} else {
return type_error2(a, b, "cannot be added");
}
}
#define LIBM_DD(name) \
static jv f_ ## name(jv input) { \
if (jv_get_kind(input) != JV_KIND_NUMBER) { \
return type_error(input, "number required"); \
} \
jv ret = jv_number(name(jv_number_value(input))); \
jv_free(input); \
return ret; \
}
#include "libm.h"
#undef LIBM_DD
static jv f_negate(jv input) {
if (jv_get_kind(input) != JV_KIND_NUMBER) {
return type_error(input, "cannot be negated");
}
jv ret = jv_number(-jv_number_value(input));
jv_free(input);
return ret;
}
static jv f_startswith(jv a, jv b) {
int alen = jv_string_length_bytes(jv_copy(a));
int blen = jv_string_length_bytes(jv_copy(b));
jv ret;
if (blen <= alen && memcmp(jv_string_value(a), jv_string_value(b), blen) == 0)
ret = jv_true();
else
ret = jv_false();
jv_free(a);
jv_free(b);
return ret;
}
static jv f_endswith(jv a, jv b) {
const char *astr = jv_string_value(a);
const char *bstr = jv_string_value(b);
size_t alen = jv_string_length_bytes(jv_copy(a));
size_t blen = jv_string_length_bytes(jv_copy(b));
jv ret;;
if (alen < blen ||
memcmp(astr + (alen - blen), bstr, blen) != 0)
ret = jv_false();
else
ret = jv_true();
jv_free(a);
jv_free(b);
return ret;
}
static jv f_ltrimstr(jv input, jv left) {
if (jv_get_kind(f_startswith(jv_copy(input), jv_copy(left))) != JV_KIND_TRUE) {
jv_free(left);
return input;
}
/*
* FIXME It'd be better to share the suffix with the original input --
* that we could do, we just can't share prefixes.
*/
int prefixlen = jv_string_length_bytes(left);
jv res = jv_string_sized(jv_string_value(input) + prefixlen,
jv_string_length_bytes(jv_copy(input)) - prefixlen);
jv_free(input);
return res;
}
static jv f_rtrimstr(jv input, jv right) {
if (jv_get_kind(f_endswith(jv_copy(input), jv_copy(right))) == JV_KIND_TRUE) {
jv res = jv_string_sized(jv_string_value(input),
jv_string_length_bytes(jv_copy(input)) - jv_string_length_bytes(right));
jv_free(input);
return res;
}
jv_free(right);
return input;
}
static jv f_minus(jv input, jv a, jv b) {
jv_free(input);
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
return jv_number(jv_number_value(a) - jv_number_value(b));
} else if (jv_get_kind(a) == JV_KIND_ARRAY && jv_get_kind(b) == JV_KIND_ARRAY) {
jv out = jv_array();
jv_array_foreach(a, i, x) {
int include = 1;
jv_array_foreach(b, j, y) {
if (jv_equal(jv_copy(x), y)) {
include = 0;
break;
}
}
if (include)
out = jv_array_append(out, jv_copy(x));
jv_free(x);
}
jv_free(a);
jv_free(b);
return out;
} else {
return type_error2(a, b, "cannot be subtracted");
}
}
static jv f_multiply(jv input, jv a, jv b) {
jv_kind ak = jv_get_kind(a);
jv_kind bk = jv_get_kind(b);
jv_free(input);
if (ak == JV_KIND_NUMBER && bk == JV_KIND_NUMBER) {
return jv_number(jv_number_value(a) * jv_number_value(b));
} else if ((ak == JV_KIND_STRING && bk == JV_KIND_NUMBER) ||
(ak == JV_KIND_NUMBER && bk == JV_KIND_STRING)) {
jv str = a;
jv num = b;
if (ak == JV_KIND_NUMBER) {
str = b;
num = a;
}
int n;
size_t alen = jv_string_length_bytes(jv_copy(str));
jv res = str;
for (n = jv_number_value(num) - 1; n > 0; n--)
res = jv_string_append_buf(res, jv_string_value(str), alen);
jv_free(num);
if (n < 0) {
jv_free(str);
return jv_null();
}
return res;
} else if (ak == JV_KIND_OBJECT && bk == JV_KIND_OBJECT) {
return jv_object_merge_recursive(a, b);
} else {
return type_error2(a, b, "cannot be multiplied");
}
}
static jv f_divide(jv input, jv a, jv b) {
jv_free(input);
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
return jv_number(jv_number_value(a) / jv_number_value(b));
} else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) {
return jv_string_split(a, b);
} else {
return type_error2(a, b, "cannot be divided");
}
}
static jv f_mod(jv input, jv a, jv b) {
jv_free(input);
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
return jv_number((intmax_t)jv_number_value(a) % (intmax_t)jv_number_value(b));
} else {
return type_error2(a, b, "cannot be divided");
}
}
static jv f_equal(jv input, jv a, jv b) {
jv_free(input);
return jv_bool(jv_equal(a, b));
}
static jv f_notequal(jv input, jv a, jv b) {
jv_free(input);
return jv_bool(!jv_equal(a, b));
}
enum cmp_op {
CMP_OP_LESS,
CMP_OP_GREATER,
CMP_OP_LESSEQ,
CMP_OP_GREATEREQ
};
static jv order_cmp(jv input, jv a, jv b, enum cmp_op op) {
jv_free(input);
int r = jv_cmp(a, b);
return jv_bool((op == CMP_OP_LESS && r < 0) ||
(op == CMP_OP_LESSEQ && r <= 0) ||
(op == CMP_OP_GREATEREQ && r >= 0) ||
(op == CMP_OP_GREATER && r > 0));
}
static jv f_less(jv input, jv a, jv b) {
return order_cmp(input, a, b, CMP_OP_LESS);
}
static jv f_greater(jv input, jv a, jv b) {
return order_cmp(input, a, b, CMP_OP_GREATER);
}
static jv f_lesseq(jv input, jv a, jv b) {
return order_cmp(input, a, b, CMP_OP_LESSEQ);
}
static jv f_greatereq(jv input, jv a, jv b) {
return order_cmp(input, a, b, CMP_OP_GREATEREQ);
}
static jv f_contains(jv a, jv b) {
if (jv_get_kind(a) == jv_get_kind(b)) {
return jv_bool(jv_contains(a, b));
} else {
return type_error2(a, b, "cannot have their containment checked");
}
}
static jv f_dump(jv input) {
return jv_dump_string(input, 0);
}
static jv f_json_parse(jv input) {
if (jv_get_kind(input) != JV_KIND_STRING)
return type_error(input, "only strings can be parsed");
jv res = jv_parse_sized(jv_string_value(input),
jv_string_length_bytes(jv_copy(input)));
jv_free(input);
return res;
}
static jv f_tonumber(jv input) {
if (jv_get_kind(input) == JV_KIND_NUMBER) {
return input;
}
if (jv_get_kind(input) == JV_KIND_STRING) {
jv parsed = jv_parse(jv_string_value(input));
if (!jv_is_valid(parsed) || jv_get_kind(parsed) == JV_KIND_NUMBER) {
jv_free(input);
return parsed;
}
}
return type_error(input, "cannot be parsed as a number");
}
static jv f_length(jv input) {
if (jv_get_kind(input) == JV_KIND_ARRAY) {
return jv_number(jv_array_length(input));
} else if (jv_get_kind(input) == JV_KIND_OBJECT) {
return jv_number(jv_object_length(input));
} else if (jv_get_kind(input) == JV_KIND_STRING) {
return jv_number(jv_string_length_codepoints(input));
} else if (jv_get_kind(input) == JV_KIND_NUMBER) {
return jv_number(fabs(jv_number_value(input)));
} else if (jv_get_kind(input) == JV_KIND_NULL) {
jv_free(input);
return jv_number(0);
} else {
return type_error(input, "has no length");
}
}
static jv f_tostring(jv input) {
if (jv_get_kind(input) == JV_KIND_STRING) {
return input;
} else {
return jv_dump_string(input, 0);
}
}
#define CHARS_ALPHANUM "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
static jv escape_string(jv input, const char* escapings) {
assert(jv_get_kind(input) == JV_KIND_STRING);
const char* lookup[128] = {0};
const char* p = escapings;
while (*p) {
lookup[(int)*p] = p+1;
p++;
p += strlen(p);
p++;
}
jv ret = jv_string("");
const char* i = jv_string_value(input);
const char* end = i + jv_string_length_bytes(jv_copy(input));
const char* cstart;
int c = 0;
while ((i = jvp_utf8_next((cstart = i), end, &c))) {
assert(c != -1);
if (c < 128 && lookup[c]) {
ret = jv_string_append_str(ret, lookup[c]);
} else {
ret = jv_string_append_buf(ret, cstart, i - cstart);
}
}
jv_free(input);
return ret;
}
static jv f_format(jv input, jv fmt) {
if (jv_get_kind(fmt) != JV_KIND_STRING) {
jv_free(input);
return type_error(fmt, "is not a valid format");
}
const char* fmt_s = jv_string_value(fmt);
if (!strcmp(fmt_s, "json")) {
jv_free(fmt);
return jv_dump_string(input, 0);
} else if (!strcmp(fmt_s, "text")) {
jv_free(fmt);
return f_tostring(input);
} else if (!strcmp(fmt_s, "csv")) {
jv_free(fmt);
if (jv_get_kind(input) != JV_KIND_ARRAY)
return type_error(input, "cannot be csv-formatted, only array");
jv line = jv_string("");
jv_array_foreach(input, i, x) {
if (i) line = jv_string_append_str(line, ",");
switch (jv_get_kind(x)) {
case JV_KIND_NULL:
/* null rendered as empty string */
jv_free(x);
break;
case JV_KIND_TRUE:
case JV_KIND_FALSE:
line = jv_string_concat(line, jv_dump_string(x, 0));
break;
case JV_KIND_NUMBER:
if (jv_number_value(x) != jv_number_value(x)) {
/* NaN, render as empty string */
jv_free(x);
} else {
line = jv_string_concat(line, jv_dump_string(x, 0));
}
break;
case JV_KIND_STRING: {
line = jv_string_append_str(line, "\"");
line = jv_string_concat(line, escape_string(x, "\"\"\"\0"));
line = jv_string_append_str(line, "\"");
break;
}
default:
jv_free(input);
jv_free(line);
return type_error(x, "is not valid in a csv row");
}
}
jv_free(input);
return line;
} else if (!strcmp(fmt_s, "html")) {
jv_free(fmt);
return escape_string(f_tostring(input), "&&amp;\0<&lt;\0>&gt;\0'&apos;\0\"&quot;\0");
} else if (!strcmp(fmt_s, "uri")) {
jv_free(fmt);
input = f_tostring(input);
int unreserved[128] = {0};
const char* p = CHARS_ALPHANUM "-_.!~*'()";
while (*p) unreserved[(int)*p++] = 1;
jv line = jv_string("");
const char* s = jv_string_value(input);
for (int i=0; i<jv_string_length_bytes(jv_copy(input)); i++) {
unsigned ch = (unsigned)(unsigned char)*s;
if (ch < 128 && unreserved[ch]) {
line = jv_string_append_buf(line, s, 1);
} else {
line = jv_string_concat(line, jv_string_fmt("%%%02x", ch));
}
s++;
}
jv_free(input);
return line;
} else if (!strcmp(fmt_s, "sh")) {
jv_free(fmt);
if (jv_get_kind(input) != JV_KIND_ARRAY)
input = jv_array_set(jv_array(), 0, input);
jv line = jv_string("");
jv_array_foreach(input, i, x) {
if (i) line = jv_string_append_str(line, " ");
switch (jv_get_kind(x)) {
case JV_KIND_NULL:
case JV_KIND_TRUE:
case JV_KIND_FALSE:
case JV_KIND_NUMBER:
line = jv_string_concat(line, jv_dump_string(x, 0));
break;
case JV_KIND_STRING: {
line = jv_string_append_str(line, "'");
line = jv_string_concat(line, escape_string(x, "''\\''\0"));
line = jv_string_append_str(line, "'");
break;
}
default:
jv_free(input);
jv_free(line);
return type_error(x, "can not be escaped for shell");
}
}
jv_free(input);
return line;
} else if (!strcmp(fmt_s, "base64")) {
jv_free(fmt);
input = f_tostring(input);
jv line = jv_string("");
const char b64[64 + 1] = CHARS_ALPHANUM "+/";
const char* data = jv_string_value(input);
int len = jv_string_length_bytes(jv_copy(input));
for (int i=0; i<len; i+=3) {
uint32_t code = 0;
int n = len - i >= 3 ? 3 : len-i;
for (int j=0; j<3; j++) {
code <<= 8;
code |= j < n ? (unsigned)data[i+j] : 0;
}
char buf[4];
for (int j=0; j<4; j++) {
buf[j] = b64[(code >> (18 - j*6)) & 0x3f];
}
if (n < 3) buf[3] = '=';
if (n < 2) buf[2] = '=';
line = jv_string_append_buf(line, buf, sizeof(buf));
}
jv_free(input);
return line;
} else {
jv_free(input);
return jv_invalid_with_msg(jv_string_concat(fmt, jv_string(" is not a valid format")));
}
}
static jv f_keys(jv input) {
if (jv_get_kind(input) == JV_KIND_OBJECT || jv_get_kind(input) == JV_KIND_ARRAY) {
return jv_keys(input);
} else {
return type_error(input, "has no keys");
}
}
static jv f_sort(jv input){
if (jv_get_kind(input) == JV_KIND_ARRAY) {
return jv_sort(input, jv_copy(input));
} else {
return type_error(input, "cannot be sorted, as it is not an array");
}
}
static jv f_sort_by_impl(jv input, jv keys) {
if (jv_get_kind(input) == JV_KIND_ARRAY &&
jv_get_kind(keys) == JV_KIND_ARRAY &&
jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) {
return jv_sort(input, keys);
} else {
return type_error2(input, keys, "cannot be sorted, as they are not both arrays");
}
}
static jv f_group_by_impl(jv input, jv keys) {
if (jv_get_kind(input) == JV_KIND_ARRAY &&
jv_get_kind(keys) == JV_KIND_ARRAY &&
jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) {
return jv_group(input, keys);
} else {
return type_error2(input, keys, "cannot be sorted, as they are not both arrays");
}
}
static jv minmax_by(jv values, jv keys, int is_min) {
if (jv_get_kind(values) != JV_KIND_ARRAY)
return type_error2(values, keys, "cannot be iterated over");
if (jv_get_kind(keys) != JV_KIND_ARRAY)
return type_error2(values, keys, "cannot be iterated over");
if (jv_array_length(jv_copy(values)) != jv_array_length(jv_copy(keys)))
return type_error2(values, keys, "have wrong length");
if (jv_array_length(jv_copy(values)) == 0) {
jv_free(values);
jv_free(keys);
return jv_null();
}
jv ret = jv_array_get(jv_copy(values), 0);
jv retkey = jv_array_get(jv_copy(keys), 0);
for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
jv item = jv_array_get(jv_copy(keys), i);
int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
if ((cmp < 0) == (is_min == 1)) {
jv_free(retkey);
retkey = item;
jv_free(ret);
ret = jv_array_get(jv_copy(values), i);
} else {
jv_free(item);
}
}
jv_free(values);
jv_free(keys);
jv_free(retkey);
return ret;
}
static jv f_min(jv x) {
return minmax_by(x, jv_copy(x), 1);
}
static jv f_max(jv x) {
return minmax_by(x, jv_copy(x), 0);
}
static jv f_min_by_impl(jv x, jv y) {
return minmax_by(x, y, 1);
}
static jv f_max_by_impl(jv x, jv y) {
return minmax_by(x, y, 0);
}
static jv f_type(jv input) {
jv out = jv_string(jv_kind_name(jv_get_kind(input)));
jv_free(input);
return out;
}
static jv f_error(jv input, jv msg) {
jv_free(input);
msg = f_tostring(msg);
return jv_invalid_with_msg(msg);
}
#define LIBM_DD(name) \
{(cfunction_ptr)f_ ## name, "_" #name, 1},
static const struct cfunction function_list[] = {
#include "libm.h"
{(cfunction_ptr)f_plus, "_plus", 3},
{(cfunction_ptr)f_negate, "_negate", 1},
{(cfunction_ptr)f_minus, "_minus", 3},
{(cfunction_ptr)f_multiply, "_multiply", 3},
{(cfunction_ptr)f_divide, "_divide", 3},
{(cfunction_ptr)f_mod, "_mod", 3},
{(cfunction_ptr)f_dump, "tojson", 1},
{(cfunction_ptr)f_json_parse, "fromjson", 1},
{(cfunction_ptr)f_tonumber, "tonumber", 1},
{(cfunction_ptr)f_tostring, "tostring", 1},
{(cfunction_ptr)f_keys, "keys", 1},
{(cfunction_ptr)f_startswith, "startswith", 2},
{(cfunction_ptr)f_endswith, "endswith", 2},
{(cfunction_ptr)f_ltrimstr, "ltrimstr", 2},
{(cfunction_ptr)f_rtrimstr, "rtrimstr", 2},
{(cfunction_ptr)jv_string_split, "split", 2},
{(cfunction_ptr)jv_string_explode, "explode", 1},
{(cfunction_ptr)jv_string_implode, "implode", 1},
{(cfunction_ptr)jv_setpath, "setpath", 3}, // FIXME typechecking
{(cfunction_ptr)jv_getpath, "getpath", 2},
{(cfunction_ptr)jv_delpaths, "delpaths", 2},
{(cfunction_ptr)jv_has, "has", 2},
{(cfunction_ptr)f_equal, "_equal", 3},
{(cfunction_ptr)f_notequal, "_notequal", 3},
{(cfunction_ptr)f_less, "_less", 3},
{(cfunction_ptr)f_greater, "_greater", 3},
{(cfunction_ptr)f_lesseq, "_lesseq", 3},
{(cfunction_ptr)f_greatereq, "_greatereq", 3},
{(cfunction_ptr)f_contains, "contains", 2},
{(cfunction_ptr)f_length, "length", 1},
{(cfunction_ptr)f_type, "type", 1},
{(cfunction_ptr)f_sort, "sort", 1},
{(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2},
{(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2},
{(cfunction_ptr)f_min, "min", 1},
{(cfunction_ptr)f_max, "max", 1},
{(cfunction_ptr)f_min_by_impl, "_min_by_impl", 2},
{(cfunction_ptr)f_max_by_impl, "_max_by_impl", 2},
{(cfunction_ptr)f_error, "error", 2},
{(cfunction_ptr)f_format, "format", 2},
};
#undef LIBM_DD
struct bytecoded_builtin { const char* name; block code; };
static block bind_bytecoded_builtins(block b) {
block builtins = gen_noop();
{
struct bytecoded_builtin builtin_defs[] = {
{"empty", gen_op_simple(BACKTRACK)},
{"false", gen_const(jv_false())},
{"true", gen_const(jv_true())},
{"null", gen_const(jv_null())},
{"not", gen_condbranch(gen_const(jv_false()),
gen_const(jv_true()))}
};
for (unsigned i=0; i<sizeof(builtin_defs)/sizeof(builtin_defs[0]); i++) {
builtins = BLOCK(builtins, gen_function(builtin_defs[i].name, gen_noop(),
builtin_defs[i].code));
}
}
{
struct bytecoded_builtin builtin_def_1arg[] = {
{"path", BLOCK(gen_op_simple(PATH_BEGIN),
gen_call("arg", gen_noop()),
gen_op_simple(PATH_END))},
};
for (unsigned i=0; i<sizeof(builtin_def_1arg)/sizeof(builtin_def_1arg[0]); i++) {
builtins = BLOCK(builtins, gen_function(builtin_def_1arg[i].name,
gen_param("arg"),
builtin_def_1arg[i].code));
}
}
{
block rangevar = gen_op_var_fresh(STOREV, "rangevar");
block init = BLOCK(gen_op_simple(DUP), gen_call("start", gen_noop()), rangevar);
block range = BLOCK(init,
gen_call("end", gen_noop()),
gen_op_bound(RANGE, rangevar));
builtins = BLOCK(builtins, gen_function("range",
BLOCK(gen_param("start"), gen_param("end")),
range));
}
return block_bind_referenced(builtins, b, OP_IS_CALL_PSEUDO);
}
#define LIBM_DD(name) "def " #name ": _" #name ";",
static const char* const jq_builtins[] = {
"def map(f): [.[] | f];",
"def select(f): if f then . else empty end;",
"def sort_by(f): _sort_by_impl(map([f]));",
"def group_by(f): _group_by_impl(map([f]));",
"def unique: group_by(.) | map(.[0]);",
"def unique_by(f): group_by(f) | map(.[0]);",
"def max_by(f): _max_by_impl(map([f]));",
"def min_by(f): _min_by_impl(map([f]));",
#include "libm.h"
"def add: reduce .[] as $x (null; . + $x);",
"def del(f): delpaths([path(f)]);",
"def _assign(paths; value): value as $v | reduce path(paths) as $p (.; setpath($p; $v));",
"def _modify(paths; update): reduce path(paths) as $p (.; setpath($p; getpath($p) | update));",
"def recurse(f): ., (f | select(. != null) | recurse(f));",
"def recurse_down: recurse(.[]?);",
"def to_entries: [keys[] as $k | {key: $k, value: .[$k]}];",
"def from_entries: map({(.key): .value}) | add;",
"def with_entries(f): to_entries | map(f) | from_entries;",
"def reverse: [.[length - 1 - range(0;length)]];",
"def indices(i): if type == \"array\" and (i|type) == \"array\" then .[i] elif type == \"array\" then .[[i]] else .[i] end;",
"def index(i): if type == \"array\" and (i|type) == \"array\" then .[i] elif type == \"array\" then .[[i]] else .[i] end | .[0];",
"def rindex(i): if type == \"array\" and (i|type) == \"array\" then .[i] elif type == \"array\" then .[[i]] else .[i] end | .[-1:][0];",
"def paths: path(recurse(if (type|. == \"array\" or . == \"object\") then .[] else empty end))|select(length > 0);",
"def leaf_paths: . as $dot|paths|select(. as $p|$dot|getpath($p)|type|. != \"array\" and . != \"object\");",
"def any: reduce .[] as $i (false; . or $i);",
"def all: reduce .[] as $i (true; . and $i);",
"def arrays: select(type == \"array\");",
"def objects: select(type == \"object\");",
"def iterables: arrays, objects;",
"def booleans: select(type == \"boolean\");",
"def numbers: select(type == \"number\");",
"def strings: select(type == \"string\");",
"def nulls: select(type == \"null\");",
"def values: arrays, objects, booleans, numbers, strings;",
"def scalars: select(. == null or . == true or . == false or type == \"number\" or type == \"string\");",
"def join(x): reduce .[] as $i (\"\"; . + (if . == \"\" then $i else x + $i end));",
};
#undef LIBM_DD
static int builtins_bind_one(jq_state *jq, block* bb, const char* code) {
struct locfile src;
locfile_init(&src, jq, code, strlen(code));
block funcs;
int nerrors = jq_parse_library(&src, &funcs);
if (nerrors == 0) {
*bb = block_bind_referenced(funcs, *bb, OP_IS_CALL_PSEUDO);
}
locfile_free(&src);
return nerrors;
}
static int slurp_lib(jq_state *jq, block* bb) {
int nerrors = 0;
char* home = getenv("HOME");
if (home) { // silently ignore no $HOME
jv filename = jv_string_append_str(jv_string(home), "/.jq");
jv data = jv_load_file(jv_string_value(filename), 1);
if (jv_is_valid(data)) {
nerrors = builtins_bind_one(jq, bb, jv_string_value(data) );
}
jv_free(filename);
jv_free(data);
}
return nerrors;
}
int builtins_bind(jq_state *jq, block* bb) {
int nerrors = slurp_lib(jq, bb);
if (nerrors) {
block_free(*bb);
return nerrors;
}
for (int i=(int)(sizeof(jq_builtins)/sizeof(jq_builtins[0]))-1; i>=0; i--) {
nerrors = builtins_bind_one(jq, bb, jq_builtins[i]);
assert(!nerrors);
}
*bb = bind_bytecoded_builtins(*bb);
*bb = gen_cbinding(function_list, sizeof(function_list)/sizeof(function_list[0]), *bb);
return nerrors;
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef BUILTIN_H
#define BUILTIN_H
#include "jq.h"
#include "bytecode.h"
#include "compile.h"
int builtins_bind(jq_state *, block*);
#endif
+160
View File
@@ -0,0 +1,160 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "bytecode.h"
#include "jv_alloc.h"
// flags, length
#define NONE 0, 1
#define CONSTANT OP_HAS_CONSTANT, 2
#define VARIABLE (OP_HAS_VARIABLE | OP_HAS_BINDING), 3
#define BRANCH OP_HAS_BRANCH, 2
#define CFUNC (OP_HAS_CFUNC | OP_HAS_BINDING), 3
#define UFUNC (OP_HAS_UFUNC | OP_HAS_BINDING | OP_IS_CALL_PSEUDO), 4
#define DEFINITION (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 0
#define CLOSURE_REF_IMM (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 2
#define OP(name, imm, in, out) \
{name, #name, imm, in, out},
static const struct opcode_description opcode_descriptions[] = {
#include "opcode_list.h"
};
static const struct opcode_description invalid_opcode_description = {
-1, "#INVALID", 0, 0, 0, 0
};
const struct opcode_description* opcode_describe(opcode op) {
if ((int)op >= 0 && (int)op < NUM_OPCODES) {
return &opcode_descriptions[op];
} else {
return &invalid_opcode_description;
}
}
static int bytecode_operation_length(uint16_t* codeptr) {
int length = opcode_describe(*codeptr)->length;
if (*codeptr == CALL_JQ) {
length += codeptr[1] * 2;
}
return length;
}
static void dump_code(int indent, struct bytecode* bc) {
int pc = 0;
while (pc < bc->codelen) {
printf("%*s", indent, "");
dump_operation(bc, bc->code + pc);
printf("\n");
pc += bytecode_operation_length(bc->code + pc);
}
}
static void symbol_table_free(struct symbol_table* syms) {
jv_mem_free(syms->cfunctions);
jv_free(syms->cfunc_names);
jv_mem_free(syms);
}
void dump_disassembly(int indent, struct bytecode* bc) {
if (bc->nclosures > 0) {
printf("%*s[params: ", indent, "");
jv params = jv_object_get(jv_copy(bc->debuginfo), jv_string("params"));
for (int i=0; i<bc->nclosures; i++) {
if (i) printf(", ");
jv name = jv_array_get(jv_copy(params), i);
printf("%s", jv_string_value(name));
jv_free(name);
}
jv_free(params);
printf("]\n");
}
dump_code(indent, bc);
for (int i=0; i<bc->nsubfunctions; i++) {
struct bytecode* subfn = bc->subfunctions[i];
jv name = jv_object_get(jv_copy(subfn->debuginfo), jv_string("name"));
printf("%*s%s:%d:\n", indent, "", jv_string_value(name), i);
jv_free(name);
dump_disassembly(indent+2, subfn);
}
}
static struct bytecode* getlevel(struct bytecode* bc, int level) {
while (level > 0) {
bc = bc->parent;
level--;
}
return bc;
}
void dump_operation(struct bytecode* bc, uint16_t* codeptr) {
int pc = codeptr - bc->code;
printf("%04d ", pc);
const struct opcode_description* op = opcode_describe(bc->code[pc++]);
printf("%s", op->name);
if (op->length > 1) {
uint16_t imm = bc->code[pc++];
if (op->op == CALL_JQ) {
for (int i=0; i<imm+1; i++) {
uint16_t level = bc->code[pc++];
uint16_t idx = bc->code[pc++];
jv name;
if (idx & ARG_NEWCLOSURE) {
idx &= ~ARG_NEWCLOSURE;
name = jv_object_get(jv_copy(getlevel(bc,level)->subfunctions[idx]->debuginfo),
jv_string("name"));
} else {
name = jv_array_get(jv_object_get(jv_copy(getlevel(bc,level)->debuginfo),
jv_string("params")), idx);
}
printf(" %s:%d",
jv_string_value(name),
idx);
jv_free(name);
if (level) {
printf("^%d", level);
}
}
} else if (op->op == CALL_BUILTIN) {
int func = bc->code[pc++];
jv name = jv_array_get(jv_copy(bc->globals->cfunc_names), func);
printf(" %s", jv_string_value(name));
jv_free(name);
} else if (op->flags & OP_HAS_BRANCH) {
printf(" %04d", pc + imm);
} else if (op->flags & OP_HAS_CONSTANT) {
printf(" ");
jv_dump(jv_array_get(jv_copy(bc->constants), imm), 0);
} else if (op->flags & OP_HAS_VARIABLE) {
uint16_t v = bc->code[pc++];
jv name = jv_array_get(jv_object_get(jv_copy(getlevel(bc,imm)->debuginfo), jv_string("locals")), v);
printf(" $%s:%d",
jv_string_value(name),
v);
jv_free(name);
if (imm) {
printf("^%d", imm);
}
} else {
printf(" %d", imm);
}
}
}
void bytecode_free(struct bytecode* bc) {
if (!bc)
return;
jv_mem_free(bc->code);
jv_free(bc->constants);
for (int i=0; i<bc->nsubfunctions; i++)
bytecode_free(bc->subfunctions[i]);
if (!bc->parent)
symbol_table_free(bc->globals);
jv_mem_free(bc->subfunctions);
jv_free(bc->debuginfo);
jv_mem_free(bc);
}
+88
View File
@@ -0,0 +1,88 @@
#ifndef BYTECODE_H
#define BYTECODE_H
#include <stdint.h>
#include "jv.h"
typedef enum {
#define OP(name, imm, in, out) name,
#include "opcode_list.h"
#undef OP
} opcode;
enum {
NUM_OPCODES =
#define OP(name, imm, in, out) +1
#include "opcode_list.h"
#undef OP
};
enum {
OP_HAS_CONSTANT = 2,
OP_HAS_VARIABLE = 4,
OP_HAS_BRANCH = 8,
OP_HAS_CFUNC = 32,
OP_HAS_UFUNC = 64,
OP_IS_CALL_PSEUDO = 128,
OP_HAS_BINDING = 1024,
};
struct opcode_description {
opcode op;
const char* name;
int flags;
// length in 16-bit units
int length;
int stack_in, stack_out;
};
const struct opcode_description* opcode_describe(opcode op);
#define MAX_CFUNCTION_ARGS 10
typedef void (*cfunction_ptr)();
struct cfunction {
cfunction_ptr fptr;
const char* name;
int nargs;
};
struct symbol_table {
struct cfunction* cfunctions;
int ncfunctions;
jv cfunc_names;
};
// The bytecode format matters in:
// execute.c - interpreter
// compile.c - compiler
// bytecode.c - disassembler
#define ARG_NEWCLOSURE 0x1000
struct bytecode {
uint16_t* code;
int codelen;
int nlocals;
int nclosures;
jv constants; // JSON array of constants
struct symbol_table* globals;
struct bytecode** subfunctions;
int nsubfunctions;
struct bytecode* parent;
jv debuginfo;
};
void dump_disassembly(int, struct bytecode* code);
void dump_operation(struct bytecode* bc, uint16_t* op);
void bytecode_free(struct bytecode* bc);
#endif
+672
View File
@@ -0,0 +1,672 @@
#ifndef _GNU_SOURCE
#define _GNU_SOURCE // for strdup
#endif
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include "compile.h"
#include "bytecode.h"
#include "locfile.h"
#include "jv_alloc.h"
/*
The intermediate representation for jq filters is as a sequence of
struct inst, which form a doubly-linked list via the next and prev
pointers.
A "block" represents a sequence of "struct inst", which may be
empty.
Blocks are generated by the parser bottom-up, so may have free
variables (refer to things not defined). See inst.bound_by and
inst.symbol.
*/
struct inst {
struct inst* next;
struct inst* prev;
opcode op;
struct {
uint16_t intval;
struct inst* target;
jv constant;
const struct cfunction* cfunc;
} imm;
location source;
// Binding
// An instruction requiring binding (for parameters/variables/functions)
// is in one of three states:
// inst->bound_by = NULL - Unbound free variable
// inst->bound_by = inst - This instruction binds a variable
// inst->bound_by = other - Uses variable bound by other instruction
// Unbound instructions (references to other things that may or may not
// exist) are created by "gen_foo_unbound", and bindings are created by
// block_bind(definition, body), which binds all instructions in
// body which are unboudn and refer to "definition" by name.
struct inst* bound_by;
char* symbol;
block subfn; // used by CLOSURE_CREATE (body of function)
block arglist; // used by CLOSURE_CREATE (formals) and CALL_JQ (arguments)
// This instruction is compiled as part of which function?
// (only used during block_compile)
struct bytecode* compiled;
int bytecode_pos; // position just after this insn
};
static inst* inst_new(opcode op) {
inst* i = jv_mem_alloc(sizeof(inst));
i->next = i->prev = 0;
i->op = op;
i->bytecode_pos = -1;
i->bound_by = 0;
i->symbol = 0;
i->subfn = gen_noop();
i->arglist = gen_noop();
i->source = UNKNOWN_LOCATION;
return i;
}
static void inst_free(struct inst* i) {
jv_mem_free(i->symbol);
block_free(i->subfn);
block_free(i->arglist);
if (opcode_describe(i->op)->flags & OP_HAS_CONSTANT) {
jv_free(i->imm.constant);
}
jv_mem_free(i);
}
static block inst_block(inst* i) {
block b = {i,i};
return b;
}
static int block_is_single(block b) {
return b.first && b.first == b.last;
}
static inst* block_take(block* b) {
if (b->first == 0) return 0;
inst* i = b->first;
if (i->next) {
i->next->prev = 0;
b->first = i->next;
i->next = 0;
} else {
b->first = 0;
b->last = 0;
}
return i;
}
block gen_location(location loc, block b) {
for (inst* i = b.first; i; i = i->next) {
if (i->source.start == UNKNOWN_LOCATION.start &&
i->source.end == UNKNOWN_LOCATION.end) {
i->source = loc;
}
}
return b;
}
block gen_noop() {
block b = {0,0};
return b;
}
block gen_op_simple(opcode op) {
assert(opcode_describe(op)->length == 1);
return inst_block(inst_new(op));
}
block gen_const(jv constant) {
assert(opcode_describe(LOADK)->flags & OP_HAS_CONSTANT);
inst* i = inst_new(LOADK);
i->imm.constant = constant;
return inst_block(i);
}
block gen_op_target(opcode op, block target) {
assert(opcode_describe(op)->flags & OP_HAS_BRANCH);
assert(target.last);
inst* i = inst_new(op);
i->imm.target = target.last;
return inst_block(i);
}
block gen_op_targetlater(opcode op) {
assert(opcode_describe(op)->flags & OP_HAS_BRANCH);
inst* i = inst_new(op);
i->imm.target = 0;
return inst_block(i);
}
void inst_set_target(block b, block target) {
assert(block_is_single(b));
assert(opcode_describe(b.first->op)->flags & OP_HAS_BRANCH);
assert(target.last);
b.first->imm.target = target.last;
}
block gen_op_unbound(opcode op, const char* name) {
assert(opcode_describe(op)->flags & OP_HAS_BINDING);
inst* i = inst_new(op);
i->symbol = strdup(name);
return inst_block(i);
}
block gen_op_var_fresh(opcode op, const char* name) {
assert(opcode_describe(op)->flags & OP_HAS_VARIABLE);
return block_bind(gen_op_unbound(op, name),
gen_noop(), OP_HAS_VARIABLE);
}
block gen_op_bound(opcode op, block binder) {
assert(block_is_single(binder));
block b = gen_op_unbound(op, binder.first->symbol);
b.first->bound_by = binder.first;
return b;
}
static void inst_join(inst* a, inst* b) {
assert(a && b);
assert(!a->next);
assert(!b->prev);
a->next = b;
b->prev = a;
}
void block_append(block* b, block b2) {
if (b2.first) {
if (b->last) {
inst_join(b->last, b2.first);
} else {
b->first = b2.first;
}
b->last = b2.last;
}
}
block block_join(block a, block b) {
block c = a;
block_append(&c, b);
return c;
}
int block_has_only_binders(block binders, int bindflags) {
bindflags |= OP_HAS_BINDING;
for (inst* curr = binders.first; curr; curr = curr->next) {
if ((opcode_describe(curr->op)->flags & bindflags) != bindflags) {
return 0;
}
}
return 1;
}
static int block_bind_subblock(block binder, block body, int bindflags) {
assert(block_is_single(binder));
assert((opcode_describe(binder.first->op)->flags & bindflags) == bindflags);
assert(binder.first->symbol);
assert(binder.first->bound_by == 0 || binder.first->bound_by == binder.first);
binder.first->bound_by = binder.first;
int nrefs = 0;
for (inst* i = body.first; i; i = i->next) {
int flags = opcode_describe(i->op)->flags;
if ((flags & bindflags) == bindflags &&
i->bound_by == 0 &&
!strcmp(i->symbol, binder.first->symbol)) {
// bind this instruction
i->bound_by = binder.first;
nrefs++;
}
// binding recurses into closures
nrefs += block_bind_subblock(binder, i->subfn, bindflags);
// binding recurses into argument list
nrefs += block_bind_subblock(binder, i->arglist, bindflags);
}
return nrefs;
}
static void block_bind_each(block binder, block body, int bindflags) {
assert(block_has_only_binders(binder, bindflags));
bindflags |= OP_HAS_BINDING;
for (inst* curr = binder.first; curr; curr = curr->next) {
block_bind_subblock(inst_block(curr), body, bindflags);
}
}
block block_bind(block binder, block body, int bindflags) {
block_bind_each(binder, body, bindflags);
return block_join(binder, body);
}
block block_bind_referenced(block binder, block body, int bindflags) {
assert(block_has_only_binders(binder, bindflags));
bindflags |= OP_HAS_BINDING;
block refd = gen_noop();
for (inst* curr; (curr = block_take(&binder));) {
block b = inst_block(curr);
if (block_bind_subblock(b, body, bindflags)) {
refd = BLOCK(refd, b);
} else {
block_free(b);
}
}
return block_join(refd, body);
}
block gen_function(const char* name, block formals, block body) {
block_bind_each(formals, body, OP_IS_CALL_PSEUDO);
inst* i = inst_new(CLOSURE_CREATE);
i->subfn = body;
i->symbol = strdup(name);
i->arglist = formals;
block b = inst_block(i);
block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING);
return b;
}
block gen_param(const char* name) {
return gen_op_unbound(CLOSURE_PARAM, name);
}
block gen_lambda(block body) {
return gen_function("@lambda", gen_noop(), body);
}
block gen_call(const char* name, block args) {
block b = gen_op_unbound(CALL_JQ, name);
b.first->arglist = args;
return b;
}
block gen_subexp(block a) {
return BLOCK(gen_op_simple(SUBEXP_BEGIN), a, gen_op_simple(SUBEXP_END));
}
block gen_both(block a, block b) {
block jump = gen_op_targetlater(JUMP);
block fork = gen_op_target(FORK, jump);
block c = BLOCK(fork, a, jump, b);
inst_set_target(jump, c);
return c;
}
block gen_collect(block expr) {
block array_var = gen_op_var_fresh(STOREV, "collect");
block c = BLOCK(gen_op_simple(DUP), gen_const(jv_array()), array_var);
block tail = BLOCK(gen_op_bound(APPEND, array_var),
gen_op_simple(BACKTRACK));
return BLOCK(c,
gen_op_target(FORK, tail),
expr,
tail,
gen_op_bound(LOADVN, array_var));
}
block gen_reduce(const char* varname, block source, block init, block body) {
block res_var = gen_op_var_fresh(STOREV, "reduce");
block loop = BLOCK(gen_op_simple(DUP),
source,
block_bind(gen_op_unbound(STOREV, varname),
BLOCK(gen_op_bound(LOADVN, res_var),
body,
gen_op_bound(STOREV, res_var)),
OP_HAS_VARIABLE),
gen_op_simple(BACKTRACK));
return BLOCK(gen_op_simple(DUP),
init,
res_var,
gen_op_target(FORK, loop),
loop,
gen_op_bound(LOADVN, res_var));
}
block gen_definedor(block a, block b) {
// var found := false
block found_var = gen_op_var_fresh(STOREV, "found");
block init = BLOCK(gen_op_simple(DUP), gen_const(jv_false()), found_var);
// if found, backtrack. Otherwise execute b
block backtrack = gen_op_simple(BACKTRACK);
block tail = BLOCK(gen_op_simple(DUP),
gen_op_bound(LOADV, found_var),
gen_op_target(JUMP_F, backtrack),
backtrack,
gen_op_simple(POP),
b);
// try again
block if_notfound = gen_op_simple(BACKTRACK);
// found := true, produce result
block if_found = BLOCK(gen_op_simple(DUP),
gen_const(jv_true()),
gen_op_bound(STOREV, found_var),
gen_op_target(JUMP, tail));
return BLOCK(init,
gen_op_target(FORK, if_notfound),
a,
gen_op_target(JUMP_F, if_found),
if_found,
if_notfound,
tail);
}
block gen_condbranch(block iftrue, block iffalse) {
iftrue = BLOCK(iftrue, gen_op_target(JUMP, iffalse));
return BLOCK(gen_op_target(JUMP_F, iftrue), iftrue, iffalse);
}
block gen_and(block a, block b) {
// a and b = if a then (if b then true else false) else false
return BLOCK(gen_op_simple(DUP), a,
gen_condbranch(BLOCK(gen_op_simple(POP),
b,
gen_condbranch(gen_const(jv_true()),
gen_const(jv_false()))),
BLOCK(gen_op_simple(POP), gen_const(jv_false()))));
}
block gen_or(block a, block b) {
// a or b = if a then true else (if b then true else false)
return BLOCK(gen_op_simple(DUP), a,
gen_condbranch(BLOCK(gen_op_simple(POP), gen_const(jv_true())),
BLOCK(gen_op_simple(POP),
b,
gen_condbranch(gen_const(jv_true()),
gen_const(jv_false())))));
}
block gen_var_binding(block var, const char* name, block body) {
return BLOCK(gen_op_simple(DUP), var,
block_bind(gen_op_unbound(STOREV, name),
body, OP_HAS_VARIABLE));
}
block gen_cond(block cond, block iftrue, block iffalse) {
return BLOCK(gen_op_simple(DUP), cond,
gen_condbranch(BLOCK(gen_op_simple(POP), iftrue),
BLOCK(gen_op_simple(POP), iffalse)));
}
block gen_cbinding(const struct cfunction* cfunctions, int ncfunctions, block code) {
for (int cfunc=0; cfunc<ncfunctions; cfunc++) {
inst* i = inst_new(CLOSURE_CREATE_C);
i->imm.cfunc = &cfunctions[cfunc];
i->symbol = strdup(i->imm.cfunc->name);
code = block_bind(inst_block(i), code, OP_IS_CALL_PSEUDO);
}
return code;
}
static uint16_t nesting_level(struct bytecode* bc, inst* target) {
uint16_t level = 0;
assert(bc && target->compiled);
while (bc && target->compiled != bc) {
level++;
bc = bc->parent;
}
assert(bc && bc == target->compiled);
return level;
}
static int count_cfunctions(block b) {
int n = 0;
for (inst* i = b.first; i; i = i->next) {
if (i->op == CLOSURE_CREATE_C) n++;
n += count_cfunctions(i->subfn);
}
return n;
}
// Expands call instructions into a calling sequence
static int expand_call_arglist(struct locfile* locations, block* b) {
int errors = 0;
block ret = gen_noop();
for (inst* curr; (curr = block_take(b));) {
if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) {
if (!curr->bound_by) {
locfile_locate(locations, curr->source, "error: %s is not defined", curr->symbol);
errors++;
// don't process this instruction if it's not well-defined
ret = BLOCK(ret, inst_block(curr));
continue;
}
}
block prelude = gen_noop();
if (curr->op == CALL_JQ) {
int actual_args = 0, desired_args = 0;
// We expand the argument list as a series of instructions
switch (curr->bound_by->op) {
default: assert(0 && "Unknown function type"); break;
case CLOSURE_CREATE:
case CLOSURE_PARAM: {
block callargs = gen_noop();
for (inst* i; (i = block_take(&curr->arglist));) {
assert(opcode_describe(i->op)->flags & OP_IS_CALL_PSEUDO);
block b = inst_block(i);
switch (i->op) {
default: assert(0 && "Unknown type of parameter"); break;
case CLOSURE_REF:
block_append(&callargs, b);
break;
case CLOSURE_CREATE:
block_append(&prelude, b);
block_append(&callargs, gen_op_bound(CLOSURE_REF, b));
break;
}
actual_args++;
}
curr->imm.intval = actual_args;
curr->arglist = callargs;
if (curr->bound_by->op == CLOSURE_CREATE) {
for (inst* i = curr->bound_by->arglist.first; i; i = i->next) {
assert(i->op == CLOSURE_PARAM);
desired_args++;
}
}
break;
}
case CLOSURE_CREATE_C: {
for (inst* i; (i = block_take(&curr->arglist)); ) {
assert(i->op == CLOSURE_CREATE); // FIXME
block body = i->subfn;
i->subfn = gen_noop();
inst_free(i);
// arguments should be pushed in reverse order, prepend them to prelude
errors += expand_call_arglist(locations, &body);
prelude = BLOCK(gen_subexp(body), prelude);
actual_args++;
}
assert(curr->op == CALL_JQ);
curr->op = CALL_BUILTIN;
curr->imm.intval = actual_args + 1 /* include the implicit input in arg count */;
assert(curr->bound_by->op == CLOSURE_CREATE_C);
desired_args = curr->bound_by->imm.cfunc->nargs - 1;
assert(!curr->arglist.first);
break;
}
}
if (actual_args != desired_args) {
locfile_locate(locations, curr->source,
"error: %s arguments to %s (expected %d but got %d)",
actual_args > desired_args ? "too many" : "too few",
curr->symbol, desired_args, actual_args);
errors++;
}
}
ret = BLOCK(ret, prelude, inst_block(curr));
}
*b = ret;
return errors;
}
static int compile(struct locfile* locations, struct bytecode* bc, block b) {
int errors = 0;
int pos = 0;
int var_frame_idx = 0;
bc->nsubfunctions = 0;
errors += expand_call_arglist(locations, &b);
b = BLOCK(b, gen_op_simple(RET));
jv localnames = jv_array();
for (inst* curr = b.first; curr; curr = curr->next) {
if (!curr->next) assert(curr == b.last);
int length = opcode_describe(curr->op)->length;
if (curr->op == CALL_JQ) {
for (inst* arg = curr->arglist.first; arg; arg = arg->next) {
length += 2;
}
}
pos += length;
curr->bytecode_pos = pos;
curr->compiled = bc;
assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM);
if ((opcode_describe(curr->op)->flags & OP_HAS_VARIABLE) &&
curr->bound_by == curr) {
curr->imm.intval = var_frame_idx++;
localnames = jv_array_append(localnames, jv_string(curr->symbol));
}
if (curr->op == CLOSURE_CREATE) {
assert(curr->bound_by == curr);
curr->imm.intval = bc->nsubfunctions++;
}
if (curr->op == CLOSURE_CREATE_C) {
assert(curr->bound_by == curr);
int idx = bc->globals->ncfunctions++;
bc->globals->cfunc_names = jv_array_append(bc->globals->cfunc_names,
jv_string(curr->symbol));
bc->globals->cfunctions[idx] = *curr->imm.cfunc;
curr->imm.intval = idx;
}
}
bc->debuginfo = jv_object_set(bc->debuginfo, jv_string("locals"), localnames);
if (bc->nsubfunctions) {
bc->subfunctions = jv_mem_alloc(sizeof(struct bytecode*) * bc->nsubfunctions);
for (inst* curr = b.first; curr; curr = curr->next) {
if (curr->op == CLOSURE_CREATE) {
struct bytecode* subfn = jv_mem_alloc(sizeof(struct bytecode));
bc->subfunctions[curr->imm.intval] = subfn;
subfn->globals = bc->globals;
subfn->parent = bc;
subfn->nclosures = 0;
subfn->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_string(curr->symbol));
jv params = jv_array();
for (inst* param = curr->arglist.first; param; param = param->next) {
assert(param->op == CLOSURE_PARAM);
assert(param->bound_by == param);
param->imm.intval = subfn->nclosures++;
param->compiled = subfn;
params = jv_array_append(params, jv_string(param->symbol));
}
subfn->debuginfo = jv_object_set(subfn->debuginfo, jv_string("params"), params);
errors += compile(locations, subfn, curr->subfn);
curr->subfn = gen_noop();
}
}
} else {
bc->subfunctions = 0;
}
bc->codelen = pos;
uint16_t* code = jv_mem_alloc(sizeof(uint16_t) * bc->codelen);
bc->code = code;
pos = 0;
jv constant_pool = jv_array();
int maxvar = -1;
if (!errors) for (inst* curr = b.first; curr; curr = curr->next) {
const struct opcode_description* op = opcode_describe(curr->op);
if (op->length == 0)
continue;
code[pos++] = curr->op;
assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM);
if (curr->op == CALL_BUILTIN) {
assert(curr->bound_by->op == CLOSURE_CREATE_C);
assert(!curr->arglist.first);
code[pos++] = (uint16_t)curr->imm.intval;
code[pos++] = curr->bound_by->imm.intval;
} else if (curr->op == CALL_JQ) {
assert(curr->bound_by->op == CLOSURE_CREATE ||
curr->bound_by->op == CLOSURE_PARAM);
code[pos++] = (uint16_t)curr->imm.intval;
code[pos++] = nesting_level(bc, curr->bound_by);
code[pos++] = curr->bound_by->imm.intval |
(curr->bound_by->op == CLOSURE_CREATE ? ARG_NEWCLOSURE : 0);
for (inst* arg = curr->arglist.first; arg; arg = arg->next) {
assert(arg->op == CLOSURE_REF && arg->bound_by->op == CLOSURE_CREATE);
code[pos++] = nesting_level(bc, arg->bound_by);
code[pos++] = arg->bound_by->imm.intval | ARG_NEWCLOSURE;
}
} else if (op->flags & OP_HAS_CONSTANT) {
code[pos++] = jv_array_length(jv_copy(constant_pool));
constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant));
} else if (op->flags & OP_HAS_VARIABLE) {
code[pos++] = nesting_level(bc, curr->bound_by);
uint16_t var = (uint16_t)curr->bound_by->imm.intval;
code[pos++] = var;
if (var > maxvar) maxvar = var;
} else if (op->flags & OP_HAS_BRANCH) {
assert(curr->imm.target->bytecode_pos != -1);
assert(curr->imm.target->bytecode_pos > pos); // only forward branches
code[pos] = curr->imm.target->bytecode_pos - (pos + 1);
pos++;
} else if (op->length > 1) {
assert(0 && "codegen not implemented for this operation");
}
}
bc->constants = constant_pool;
bc->nlocals = maxvar + 2; // FIXME: frames of size zero?
block_free(b);
return errors;
}
int block_compile(block b, struct locfile* locations, struct bytecode** out) {
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
bc->parent = 0;
bc->nclosures = 0;
bc->globals = jv_mem_alloc(sizeof(struct symbol_table));
int ncfunc = count_cfunctions(b);
bc->globals->ncfunctions = 0;
bc->globals->cfunctions = jv_mem_alloc(sizeof(struct cfunction) * ncfunc);
bc->globals->cfunc_names = jv_array();
bc->debuginfo = jv_object_set(jv_object(), jv_string("name"), jv_null());
int nerrors = compile(locations, bc, b);
assert(bc->globals->ncfunctions == ncfunc);
if (nerrors > 0) {
bytecode_free(bc);
*out = 0;
} else {
*out = bc;
}
return nerrors;
}
void block_free(block b) {
struct inst* next;
for (struct inst* curr = b.first; curr; curr = next) {
next = curr->next;
inst_free(curr);
}
}
+74
View File
@@ -0,0 +1,74 @@
#ifndef COMPILE_H
#define COMPILE_H
#include <stdint.h>
#include "jv.h"
#include "bytecode.h"
#include "locfile.h"
struct inst;
typedef struct inst inst;
typedef struct block {
inst* first;
inst* last;
} block;
block gen_location(location, block);
block gen_noop();
block gen_op_simple(opcode op);
block gen_const(jv constant);
block gen_op_target(opcode op, block target);
block gen_op_unbound(opcode op, const char* name);
block gen_op_bound(opcode op, block binder);
block gen_op_var_fresh(opcode op, const char* name);
block gen_function(const char* name, block formals, block body);
block gen_param(const char* name);
block gen_lambda(block body);
block gen_call(const char* name, block body);
block gen_subexp(block a);
block gen_both(block a, block b);
block gen_collect(block expr);
block gen_reduce(const char* varname, block source, block init, block body);
block gen_definedor(block a, block b);
block gen_condbranch(block iftrue, block iffalse);
block gen_and(block a, block b);
block gen_or(block a, block b);
block gen_var_binding(block var, const char* name, block body);
block gen_cond(block cond, block iftrue, block iffalse);
block gen_cbinding(const struct cfunction* functions, int nfunctions, block b);
void block_append(block* b, block b2);
block block_join(block a, block b);
int block_has_only_binders(block, int bindflags);
block block_bind(block binder, block body, int bindflags);
block block_bind_referenced(block binder, block body, int bindflags);
int block_compile(block, struct locfile*, struct bytecode**);
void block_free(block);
// Here's some horrible preprocessor gunk so that code
// sequences can be contructed as BLOCK(block1, block2, block3)
#define BLOCK_1(b1) (b1)
#define BLOCK_2(b1,b2) (block_join((b1),(b2)))
#define BLOCK_3(b1,b2,b3) (block_join(BLOCK_2(b1,b2),(b3)))
#define BLOCK_4(b1,b2,b3,b4) (block_join(BLOCK_3(b1,b2,b3),(b4)))
#define BLOCK_5(b1,b2,b3,b4,b5) (block_join(BLOCK_4(b1,b2,b3,b4),(b5)))
#define BLOCK_6(b1,b2,b3,b4,b5,b6) (block_join(BLOCK_5(b1,b2,b3,b4,b5),(b6)))
#define BLOCK_7(b1,b2,b3,b4,b5,b6,b7) (block_join(BLOCK_6(b1,b2,b3,b4,b5,b6),(b7)))
#define BLOCK_IDX(_1,_2,_3,_4,_5,_6,_7,NAME,...) NAME
#define BLOCK(...) \
BLOCK_IDX(__VA_ARGS__, BLOCK_7, BLOCK_6, BLOCK_5, BLOCK_4, BLOCK_3, BLOCK_2, BLOCK_1)(__VA_ARGS__)
#endif
+10
View File
@@ -0,0 +1,10 @@
config.guess
config.sub
compile
depcomp
install-sh
ltmain.sh
missing
ylwrap
test-driver
ar-lib
+2
View File
@@ -0,0 +1,2 @@
libtool.m4
lt*.m4
+8
View File
@@ -0,0 +1,8 @@
dnl Shamelessly stolen from Heimdal
dnl
dnl Look for function in any of the specified libraries
dnl
dnl AC_FIND_FUNC_NO_LIBS(func, libraries, includes, arguments, extra libs, extra args)
AC_DEFUN([AC_FIND_FUNC_NO_LIBS], [
AC_FIND_FUNC_NO_LIBS2([$1], ["" $2], [$3], [$4], [$5], [$6])])
+62
View File
@@ -0,0 +1,62 @@
dnl Shamelessly stolen from Heimdal
dnl
dnl Look for function in any of the specified libraries
dnl
dnl AC_FIND_FUNC_NO_LIBS2(func, libraries, includes, arguments, extra libs, extra args)
AC_DEFUN([AC_FIND_FUNC_NO_LIBS2], [
AC_MSG_CHECKING([for $1])
AC_CACHE_VAL(ac_cv_funclib_$1,
[
if eval "test \"\$ac_cv_func_$1\" != yes" ; then
ac_save_LIBS="$LIBS"
for ac_lib in $2; do
case "$ac_lib" in
"") ;;
yes) ac_lib="" ;;
no) continue ;;
-l*) ;;
*) ac_lib="-l$ac_lib" ;;
esac
LIBS="$6 $ac_lib $5 $ac_save_LIBS"
AC_LINK_IFELSE([AC_LANG_PROGRAM([[$3]],[[$1($4)]])],[eval "if test -n \"$ac_lib\";then ac_cv_funclib_$1=$ac_lib; else ac_cv_funclib_$1=yes; fi";break])
done
eval "ac_cv_funclib_$1=\${ac_cv_funclib_$1-no}"
LIBS="$ac_save_LIBS"
fi
])
eval "ac_res=\$ac_cv_funclib_$1"
if false; then
AC_CHECK_FUNCS($1)
dnl AC_CHECK_LIBS($2, foo)
fi
# $1
eval "ac_tr_func=HAVE_[]upcase($1)"
eval "ac_tr_lib=HAVE_LIB[]upcase($ac_res | sed -e 's/-l//')"
eval "LIB_$1=$ac_res"
case "$ac_res" in
yes)
eval "ac_cv_func_$1=yes"
eval "LIB_$1="
AC_DEFINE_UNQUOTED($ac_tr_func)
AC_MSG_RESULT([yes])
;;
no)
eval "ac_cv_func_$1=no"
eval "LIB_$1="
AC_MSG_RESULT([no])
;;
*)
eval "ac_cv_func_$1=yes"
eval "ac_cv_lib_`echo "$ac_res" | sed 's/-l//'`=yes"
AC_DEFINE_UNQUOTED($ac_tr_func)
AC_DEFINE_UNQUOTED($ac_tr_lib)
AC_MSG_RESULT([yes, in $ac_res])
;;
esac
AC_SUBST(LIB_$1)
])
+9
View File
@@ -0,0 +1,9 @@
dnl Shamelessly stolen from Heimdal
dnl
dnl AC_FIND_FUNC(func, libraries, includes, arguments)
AC_DEFUN([AC_FIND_FUNC], [
AC_FIND_FUNC_NO_LIBS([$1], [$2], [$3], [$4])
if test -n "$LIB_$1"; then
LIBS="$LIB_$1 $LIBS"
fi
])
+3
View File
@@ -0,0 +1,3 @@
dnl Shamelessly stolen from Heimdal
AC_DEFUN([upcase],[`echo $1 | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ`])dnl
+113
View File
@@ -0,0 +1,113 @@
m4_define([jq_version], m4_esyscmd_s([git describe --tags --dirty --match 'jq-*'|sed 's/^jq-//']))
AC_INIT([jq], [jq_version], [mu@netsoc.tcd.ie],
[jq], [http://stedolan.github.com/jq/])
dnl Created autoconf implementation thompson@dtosolutions, 26NOV12
AC_PREREQ([2.61])
AC_CONFIG_AUX_DIR([config])
AM_INIT_AUTOMAKE([parallel-tests foreign -Wall])
AM_SILENT_RULES([yes])
AM_PROG_AR
AC_PROG_CC
AC_PROG_CC_STDC
AC_PROG_CPP_WERROR
AC_PROG_YACC
AC_OBJEXT
AC_EXEEXT
LT_INIT([shared static win32-dll])
AM_PROG_CC_C_O
dnl couldn't use AM_PROG_LEX as it doesn't support header files like the
dnl AC_PROG_YACC macros...
dnl check bison version
AC_MSG_CHECKING([bison version])
if test "$YACC" != "bison -y"; then
AC_MSG_RESULT([not bison])
elif $YACC --help | grep -- --warnings >/dev/null; then
AC_MSG_RESULT([ok])
else
AC_MSG_RESULT([no])
AC_MSG_ERROR([You need bison version 2.5 or more recent.])
fi
dnl
dnl these program checks should probably be deleted
dnl
AC_PROG_LEX
if test "x$LEX" != xflex; then
LEX="$SHELL $missing_dir/missing flex"
fi
dnl Check for valgrind
AC_CHECK_PROGS(valgrind_cmd, valgrind)
if test "x$valgrind_cmd" = "x" ; then
AC_MSG_WARN([valgrind is required to test jq.])
fi
dnl Don't attempt to build docs if there's no Ruby lying around
AC_ARG_ENABLE([docs],
AC_HELP_STRING([--disable-docs], [don't build docs]))
AS_IF([test "x$enable_docs" != "xno"],[
AC_CHECK_PROGS(bundle_cmd, bundle)
AC_CACHE_CHECK([for Ruby dependencies], [jq_cv_ruby_deps],
[jq_cv_ruby_deps=yes;
AS_IF([test "x$bundle_cmd" = "x" || \
! bmsg="`cd ${srcdir}/docs; "$bundle_cmd" check 2>/dev/null`"],[
AC_MSG_WARN([$bmsg])
cat <<EOF
*****************************************************************
* Ruby dependencies for building jq documentation not found. *
* You can still build, install and hack on jq, but the manpage *
* will not be rebuilt and some of the tests won't run. *
* See docs/README.md for how to install the docs dependencies. *
*****************************************************************
EOF
jq_cv_ruby_deps=no
])])
if test "x$jq_cv_ruby_deps" != "xyes"; then
enable_docs=no
fi
])
AM_CONDITIONAL([ENABLE_DOCS], [test "x$enable_docs" != xno])
AC_ARG_ENABLE([pthread-tls],
[AC_HELP_STRING([--enable-pthread-tls],
[Enable use of pthread thread local storage])],
[],
[enable_pthread_tls=no])
if test $enable_pthread_tls = yes; then
AC_FIND_FUNC([pthread_key_create], [pthread], [#include <pthread.h>], [NULL, NULL])
AC_FIND_FUNC([pthread_once], [pthread], [#include <pthread.h>], [NULL, NULL])
AC_FIND_FUNC([atexit], [pthread], [#include <stdlib.h>], [NULL])
fi
dnl Thread local storage
have___thread=no
AC_MSG_CHECKING(for thread-local storage)
AC_LINK_IFELSE([AC_LANG_SOURCE([
static __thread int x ;
int main () { x = 123; return x; }
])], have___thread=yes)
if test $have___thread = yes; then
AC_DEFINE([HAVE___THREAD],1,[Define to 1 if the system supports __thread])
fi
AC_MSG_RESULT($have___thread)
AC_SUBST([BUNDLER], ["$bundle_cmd"])
AC_CONFIG_MACRO_DIR([config/m4])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
+6
View File
@@ -0,0 +1,6 @@
.DS_Store
.sass-cache
output/*
# Autogenerated from public/css/base.scss
public/css/base.css
+6
View File
@@ -0,0 +1,6 @@
source "https://rubygems.org"
gem "rake"
gem "bonsai"
gem "maruku"
gem "ronn"
+63
View File
@@ -0,0 +1,63 @@
GEM
remote: https://rubygems.org/
specs:
POpen4 (0.1.4)
Platform (>= 0.4.0)
open4
Platform (0.4.0)
activesupport (3.2.12)
i18n (~> 0.6)
multi_json (~> 1.0)
addressable (2.3.4)
bonsai (1.4.8)
activesupport (>= 3.0.3)
builder (>= 3.0.0)
i18n (>= 0.5.0)
launchy (>= 0.3.7)
liquid (>= 2.2.2)
maruku (>= 0.6.0)
rack
sass
sinatra (>= 1.0)
tilt (>= 1.3)
watch (>= 0.1.0)
yui-compressor
builder (3.2.0)
hpricot (0.8.6)
i18n (0.6.4)
launchy (2.3.0)
addressable (~> 2.3)
liquid (2.5.0)
maruku (0.6.1)
syntax (>= 1.0.0)
multi_json (1.7.3)
mustache (0.99.4)
open4 (1.3.0)
rack (1.5.2)
rack-protection (1.5.0)
rack
rake (10.0.3)
rdiscount (2.0.7.2)
ronn (0.7.3)
hpricot (>= 0.8.2)
mustache (>= 0.7.0)
rdiscount (>= 1.5.8)
sass (3.2.8)
sinatra (1.4.2)
rack (~> 1.5, >= 1.5.2)
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
syntax (1.0.0)
tilt (1.4.0)
watch (0.1.0)
yui-compressor (0.9.6)
POpen4 (>= 0.1.4)
PLATFORMS
ruby
DEPENDENCIES
bonsai
maruku
rake
ronn
+25
View File
@@ -0,0 +1,25 @@
Documentation
=============
The jq website, manpages and some of the tests are generated from this
directory. The directory holds a [Bonsai](http://tinytree.info)
website, and the manual is a YAML file in `content/3.manual`.
To build the documentation (including building the jq manpage), you'll
need a working Ruby setup. The easiest way to get one is to install
RVM and Ruby 1.9.3 like so:
\curl -L https://get.rvm.io | bash -s stable --ruby=1.9.3
After that finishes installing, you'll need to make sure RVM is on
your path by doing `source $HOME/.rvm/scripts/rvm`, or just opening a
new shell. See <http://rvm.io> for more info on RVM.
Once RVM is installed, you can install all the dependencies for jq's
documentation build by running this from the `docs` directory:
bundle install
When bundle manages to install the dependencies, rerun `./configure`
in the jq root directory and then the Makefile will be able to
generate the jq manpage.
+146
View File
@@ -0,0 +1,146 @@
require 'yaml'
require 'bonsai'
require 'liquid'
require 'maruku'
require 'json'
require 'ronn'
require 'tempfile'
require 'yaml'
module ExtraFilters
def markdownify(input)
Maruku.new(input).to_html
end
def sanitize(input)
input.gsub(/[^a-zA-Z0-9_]/,"")
end
def json(input)
input.to_json
end
def unique(input)
@n = (@n || 0) + 1
input + @n.to_s
end
end
Liquid::Template.register_filter(ExtraFilters)
task :serve do
begin
Bonsai.log "Press Control+C to quit"
require 'rack'
require 'sinatra'
require 'watch'
require 'launchy'
Bonsai.root_dir = Dir.pwd
server = fork {
app = Rack::Builder.app {
map "/jq" do
use Bonsai::StaticPassThrough, :root => Bonsai.root_dir + "/output", :urls => ["/"]
end
run Bonsai::DevelopmentServer
}
Rack::Handler.default.run(app, :Port => 5000) do
Launchy.open("http://localhost:5000/jq/")
end
}
Watch.new("{content,templates,public}/**/*") { Bonsai::Exporter.process! }
rescue Interrupt
Process.kill("QUIT", server)
Process.wait(server)
exit
end
end
task :build do
Bonsai.root_dir = Dir.pwd
Bonsai::Exporter.publish!
end
$BINARIES = {
:osx32 => "--host=i686-apple-darwin10 CFLAGS='-m32 -g -O'",
:osx64 => "--host=i686-apple-darwin10 CFLAGS='-m64 -g -O'",
:win32 => "--host=i686-w64-mingw32 CFLAGS='-g -O'",
:win64 => "--host=x86_64-w64-mingw32 CFLAGS='-g -O'",
:linux32 => "--host=x86_64-linux-gnu CFLAGS='-m32 -g -O'",
:linux64 => "--host=x86_64-linux-gnu CFLAGS='-m64 -g -O'"
}
$BINARIES.each do |name, args|
file "../build/#{name}" do |t|
sh "../scripts/crosscompile -j4 #{name} #{args}"
end
task :binaries => ["../build/#{name}"]
end
task :binaries => ["output/download"] do
$BINARIES.each do |name, args|
FileUtils.cp_r "../build/#{name}", "output/download/"
end
end
def load_manual
YAML::ENGINE.yamler = 'syck'
YAML::load(File.open("content/3.manual/manual.yml"))
end
task :manpage do
Tempfile.open "manpage" do |f|
manual = load_manual
f.puts manual['manpage_intro']
f.puts manual['body']
manual['sections'].each do |section|
f.puts "## #{section['title'].upcase}\n"
f.puts section['body']
f.puts ""
(section['entries'] || []).each do |entry|
f.puts "### #{entry['title']}\n"
f.puts entry['body']
f.puts ""
(entry['examples'] || []).each do |example|
f.puts " jq '#{example['program']}'"
f.puts " #{example['input']}"
f.puts " => #{example['output'].join(", ")}"
f.puts
end
end
f.puts ""
end
f.puts manual['manpage_epilogue']
f.close
puts Ronn::Document.new(f.path).convert('roff').gsub(/<\/?code>/,"")
end
end
task :manpage_default => ["default_manpage.md"] do
puts Ronn::Document.new("default_manpage.md").convert('roff').gsub(/<\/?code>/,"")
end
task :mantests do
load_manual['sections'].each do |section|
(section['entries'] || []).each do |entry|
(entry['examples'] || []).each do |example|
puts example['program'].gsub("\n", " ")
puts example['input']
example['output'].each do |s| puts s end
puts
end
end
end
end
directory "output/download/source"
task :tarball => ["output/download/source"] do
sh "cd ..; ./configure && make dist && make distclean"
sh "cp ../jq-*.tar.gz output/download/source"
end
task :dist => [:build, :binaries, :tarball]
+322
View File
@@ -0,0 +1,322 @@
headline: Tutorial
body:
- text: |
GitHub has a JSON API, so let's play with that. This URL gets
us the last 5 commits from the jq repo:
- command: "curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5'"
result: |
[
{
"sha": "d25341478381063d1c76e81b3a52e0592a7c997f",
"commit": {
"author": {
"name": "Stephen Dolan",
"email": "mu@netsoc.tcd.ie",
"date": "2013-06-22T16:30:59Z"
},
"committer": {
"name": "Stephen Dolan",
"email": "mu@netsoc.tcd.ie",
"date": "2013-06-22T16:30:59Z"
},
"message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161",
"tree": {
"sha": "6ab697a8dfb5a96e124666bf6d6213822599fb40",
"url": "https://api.github.com/repos/stedolan/jq/git/trees/6ab697a8dfb5a96e124666bf6d6213822599fb40"
},
"url": "https://api.github.com/repos/stedolan/jq/git/commits/d25341478381063d1c76e81b3a52e0592a7c997f",
"comment_count": 0
},
"url": "https://api.github.com/repos/stedolan/jq/commits/d25341478381063d1c76e81b3a52e0592a7c997f",
"html_url": "https://github.com/stedolan/jq/commit/d25341478381063d1c76e81b3a52e0592a7c997f",
"comments_url": "https://api.github.com/repos/stedolan/jq/commits/d25341478381063d1c76e81b3a52e0592a7c997f/comments",
"author": {
"login": "stedolan",
...
- text: |
Github returns a ton of info here, and it's relatively hard to
read as it is. To syntax highlight it and order the attributes we pipe
it through jq, telling jq to just spit the input back at us using
the expression `.`:
- command: "curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | jq '.'"
result: |
[
{
"parents": [
{
"html_url": "https://github.com/stedolan/jq/commit/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"url": "https://api.github.com/repos/stedolan/jq/commits/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"sha": "54b9c9bdb225af5d886466d72f47eafc51acb4f7"
},
{
"html_url": "https://github.com/stedolan/jq/commit/8b1b503609c161fea4b003a7179b3fbb2dd4345a",
"url": "https://api.github.com/repos/stedolan/jq/commits/8b1b503609c161fea4b003a7179b3fbb2dd4345a",
"sha": "8b1b503609c161fea4b003a7179b3fbb2dd4345a"
}
],
"committer": {
"type": "User",
"received_events_url": "https://api.github.com/users/stedolan/received_events",
"events_url": "https://api.github.com/users/stedolan/events{/privacy}",
"repos_url": "https://api.github.com/users/stedolan/repos",
"organizations_url": "https://api.github.com/users/stedolan/orgs",
...
- text: |
There's still far too much info for our purposes, so we can try
just grab the first commit:
- command: "curl 'https://api.github.com/repos/stedolan/jq/commits?per_page=5' | jq '.[0]'"
result: |
{
"parents": [
{
"html_url": "https://github.com/stedolan/jq/commit/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"url": "https://api.github.com/repos/stedolan/jq/commits/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"sha": "54b9c9bdb225af5d886466d72f47eafc51acb4f7"
},
{
"html_url": "https://github.com/stedolan/jq/commit/8b1b503609c161fea4b003a7179b3fbb2dd4345a",
"url": "https://api.github.com/repos/stedolan/jq/commits/8b1b503609c161fea4b003a7179b3fbb2dd4345a",
"sha": "8b1b503609c161fea4b003a7179b3fbb2dd4345a"
}
],
"committer": {
"type": "User",
"received_events_url": "https://api.github.com/users/stedolan/received_events",
"events_url": "https://api.github.com/users/stedolan/events{/privacy}",
"repos_url": "https://api.github.com/users/stedolan/repos",
"organizations_url": "https://api.github.com/users/stedolan/orgs",
"subscriptions_url": "https://api.github.com/users/stedolan/subscriptions",
"starred_url": "https://api.github.com/users/stedolan/starred{/owner}{/repo}",
"gists_url": "https://api.github.com/users/stedolan/gists{/gist_id}",
"login": "stedolan",
"id": 79765,
"avatar_url": "https://1.gravatar.com/avatar/31de909d8e55dd07ed782d92ece59842?d=https%3A%2F%2Fidenticons.github.com%2Ffc5b6765b1c9cfaecea48ae71df4d279.png",
"gravatar_id": "31de909d8e55dd07ed782d92ece59842",
"url": "https://api.github.com/users/stedolan",
"html_url": "https://github.com/stedolan",
"followers_url": "https://api.github.com/users/stedolan/followers",
"following_url": "https://api.github.com/users/stedolan/following{/other_user}"
},
"author": {
"type": "User",
"received_events_url": "https://api.github.com/users/stedolan/received_events",
"events_url": "https://api.github.com/users/stedolan/events{/privacy}",
"repos_url": "https://api.github.com/users/stedolan/repos",
"organizations_url": "https://api.github.com/users/stedolan/orgs",
"subscriptions_url": "https://api.github.com/users/stedolan/subscriptions",
"starred_url": "https://api.github.com/users/stedolan/starred{/owner}{/repo}",
"gists_url": "https://api.github.com/users/stedolan/gists{/gist_id}",
"login": "stedolan",
"id": 79765,
"avatar_url": "https://1.gravatar.com/avatar/31de909d8e55dd07ed782d92ece59842?d=https%3A%2F%2Fidenticons.github.com%2Ffc5b6765b1c9cfaecea48ae71df4d279.png",
"gravatar_id": "31de909d8e55dd07ed782d92ece59842",
"url": "https://api.github.com/users/stedolan",
"html_url": "https://github.com/stedolan",
"followers_url": "https://api.github.com/users/stedolan/followers",
"following_url": "https://api.github.com/users/stedolan/following{/other_user}"
},
"comments_url": "https://api.github.com/repos/stedolan/jq/commits/d25341478381063d1c76e81b3a52e0592a7c997f/comments",
"html_url": "https://github.com/stedolan/jq/commit/d25341478381063d1c76e81b3a52e0592a7c997f",
"url": "https://api.github.com/repos/stedolan/jq/commits/d25341478381063d1c76e81b3a52e0592a7c997f",
"commit": {
"comment_count": 0,
"url": "https://api.github.com/repos/stedolan/jq/git/commits/d25341478381063d1c76e81b3a52e0592a7c997f",
"tree": {
"url": "https://api.github.com/repos/stedolan/jq/git/trees/6ab697a8dfb5a96e124666bf6d6213822599fb40",
"sha": "6ab697a8dfb5a96e124666bf6d6213822599fb40"
},
"message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161",
"committer": {
"date": "2013-06-22T16:30:59Z",
"email": "mu@netsoc.tcd.ie",
"name": "Stephen Dolan"
},
"author": {
"date": "2013-06-22T16:30:59Z",
"email": "mu@netsoc.tcd.ie",
"name": "Stephen Dolan"
}
},
"sha": "d25341478381063d1c76e81b3a52e0592a7c997f"
}
- text: |
For the rest of the examples, I'll leave out the `curl` command - it's not going to change.
There's still a lot of info we don't care about there, so we'll
restrict it down to the most interesting fields.
- command: "jq '.[0] | {message: .commit.message, name: .commit.committer.name}'"
result: |
{
"name": "Stephen Dolan",
"message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161"
}
- text: |
The `|` operator in jq feeds the output of one filter
(`.[0]` which gets the first element of the array in the response)
into the input of another (`{...}` which
builds an object out of those fields). You can access nested attributes,
such as `.commit.message`:
Now let's get the rest of the commits:
- command: "jq '.[] | {message: .commit.message, name: .commit.committer.name}'"
result: |
{
"name": "Stephen Dolan",
"message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161"
}
{
"name": "Stephen Dolan",
"message": "Reject all overlong UTF8 sequences."
}
{
"name": "Stephen Dolan",
"message": "Fix various UTF8 parsing bugs.\n\nIn particular, parse bad UTF8 by replacing the broken bits with U+FFFD\nand resychronise correctly after broken sequences."
}
{
"name": "Stephen Dolan",
"message": "Fix example in manual for `floor`. See #155."
}
{
"name": "Nicolas Williams",
"message": "Document floor"
}
- text: |
`.[]` returns each element of the array returned in the response,
one at a time, which are all fed into `{message: .commit.message, name: .commit.committer.name}`.
Data in jq is represented as streams of JSON values - every jq
expression runs for each value in its input stream, and can
produce any number of values to its output stream.
Streams are serialised by just separating JSON values with
whitespace. This is a `cat`-friendly format - you can just join
two JSON streams together and get a valid JSON stream.
If you want to get the output as a single array, you can tell jq to
"collect" all of the answers by wrapping the filter in square
brackets:
- command: "jq '[.[] | {message: .commit.message, name: .commit.committer.name}]'"
result: |
[
{
"name": "Stephen Dolan",
"message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161"
},
{
"name": "Stephen Dolan",
"message": "Reject all overlong UTF8 sequences."
},
{
"name": "Stephen Dolan",
"message": "Fix various UTF8 parsing bugs.\n\nIn particular, parse bad UTF8 by replacing the broken bits with U+FFFD\nand resychronise correctly after broken sequences."
},
{
"name": "Stephen Dolan",
"message": "Fix example in manual for `floor`. See #155."
},
{
"name": "Nicolas Williams",
"message": "Document floor"
}
]
- text: |
- - -
Next, let's try getting the URLs of the parrent commits out of the
API results as well. In each commit, the GitHub API includes information
about "parent" commits. There can be one or many.
"parents": [
{
"html_url": "https://github.com/stedolan/jq/commit/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"url": "https://api.github.com/repos/stedolan/jq/commits/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"sha": "54b9c9bdb225af5d886466d72f47eafc51acb4f7"
},
{
"html_url": "https://github.com/stedolan/jq/commit/8b1b503609c161fea4b003a7179b3fbb2dd4345a",
"url": "https://api.github.com/repos/stedolan/jq/commits/8b1b503609c161fea4b003a7179b3fbb2dd4345a",
"sha": "8b1b503609c161fea4b003a7179b3fbb2dd4345a"
}
]
We want to pull out all of the "html_url" fields inside that array of parent
commits and make a simple list of strings to go along with the
"message" and "author" fields we already have.
- command: "jq '[.[] | {message: .commit.message, name: .commit.committer.name, parents: [.parents[].html_url]}]'"
result: |
[
{
"parents": [
"https://github.com/stedolan/jq/commit/54b9c9bdb225af5d886466d72f47eafc51acb4f7",
"https://github.com/stedolan/jq/commit/8b1b503609c161fea4b003a7179b3fbb2dd4345a"
],
"name": "Stephen Dolan",
"message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161"
},
{
"parents": [
"https://github.com/stedolan/jq/commit/ff48bd6ec538b01d1057be8e93b94eef6914e9ef"
],
"name": "Stephen Dolan",
"message": "Reject all overlong UTF8 sequences."
},
{
"parents": [
"https://github.com/stedolan/jq/commit/54b9c9bdb225af5d886466d72f47eafc51acb4f7"
],
"name": "Stephen Dolan",
"message": "Fix various UTF8 parsing bugs.\n\nIn particular, parse bad UTF8 by replacing the broken bits with U+FFFD\nand resychronise correctly after broken sequences."
},
{
"parents": [
"https://github.com/stedolan/jq/commit/3dcdc582ea993afea3f5503a78a77675967ecdfa"
],
"name": "Stephen Dolan",
"message": "Fix example in manual for `floor`. See #155."
},
{
"parents": [
"https://github.com/stedolan/jq/commit/7c4171d414f647ab08bcd20c76a4d8ed68d9c602"
],
"name": "Nicolas Williams",
"message": "Document floor"
}
]
- text: |
Here we're making an object as before, but this time the `parents`
field is being set to `[.parents[].html_url]}]`, which collects
all of the parent commit urls defined in the parents object.
- text: |
- - -
Here endeth the tutorial! There's lots more to play with, go
read [the manual](../manual/) if you're interested, and [download
jq](../download/) if you haven't already.
+90
View File
@@ -0,0 +1,90 @@
headline: Download jq
body:
- text: |
jq is written in C and has no runtime dependencies, so it should be
possible to build it for nearly any platform. Prebuilt binaries are
available for Linux, OS X and Windows.
The binaries should just run, but on OS X and Linux you may need
to make them executable first using `chmod +x jq`.
jq is licensed under the MIT license. For all of the gory
details, read the file `COPYING` in the source distribution.
### Linux
* Binaries for [64-bit](linux64/jq) or [32-bit](linux32/jq) systems.
* For Arch users, a PKGBUILD for jq-1.1 is in the
[AUR](https://aur.archlinux.org/packages.php?ID=63849), as
well as a PKGBUILD for the HEAD of master
([jq-git](https://aur.archlinux.org/packages.php?ID=63850)).
You can install them using
[Yaourt](https://wiki.archlinux.org/index.php/Yaourt) or
manually by following instructions on [Arch Linux's
Wiki](https://wiki.archlinux.org/index.php/Arch_UseRepository).
* jq 1.2 is [in Debian](http://packages.debian.org/jq)
### OS X
* `brew install jq` using [homebrew](http://mxcl.github.com/homebrew/)
* Or, grab prebuilt [64-bit binaries](osx64/jq) or [32-bit
binaries](osx32/jq)
### Windows
* Executables for [64-bit](win64/jq.exe) or [32-bit](win32/jq.exe)
### From source on Linux or OS X
* [Source tarball for jq $JQ_VERSION](source/jq-$JQ_VERSION.tar.gz)
You can build it using the usual `./configure && make && sudo
make install` rigmarole.
If you're interested in using the lastest development version, try:
git clone https://github.com/stedolan/jq.git
cd jq
autoreconf -i
./configure
make
sudo make install
To build it from a git clone, you'll need to install a few
packages first:
* [Flex](http://www.gnu.org/software/flex/)
* [Bison](http://www.gnu.org/software/bison/)
* [GCC](http://gcc.gnu.org)
* [Make](http://www.gnu.org/software/make)
* [Autotools](http://www.gnu.org/software/automake)
For Linux systems, these will all be in your system's package
manager, and if you do development on the machine they're most
likely already installed.
On OS X, these are all included in Apple's command line tools, which
can be installed from [Xcode](http://developer.apple.com/technologies/tools/).
However, you may find that you need a newer version of Bison than the one provided
by Apple. This can be found in [Homebrew](http://mxcl.github.com/homebrew/)
or [MacPorts](http://macports.org/).
`flex` and `bison` are used to generate the lexer and parser for
jq, and some python scripts generate the UTF8 encoding tables
needed for JSON parsing.
#### Building the documentation
jq's documentation is compiled into static HTML using
[Bonsai](http://www.tinytree.info). To view the documentation
locally, run `rake serve` from the docs/ subdirectory. You'll
need a few Ruby dependencies, which can be installed by
following the instructions in `docs/README.md`.
File diff suppressed because it is too large. Load diff
+39
View File
@@ -0,0 +1,39 @@
headline: jq
blurb: |
jq is a lightweight and flexible command-line JSON processor.
body1: |
jq is like `sed` for JSON data - you can use it to slice and filter
and map and transform structured data with the same ease that `sed`,
`awk`, `grep` and friends let you play with text.
body2: |
jq is written in portable C, and it has zero runtime
dependencies. You can download a single binary, `scp` it to a far away
machine, and expect it to work.
body3: |
jq can mangle the data format that you have into the one that you
want with very little effort, and the program to do so is often
shorter and simpler than you'd expect.
tail: |
Go read the [tutorial](tutorial/) for more, or the [manual](manual/)
for *way* more.
news:
- date: 2014-06-08
body: |
jq 1.4 (finally) released! Get it on the [download](download/) page.
- date: 2013-05-19
body: |
jq 1.3 released.
+22
View File
@@ -0,0 +1,22 @@
jq(1) -- Command-line JSON processor
====================================
## DESCRIPTION
`jq` can transform JSON in various ways, by selecting, iterating,
reducing and otherwise mangling JSON documents.
This version of `jq` was built without a manual, so this manpage is a
stub. For full documentation of the `jq` language, see:
http://stedolan.github.com/jq
## BUGS
Presumably. Report them or discuss them at:
https://github.com/stedolan/jq/issues
## AUTHOR
Stephen Dolan `<mu@netsoc.tcd.ie>`
+28
View File
@@ -0,0 +1,28 @@
DirectoryIndex index.html
FileETag All
# Compress all static assets
<IfModule mod_deflate.c>
# compress content with type html, text, and css
AddOutputFilterByType DEFLATE text/css text/html text/javascript application/javascript application/x-javascript text/js text/plain text/xml
<IfModule mod_headers.c>
# properly handle requests coming from behind proxies
Header append Vary User-Agent
</IfModule>
</IfModule>
# Cache, aggressively
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 10 days"
<FilesMatch "\.(eot|ttf|otf)$">
ExpiresDefault "access plus 10 years"
</filesMatch>
</IfModule>
# Mime-types
AddType application/vnd.ms-fontobject .eot
AddType font/ttf .ttf
AddType font/otf .otf
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,9 @@
/*!
* Bootstrap Responsive v2.1.1
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*maLine truncated
File diff suppressed because it is too large. Load diff
+9
View File
@@ -0,0 +1,9 @@
/*!
* Bootstrap v2.1.1
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluiLine truncated
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large. Load diff
+6
View File
@@ -0,0 +1,6 @@
/*!
* Bootstrap.js by @fat & @mdo
* Copyright 2012 Twitter, Inc.
* http://www.apache.org/licenses/LICENSE-2.0.txt
*/
!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hLine truncated
+99
View File
@@ -0,0 +1,99 @@
@charset "utf-8";
body {
padding-top: 60px;
}
footer {
background-color: #F5F5F5;
padding: 40px 0;
height: 100px;
margin-top: 40px;
color: #999999;
}
section {
padding-top: 30px;
}
.manual-example {
border-top: 1px solid #E5E5E5;
}
.manual-example td {
white-space: pre-wrap;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
}
.manual-example td.jqprogram {
font-weight: bold;
}
.manual-example th {
text-align: right;
padding-right: 10px;
}
.tutorial-example {
position: relative;
margin-bottom: 10px;
}
.tutorial-example pre {
margin-bottom: 0px;
}
.tutorial-example a {
position: absolute;
top: 0px;
right: 0px;
padding: 15px 8px;
color: #777777;
font-weight: bold;
line-height: 10px;
font-size: 12px;
border-left: 1px solid #DDDDDD;
bottom: 0;
display: block;
}
#blurb {
padding-top: 40px;
padding-right: 50px;
}
#blurb p {
font-size: 20pt;
}
#blurb .btn-group {
margin-top: 20px;
}
#multiblurb {
line-height: 1.7;
text-align: center;
font-size: 12pt;
}
#multiblurb code {
border: 0;
font-size: 12pt;
}
h3 code {
border: 0;
font-size: 20px;
}
footer {
text-align: center;
}
#news {
font-size: 12pt;
.date {
font-style: italic;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Allow: /
+18
View File
@@ -0,0 +1,18 @@
# The key value pairs found below are available within the templates.
:url: http://stedolan.github.io/jq
# This line is modified by the Makefile. To change the version number,
# edit the Autoconf version number at the top of configure.ac
jq_version: "1.4"
root: '/jq'
footer: |
This website is made with [Bonsai](http://www.tinytree.info) and
[Twitter Bootstrap](http://twitter.github.com/bootstrap/), themed
with [Bootswatch](http://bootswatch.com).
jq is licensed under the MIT license (code) and the
[CC-BY-3.0](http://creativecommons.org/licenses/by/3.0/) license
(docs).
+34
View File
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
{% include "shared/head" %}
<body id="{{slug}}">
{% include "shared/header" %}
<div class="container">
<div class="row">
<h1>{{headline}}</h1>
{% for item in body %}
{% if item.text %}
{{ item.text | replace: '$JQ_VERSION', jq_version | markdownify }}
{% endif %}
{% if item.command %}
{% capture resultID %}{{ "result" | unique}}{% endcapture %}
<div class="tutorial-example">
<div class="accordion-heading">
<pre>{{item.command}}</pre>
<a data-toggle="collapse" href="#{{resultID}}">Show result</a>
</div>
<div id="{{resultID}}" class="accordion-body collapse"><pre>{{item.result}}</pre></div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% include "shared/footer" %}
</body>
</html>
+51
View File
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
{% include "shared/head" %}
<body id="{{slug}}">
{% include "shared/header" %}
<div class="container">
<div class="row">
<div class="hero-unit">
<img src="{{root}}/jq.png" style="width: 400px; height: 220px; float: left; margin-right: 60px">
<div id="blurb">
{{blurb | markdownify}}
<div class="btn-group" style="float:right">
<a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#">
Download jq-{{jq_version}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="{{root}}/download/linux64/jq">Linux (64-bit)</a></li>
<li><a href="{{root}}/download/osx64/jq">OS X (64-bit)</a></li>
<li><a href="{{root}}/download/">Other platforms and source</a></li>
</ul>
</div>
</div>
<div style="clear:both;"></div>
</div>
</div>
<div class="row" id="multiblurb">
<div class="span4">{{body1 | markdownify}}</div>
<div class="span4">{{body2 | markdownify}}</div>
<div class="span4">{{body3 | markdownify}}</div>
</div>
<div class="row" style="text-align:center; margin-top: 30px">
{{tail | markdownify}}
</div>
<div class="row">
<h2>News</h2>
<div id="news">
<ul>
{% for item in news %}
<li><span class="date">{{ item.date | date: '%m %B %Y' }}</span>{{item.body | markdownify}}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% include "shared/footer" %}
</body>
</html>
+110
View File
@@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
{% include "shared/head" %}
<body id="{{slug}}" data-spy="scroll" data-target="#navcolumn" data-offset="100">
{% include "shared/header" %}
<div class="container">
<div class="row">
<div class="span3" id="navcolumn">
<div class="affix">
<h4>Contents</h4>
<ul class="nav nav-pills nav-stacked">
{% for section in sections %}
<li {% if forloop.first %} class="active" {% endif %}>
<a href="#{{section.title | sanitize}}">{{section.title}}</a>
</li>
{% endfor %}
</ul>
<form class="form-search">
<script type="text/javascript">
var section_map = {
{% for section in sections %}
{% for entry in section.entries %}
{{entry.title | json}} : {{entry.title | sanitize | json}},
{% endfor %}
{{section.title | json}} : {{section.title | sanitize | json}}
{% unless forloop.last %},{% endunless %}
{% endfor %}
};
var section_names = $.map(section_map, function(v,k){return k});
$(function(){
$('#searchbox').typeahead({source: section_names})
$('#searchbox').change(function() {
if ($(this).val() in section_map) {
location.hash = '#' + section_map[$(this).val()];
}
});
});
</script>
<input type="text"
class="input-medium search-query"
placeholder="Search"
autocomplete="off"
id="searchbox">
</form>
</div>
</div>
<div class="span9">
<h1>{{headline}}</h1>
{{ body | markdownify }}
{% for section in sections %}
<section id="{{section.title | sanitize}}">
<h2>{{section.title}}</h2>
{{section.body | markdownify}}
{% for entry in section.entries %}
<section id="{{entry.title | sanitize}}">
<h3>
{{entry.title | markdownify}}
{% if entry.subtitle %}<small>{{entry.subtitle}}</small>{% endif %}
</h3>
{{entry.body | markdownify}}
{% if entry.examples %}
<div>
{% capture exampleID %}{{ "" | unique }}{% endcapture %}
<a data-toggle="collapse" href="#example{{exampleID}}">
<i class="icon-chevron-right"></i>
{% if entry.examples[1] %}Examples{%else%}Example{%endif%}
</a>
<div id="example{{exampleID}}" class="collapse">
{% for example in entry.examples %}
<table class="manual-example">
<tr><th></th><td class="jqprogram">jq '{{example.program}}'</td></tr>
<tr><th>Input</th><td>{{example.input}}</td></tr>
{% unless example.output[0] %}
<tr>
<th>Output</th>
<td><i>none</i></td>
</tr>
{% endunless %}
{% for output in example.output %}
<tr>
{% if forloop.index == 1 %}
<th>Output</th>
{% else %}
<th></th>
{% endif %}
<td>{{output}}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
</div>
</div>
{% endif %}
</section>
{% endfor %}
</section>
{% endfor %}
</div>
</div>
</div>
{% include "shared/footer" %}
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
<footer>
<div class="container">
{{ footer | markdownify }}
</div>
</footer>
+12
View File
@@ -0,0 +1,12 @@
<head>
<meta charset=UTF-8>
<title>{{headline}}</title>
<link rel="stylesheet" href="{{root}}/bootstrap/css/bootstrap.css" type="text/css">
<link rel="stylesheet" href="{{root}}/css/base.css" type="text/css">
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="{{root}}/bootstrap/js/bootstrap.js"></script>
</head>
+24
View File
@@ -0,0 +1,24 @@
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="{{root}}/">jq</a>
<div class="nav-collapse collapse">
<ul class="nav">
{% for item in navigation %}
<li {% if item.permalink == permalink %} class="active" {% endif %}>
<a href="{{root}}{{item.permalink}}">{{item.name}}</a>
</li>
{% endfor %}
<li><a href="https://github.com/stedolan/jq/issues">Issues</a></li>
<li><a href="https://github.com/stedolan/jq">Source</a></li>
</ul>
</div>
</div>
</div>
</div>
+82
View File
@@ -0,0 +1,82 @@
#ifndef EXEC_STACK_H
#define EXEC_STACK_H
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
struct determine_alignment {
char x;
union { int i; double d; uint64_t u64; size_t sz; void* ptr; } u;
};
enum {ALIGNMENT = offsetof(struct determine_alignment, u)};
static size_t align_round_up(size_t sz) {
return ((sz + (ALIGNMENT - 1)) / ALIGNMENT) * ALIGNMENT;
}
typedef int stack_ptr;
struct stack {
char* mem_end; // one-past-the-end of allocated region
stack_ptr bound;
stack_ptr limit; // 0 - stack is empty
};
static void stack_init(struct stack* s) {
s->mem_end = 0;
s->bound = ALIGNMENT;
s->limit = 0;
}
static void stack_reset(struct stack* s) {
assert(s->limit == 0 && "stack freed while not empty");
char* mem_start = s->mem_end - ( -s->bound + ALIGNMENT);
free(mem_start);
stack_init(s);
}
static int stack_pop_will_free(struct stack* s, stack_ptr p) {
return p == s->limit;
}
static void* stack_block(struct stack* s, stack_ptr p) {
return (void*)(s->mem_end + p);
}
static stack_ptr* stack_block_next(struct stack* s, stack_ptr p) {
return &((stack_ptr*)stack_block(s, p))[-1];
}
static void stack_reallocate(struct stack* s, size_t sz) {
int old_mem_length = -(s->bound) + ALIGNMENT;
char* old_mem_start = s->mem_end - old_mem_length;
int new_mem_length = align_round_up((old_mem_length + sz + 256) * 2);
char* new_mem_start = realloc(old_mem_start, new_mem_length);
memmove(new_mem_start + (new_mem_length - old_mem_length),
new_mem_start, old_mem_length);
s->mem_end = new_mem_start + new_mem_length;
s->bound = -(new_mem_length - ALIGNMENT);
}
static stack_ptr stack_push_block(struct stack* s, stack_ptr p, size_t sz) {
int alloc_sz = align_round_up(sz) + ALIGNMENT;
stack_ptr r = s->limit - alloc_sz;
if (r < s->bound) {
stack_reallocate(s, alloc_sz);
}
s->limit = r;
*stack_block_next(s, r) = p;
return r;
}
static stack_ptr stack_pop_block(struct stack* s, stack_ptr p, size_t sz) {
stack_ptr r = *stack_block_next(s, p);
if (p == s->limit) {
int alloc_sz = align_round_up(sz) + ALIGNMENT;
s->limit += alloc_sz;
}
return r;
}
#endif
+809
View File
@@ -0,0 +1,809 @@
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "exec_stack.h"
#include "bytecode.h"
#include "jv_alloc.h"
#include "jq_parser.h"
#include "locfile.h"
#include "jv.h"
#include "jq.h"
#include "parser.h"
#include "builtin.h"
struct jq_state {
void (*nomem_handler)(void *);
void *nomem_handler_data;
struct bytecode* bc;
jq_err_cb err_cb;
void *err_cb_data;
struct stack stk;
stack_ptr curr_frame;
stack_ptr stk_top;
stack_ptr fork_top;
jv path;
int subexp_nest;
int debug_trace_enabled;
int initial_execution;
};
struct closure {
struct bytecode* bc;
stack_ptr env;
};
union frame_entry {
struct closure closure;
jv localvar;
};
struct frame {
struct bytecode* bc;
stack_ptr env;
stack_ptr retdata;
uint16_t* retaddr;
/* bc->nclosures closures followed by bc->nlocals local variables */
union frame_entry entries[0];
};
static int frame_size(struct bytecode* bc) {
return sizeof(struct frame) + sizeof(union frame_entry) * (bc->nclosures + bc->nlocals);
}
static struct frame* frame_current(struct jq_state* jq) {
struct frame* fp = stack_block(&jq->stk, jq->curr_frame);
stack_ptr next = *stack_block_next(&jq->stk, jq->curr_frame);
if (next) {
struct frame* fpnext = stack_block(&jq->stk, next);
struct bytecode* bc = fpnext->bc;
assert(fp->retaddr >= bc->code && fp->retaddr < bc->code + bc->codelen);
} else {
assert(fp->retaddr == 0);
}
return fp;
}
static stack_ptr frame_get_level(struct jq_state* jq, int level) {
stack_ptr fr = jq->curr_frame;
for (int i=0; i<level; i++) {
struct frame* fp = stack_block(&jq->stk, fr);
fr = fp->env;
}
return fr;
}
static jv* frame_local_var(struct jq_state* jq, int var, int level) {
struct frame* fr = stack_block(&jq->stk, frame_get_level(jq, level));
assert(var >= 0);
assert(var < fr->bc->nlocals);
return &fr->entries[fr->bc->nclosures + var].localvar;
}
static struct closure make_closure(struct jq_state* jq, uint16_t* pc) {
uint16_t level = *pc++;
uint16_t idx = *pc++;
stack_ptr fridx = frame_get_level(jq, level);
struct frame* fr = stack_block(&jq->stk, fridx);
if (idx & ARG_NEWCLOSURE) {
int subfn_idx = idx & ~ARG_NEWCLOSURE;
assert(subfn_idx < fr->bc->nsubfunctions);
struct closure cl = {fr->bc->subfunctions[subfn_idx],
fridx};
return cl;
} else {
int closure = idx;
assert(closure >= 0);
assert(closure < fr->bc->nclosures);
return fr->entries[closure].closure;
}
}
static struct frame* frame_push(struct jq_state* jq, struct closure callee,
uint16_t* argdef, int nargs) {
stack_ptr new_frame_idx = stack_push_block(&jq->stk, jq->curr_frame, frame_size(callee.bc));
struct frame* new_frame = stack_block(&jq->stk, new_frame_idx);
new_frame->bc = callee.bc;
new_frame->env = callee.env;
assert(nargs == new_frame->bc->nclosures);
union frame_entry* entries = new_frame->entries;
for (int i=0; i<nargs; i++) {
entries->closure = make_closure(jq, argdef + i * 2);
entries++;
}
for (int i=0; i<callee.bc->nlocals; i++) {
entries->localvar = jv_invalid();
entries++;
}
jq->curr_frame = new_frame_idx;
return new_frame;
}
static void frame_pop(struct jq_state* jq) {
assert(jq->curr_frame);
struct frame* fp = frame_current(jq);
if (stack_pop_will_free(&jq->stk, jq->curr_frame)) {
int nlocals = fp->bc->nlocals;
for (int i=0; i<nlocals; i++) {
jv_free(*frame_local_var(jq, i, 0));
}
}
jq->curr_frame = stack_pop_block(&jq->stk, jq->curr_frame, frame_size(fp->bc));
}
void stack_push(jq_state *jq, jv val) {
assert(jv_is_valid(val));
jq->stk_top = stack_push_block(&jq->stk, jq->stk_top, sizeof(jv));
jv* sval = stack_block(&jq->stk, jq->stk_top);
*sval = val;
}
jv stack_pop(jq_state *jq) {
jv* sval = stack_block(&jq->stk, jq->stk_top);
jv val = *sval;
if (!stack_pop_will_free(&jq->stk, jq->stk_top)) {
val = jv_copy(val);
}
jq->stk_top = stack_pop_block(&jq->stk, jq->stk_top, sizeof(jv));
assert(jv_is_valid(val));
return val;
}
struct forkpoint {
stack_ptr saved_data_stack;
stack_ptr saved_curr_frame;
int path_len, subexp_nest;
uint16_t* return_address;
};
struct stack_pos {
stack_ptr saved_data_stack, saved_curr_frame;
};
struct stack_pos stack_get_pos(jq_state* jq) {
struct stack_pos sp = {jq->stk_top, jq->curr_frame};
return sp;
}
void stack_save(jq_state *jq, uint16_t* retaddr, struct stack_pos sp){
jq->fork_top = stack_push_block(&jq->stk, jq->fork_top, sizeof(struct forkpoint));
struct forkpoint* fork = stack_block(&jq->stk, jq->fork_top);
fork->saved_data_stack = jq->stk_top;
fork->saved_curr_frame = jq->curr_frame;
fork->path_len =
jv_get_kind(jq->path) == JV_KIND_ARRAY ? jv_array_length(jv_copy(jq->path)) : 0;
fork->subexp_nest = jq->subexp_nest;
fork->return_address = retaddr;
jq->stk_top = sp.saved_data_stack;
jq->curr_frame = sp.saved_curr_frame;
}
void path_append(jq_state* jq, jv component) {
if (jq->subexp_nest == 0 && jv_get_kind(jq->path) == JV_KIND_ARRAY) {
int n1 = jv_array_length(jv_copy(jq->path));
jq->path = jv_array_append(jq->path, component);
int n2 = jv_array_length(jv_copy(jq->path));
assert(n2 == n1 + 1);
} else {
jv_free(component);
}
}
uint16_t* stack_restore(jq_state *jq){
while (!stack_pop_will_free(&jq->stk, jq->fork_top)) {
if (stack_pop_will_free(&jq->stk, jq->stk_top)) {
jv_free(stack_pop(jq));
} else if (stack_pop_will_free(&jq->stk, jq->curr_frame)) {
frame_pop(jq);
} else {
assert(0);
}
}
if (jq->fork_top == 0) {
return 0;
}
struct forkpoint* fork = stack_block(&jq->stk, jq->fork_top);
uint16_t* retaddr = fork->return_address;
jq->stk_top = fork->saved_data_stack;
jq->curr_frame = fork->saved_curr_frame;
int path_len = fork->path_len;
if (jv_get_kind(jq->path) == JV_KIND_ARRAY) {
assert(path_len >= 0);
jq->path = jv_array_slice(jq->path, 0, path_len);
} else {
assert(path_len == 0);
}
jq->subexp_nest = fork->subexp_nest;
jq->fork_top = stack_pop_block(&jq->stk, jq->fork_top, sizeof(struct forkpoint));
return retaddr;
}
static void jq_reset(jq_state *jq) {
while (stack_restore(jq)) {}
assert(jq->stk_top == 0);
assert(jq->fork_top == 0);
assert(jq->curr_frame == 0);
stack_reset(&jq->stk);
if (jv_get_kind(jq->path) != JV_KIND_INVALID)
jv_free(jq->path);
jq->path = jv_null();
jq->subexp_nest = 0;
}
static void print_error(jq_state *jq, jv value) {
assert(!jv_is_valid(value));
jv msg = jv_invalid_get_msg(value);
jv msg2;
if (jv_get_kind(msg) == JV_KIND_STRING)
msg2 = jv_string_fmt("jq: error: %s", jv_string_value(msg));
else
msg2 = jv_string_fmt("jq: error: <unknown>");
jv_free(msg);
if (jq->err_cb)
jq->err_cb(jq->err_cb_data, msg2);
else if (jv_get_kind(msg2) == JV_KIND_STRING)
fprintf(stderr, "%s\n", jv_string_value(msg2));
else
fprintf(stderr, "jq: error: %s\n", strerror(ENOMEM));
jv_free(msg2);
}
#define ON_BACKTRACK(op) ((op)+NUM_OPCODES)
jv jq_next(jq_state *jq) {
jv cfunc_input[MAX_CFUNCTION_ARGS];
jv_nomem_handler(jq->nomem_handler, jq->nomem_handler_data);
uint16_t* pc = stack_restore(jq);
assert(pc);
int backtracking = !jq->initial_execution;
jq->initial_execution = 0;
while (1) {
uint16_t opcode = *pc;
if (jq->debug_trace_enabled) {
dump_operation(frame_current(jq)->bc, pc);
printf("\t");
const struct opcode_description* opdesc = opcode_describe(opcode);
stack_ptr param = 0;
if (!backtracking) {
int stack_in = opdesc->stack_in;
if (stack_in == -1) stack_in = pc[1];
for (int i=0; i<stack_in; i++) {
if (i == 0) {
param = jq->stk_top;
} else {
printf(" | ");
param = *stack_block_next(&jq->stk, param);
}
if (!param) break;
jv_dump(jv_copy(*(jv*)stack_block(&jq->stk, param)), 0);
//printf("<%d>", jv_get_refcnt(param->val));
//printf(" -- ");
//jv_dump(jv_copy(jq->path), 0);
}
} else {
printf("\t<backtracking>");
}
printf("\n");
}
if (backtracking) {
opcode = ON_BACKTRACK(opcode);
backtracking = 0;
}
pc++;
switch (opcode) {
default: assert(0 && "invalid instruction");
case LOADK: {
jv v = jv_array_get(jv_copy(frame_current(jq)->bc->constants), *pc++);
assert(jv_is_valid(v));
jv_free(stack_pop(jq));
stack_push(jq, v);
break;
}
case DUP: {
jv v = stack_pop(jq);
stack_push(jq, jv_copy(v));
stack_push(jq, v);
break;
}
case DUP2: {
jv keep = stack_pop(jq);
jv v = stack_pop(jq);
stack_push(jq, jv_copy(v));
stack_push(jq, keep);
stack_push(jq, v);
break;
}
case SUBEXP_BEGIN: {
jv v = stack_pop(jq);
stack_push(jq, jv_copy(v));
stack_push(jq, v);
jq->subexp_nest++;
break;
}
case SUBEXP_END: {
assert(jq->subexp_nest > 0);
jq->subexp_nest--;
jv a = stack_pop(jq);
jv b = stack_pop(jq);
stack_push(jq, a);
stack_push(jq, b);
break;
}
case POP: {
jv_free(stack_pop(jq));
break;
}
case APPEND: {
jv v = stack_pop(jq);
uint16_t level = *pc++;
uint16_t vidx = *pc++;
jv* var = frame_local_var(jq, vidx, level);
assert(jv_get_kind(*var) == JV_KIND_ARRAY);
*var = jv_array_append(*var, v);
break;
}
case INSERT: {
jv stktop = stack_pop(jq);
jv v = stack_pop(jq);
jv k = stack_pop(jq);
jv objv = stack_pop(jq);
assert(jv_get_kind(objv) == JV_KIND_OBJECT);
if (jv_get_kind(k) == JV_KIND_STRING) {
stack_push(jq, jv_object_set(objv, k, v));
stack_push(jq, stktop);
} else {
print_error(jq, jv_invalid_with_msg(jv_string_fmt("Cannot use %s as object key",
jv_kind_name(jv_get_kind(k)))));
jv_free(stktop);
jv_free(v);
jv_free(k);
jv_free(objv);
goto do_backtrack;
}
break;
}
case ON_BACKTRACK(RANGE):
case RANGE: {
uint16_t level = *pc++;
uint16_t v = *pc++;
jv* var = frame_local_var(jq, v, level);
jv max = stack_pop(jq);
if (jv_get_kind(*var) != JV_KIND_NUMBER ||
jv_get_kind(max) != JV_KIND_NUMBER) {
print_error(jq, jv_invalid_with_msg(jv_string_fmt("Range bounds must be numeric")));
jv_free(max);
goto do_backtrack;
} else if (jv_number_value(jv_copy(*var)) >= jv_number_value(jv_copy(max))) {
/* finished iterating */
goto do_backtrack;
} else {
jv curr = jv_copy(*var);
*var = jv_number(jv_number_value(*var) + 1);
struct stack_pos spos = stack_get_pos(jq);
stack_push(jq, jv_copy(max));
stack_save(jq, pc - 3, spos);
stack_push(jq, curr);
}
break;
}
// FIXME: loadv/storev may do too much copying/freeing
case LOADV: {
uint16_t level = *pc++;
uint16_t v = *pc++;
jv* var = frame_local_var(jq, v, level);
if (jq->debug_trace_enabled) {
printf("V%d = ", v);
jv_dump(jv_copy(*var), 0);
printf("\n");
}
jv_free(stack_pop(jq));
stack_push(jq, jv_copy(*var));
break;
}
// Does a load but replaces the variable with null
case LOADVN: {
uint16_t level = *pc++;
uint16_t v = *pc++;
jv* var = frame_local_var(jq, v, level);
if (jq->debug_trace_enabled) {
printf("V%d = ", v);
jv_dump(jv_copy(*var), 0);
printf("\n");
}
jv_free(stack_pop(jq));
stack_push(jq, *var);
*var = jv_null();
break;
}
case STOREV: {
uint16_t level = *pc++;
uint16_t v = *pc++;
jv* var = frame_local_var(jq, v, level);
jv val = stack_pop(jq);
if (jq->debug_trace_enabled) {
printf("V%d = ", v);
jv_dump(jv_copy(val), 0);
printf("\n");
}
jv_free(*var);
*var = val;
break;
}
case PATH_BEGIN: {
jv v = stack_pop(jq);
stack_push(jq, jq->path);
stack_save(jq, pc - 1, stack_get_pos(jq));
stack_push(jq, jv_number(jq->subexp_nest));
stack_push(jq, v);
jq->path = jv_array();
jq->subexp_nest = 0;
break;
}
case PATH_END: {
jv v = stack_pop(jq);
jv_free(v); // discard value, only keep path
int old_subexp_nest = (int)jv_number_value(stack_pop(jq));
jv path = jq->path;
jq->path = stack_pop(jq);
struct stack_pos spos = stack_get_pos(jq);
stack_push(jq, jv_copy(path));
stack_save(jq, pc - 1, spos);
stack_push(jq, path);
jq->subexp_nest = old_subexp_nest;
break;
}
case ON_BACKTRACK(PATH_BEGIN):
case ON_BACKTRACK(PATH_END): {
jv_free(jq->path);
jq->path = stack_pop(jq);
goto do_backtrack;
}
case INDEX:
case INDEX_OPT: {
jv t = stack_pop(jq);
jv k = stack_pop(jq);
path_append(jq, jv_copy(k));
jv v = jv_get(t, k);
if (jv_is_valid(v)) {
stack_push(jq, v);
} else {
if (opcode == INDEX)
print_error(jq, v);
else
jv_free(v);
goto do_backtrack;
}
break;
}
case JUMP: {
uint16_t offset = *pc++;
pc += offset;
break;
}
case JUMP_F: {
uint16_t offset = *pc++;
jv t = stack_pop(jq);
jv_kind kind = jv_get_kind(t);
if (kind == JV_KIND_FALSE || kind == JV_KIND_NULL) {
pc += offset;
}
stack_push(jq, t); // FIXME do this better
break;
}
case EACH:
case EACH_OPT:
stack_push(jq, jv_number(-1));
// fallthrough
case ON_BACKTRACK(EACH):
case ON_BACKTRACK(EACH_OPT): {
int idx = jv_number_value(stack_pop(jq));
jv container = stack_pop(jq);
int keep_going, is_last = 0;
jv key, value;
if (jv_get_kind(container) == JV_KIND_ARRAY) {
if (opcode == EACH || opcode == EACH_OPT) idx = 0;
else idx = idx + 1;
int len = jv_array_length(jv_copy(container));
keep_going = idx < len;
is_last = idx == len - 1;
if (keep_going) {
key = jv_number(idx);
value = jv_array_get(jv_copy(container), idx);
}
} else if (jv_get_kind(container) == JV_KIND_OBJECT) {
if (opcode == EACH || opcode == EACH_OPT) idx = jv_object_iter(container);
else idx = jv_object_iter_next(container, idx);
keep_going = jv_object_iter_valid(container, idx);
if (keep_going) {
key = jv_object_iter_key(container, idx);
value = jv_object_iter_value(container, idx);
}
} else {
assert(opcode == EACH || opcode == EACH_OPT);
if (opcode == EACH) {
print_error(jq,
jv_invalid_with_msg(jv_string_fmt("Cannot iterate over %s",
jv_kind_name(jv_get_kind(container)))));
}
keep_going = 0;
}
if (!keep_going) {
jv_free(container);
goto do_backtrack;
} else if (is_last) {
// we don't need to make a backtrack point
jv_free(container);
path_append(jq, key);
stack_push(jq, value);
} else {
struct stack_pos spos = stack_get_pos(jq);
stack_push(jq, container);
stack_push(jq, jv_number(idx));
stack_save(jq, pc - 1, spos);
path_append(jq, key);
stack_push(jq, value);
}
break;
}
do_backtrack:
case BACKTRACK: {
pc = stack_restore(jq);
if (!pc) {
return jv_invalid();
}
backtracking = 1;
break;
}
case FORK: {
stack_save(jq, pc - 1, stack_get_pos(jq));
pc++; // skip offset this time
break;
}
case ON_BACKTRACK(FORK): {
uint16_t offset = *pc++;
pc += offset;
break;
}
case CALL_BUILTIN: {
int nargs = *pc++;
jv top = stack_pop(jq);
jv* in = cfunc_input;
in[0] = top;
for (int i = 1; i < nargs; i++) {
in[i] = stack_pop(jq);
}
struct cfunction* function = &frame_current(jq)->bc->globals->cfunctions[*pc++];
typedef jv (*func_1)(jv);
typedef jv (*func_2)(jv,jv);
typedef jv (*func_3)(jv,jv,jv);
typedef jv (*func_4)(jv,jv,jv,jv);
typedef jv (*func_5)(jv,jv,jv,jv,jv);
switch (function->nargs) {
case 1: top = ((func_1)function->fptr)(in[0]); break;
case 2: top = ((func_2)function->fptr)(in[0], in[1]); break;
case 3: top = ((func_3)function->fptr)(in[0], in[1], in[2]); break;
case 4: top = ((func_4)function->fptr)(in[0], in[1], in[2], in[3]); break;
case 5: top = ((func_5)function->fptr)(in[0], in[1], in[2], in[3], in[4]); break;
default: return jv_invalid_with_msg(jv_string("Function takes too many arguments"));
}
if (jv_is_valid(top)) {
stack_push(jq, top);
} else {
print_error(jq, top);
goto do_backtrack;
}
break;
}
case CALL_JQ: {
jv input = stack_pop(jq);
uint16_t nclosures = *pc++;
uint16_t* retaddr = pc + 2 + nclosures*2;
struct frame* new_frame = frame_push(jq, make_closure(jq, pc),
pc + 2, nclosures);
new_frame->retdata = jq->stk_top;
new_frame->retaddr = retaddr;
pc = new_frame->bc->code;
stack_push(jq, input);
break;
}
case RET: {
jv value = stack_pop(jq);
assert(jq->stk_top == frame_current(jq)->retdata);
uint16_t* retaddr = frame_current(jq)->retaddr;
if (retaddr) {
// function return
pc = retaddr;
frame_pop(jq);
} else {
// top-level return, yielding value
struct stack_pos spos = stack_get_pos(jq);
stack_push(jq, jv_null());
stack_save(jq, pc - 1, spos);
return value;
}
stack_push(jq, value);
break;
}
case ON_BACKTRACK(RET): {
// resumed after top-level return
goto do_backtrack;
}
}
}
}
jq_state *jq_init(void) {
jq_state *jq;
jq = jv_mem_alloc_unguarded(sizeof(*jq));
if (jq == NULL)
return NULL;
jq->bc = 0;
stack_init(&jq->stk);
jq->stk_top = 0;
jq->fork_top = 0;
jq->curr_frame = 0;
jq->err_cb = NULL;
jq->err_cb_data = NULL;
jq->path = jv_null();
return jq;
}
void jq_set_error_cb(jq_state *jq, jq_err_cb cb, void *data) {
jq->err_cb = cb;
jq->err_cb_data = data;
}
void jq_get_error_cb(jq_state *jq, jq_err_cb *cb, void **data) {
*cb = jq->err_cb;
*data = jq->err_cb_data;
}
void jq_set_nomem_handler(jq_state *jq, void (*nomem_handler)(void *), void *data) {
jv_nomem_handler(nomem_handler, data);
jq->nomem_handler = nomem_handler;
jq->nomem_handler_data = data;
}
void jq_start(jq_state *jq, jv input, int flags) {
jv_nomem_handler(jq->nomem_handler, jq->nomem_handler_data);
jq_reset(jq);
struct closure top = {jq->bc, -1};
struct frame* top_frame = frame_push(jq, top, 0, 0);
top_frame->retdata = 0;
top_frame->retaddr = 0;
stack_push(jq, input);
stack_save(jq, jq->bc->code, stack_get_pos(jq));
if (flags & JQ_DEBUG_TRACE) {
jq->debug_trace_enabled = 1;
} else {
jq->debug_trace_enabled = 0;
}
jq->initial_execution = 1;
}
void jq_teardown(jq_state **jq) {
jq_state *old_jq = *jq;
if (old_jq == NULL)
return;
*jq = NULL;
jq_reset(old_jq);
bytecode_free(old_jq->bc);
old_jq->bc = 0;
jv_mem_free(old_jq);
}
int jq_compile_args(jq_state *jq, const char* str, jv args) {
jv_nomem_handler(jq->nomem_handler, jq->nomem_handler_data);
assert(jv_get_kind(args) == JV_KIND_ARRAY);
struct locfile locations;
locfile_init(&locations, jq, str, strlen(str));
block program;
jq_reset(jq);
if (jq->bc) {
bytecode_free(jq->bc);
jq->bc = 0;
}
int nerrors = jq_parse(&locations, &program);
if (nerrors == 0) {
for (int i=0; i<jv_array_length(jv_copy(args)); i++) {
jv arg = jv_array_get(jv_copy(args), i);
jv name = jv_object_get(jv_copy(arg), jv_string("name"));
jv value = jv_object_get(arg, jv_string("value"));
program = gen_var_binding(gen_const(value), jv_string_value(name), program);
jv_free(name);
}
nerrors = builtins_bind(jq, &program);
if (nerrors == 0) {
nerrors = block_compile(program, &locations, &jq->bc);
}
}
jv_free(args);
if (nerrors) {
jv s = jv_string_fmt("%d compile %s", nerrors,
nerrors > 1 ? "errors" : "error");
if (jq->err_cb != NULL)
jq->err_cb(jq->err_cb_data, s);
else if (!jv_is_valid(s))
fprintf(stderr, "Error formatting jq compilation errors: %s\n", strerror(errno));
else
fprintf(stderr, "%s\n", jv_string_value(s));
jv_free(s);
}
locfile_free(&locations);
return jq->bc != NULL;
}
int jq_compile(jq_state *jq, const char* str) {
return jq_compile_args(jq, str, jv_array());
}
void jq_dump_disassembly(jq_state *jq, int indent) {
dump_disassembly(indent, jq->bc);
}
+39
View File
@@ -0,0 +1,39 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "JQ" "1" "May 2013" "" ""
.
.SH "NAME"
\fBjq\fR \- Command\-line JSON processor
.
.SH "DESCRIPTION"
\fBjq\fR can transform JSON in various ways, by selecting, iterating, reducing and otherwise mangling JSON documents\.
.
.P
This version of \fBjq\fR was built without a manual, so this manpage is a stub\. For full documentation of the \fBjq\fR language, see:
.
.IP "" 4
.
.nf
http://stedolan\.github\.com/jq
.
.fi
.
.IP "" 0
.
.SH "BUGS"
Presumably\. Report them or discuss them at:
.
.IP "" 4
.
.nf
https://github\.com/stedolan/jq/issues
.
.fi
.
.IP "" 0
.
.SH "AUTHOR"
Stephen Dolan \fB<mu@netsoc\.tcd\.ie>\fR
+23
View File
@@ -0,0 +1,23 @@
#ifndef _JQ_H_
#define _JQ_H_
#include <stdio.h>
#include <jv.h>
enum {JQ_DEBUG_TRACE = 1};
typedef struct jq_state jq_state;
typedef void (*jq_err_cb)(void *, jv);
jq_state *jq_init(void);
void jq_set_error_cb(jq_state *, jq_err_cb, void *);
void jq_get_error_cb(jq_state *, jq_err_cb *, void **);
void jq_set_nomem_handler(jq_state *, void (*)(void *), void *);
int jq_compile(jq_state *, const char* str);
int jq_compile_args(jq_state *, const char* str, jv args);
void jq_dump_disassembly(jq_state *, int);
void jq_start(jq_state *, jv value, int flags);
jv jq_next(jq_state *);
void jq_teardown(jq_state **);
#endif /* !_JQ_H_ */
+70
View File
@@ -0,0 +1,70 @@
# This is spec file maintained by developers of JQ, not by a OS distro.
# Your OS of choice will likely ignore this RPM spec file.
Summary: Command-line JSON processor
Name: jq
Version: %{myver}
Release: %{myrel}%{?dist}
Source0: jq-%{myver}.tar.gz
URL: https://github.com/stedolan/jq
License: BSD
AutoReqProv: no
#BuildPrereq: autoconf, libtool, automake, flex, bison, python
Group: Applications/System
# Requires:
# Disables debug packages and stripping of binaries:
%global _enable_debug_package 0
%global debug_package %{nil}
%global __os_install_post %{nil}
# Crank up the compression
%define _binary_payload w7.lzdio
%description
jq is a command-line JSON processor
%prep
%setup
%build
echo "Building in: \"$(pwd)\""
%if "%{devbuild}" == "yes"
./configure --prefix=%{_prefix} --enable-devel
%else
./configure --prefix=%{_prefix}
%endif
make
%install
echo "Installing to: \"%{buildroot}\""
make install DESTDIR=%{buildroot}
%clean
rm -rf %{buildroot}
%files
%defattr(-,root,root)
%{_bindir}/jq
%if "%{devbuild}" == "yes"
%{_libexecdir}/%{name}/jq_test
%{_libexecdir}/%{name}/testdata
%endif
%{_datadir}/doc/%{name}/AUTHORS
%{_datadir}/doc/%{name}/COPYING
%{_datadir}/doc/%{name}/README
%{_datadir}/doc/%{name}/README.md
%{_datadir}/man/man1/jq.1
%{_includedir}/jq.h
%{_includedir}/jv.h
%{_prefix}/lib/libjq.a
%{_prefix}/lib/libjq.la
%{_prefix}/lib/libjq.so
%{_prefix}/lib/libjq.so.1
%{_prefix}/lib/libjq.so.1.0.4
%changelog
%pre
%post
+9
View File
@@ -0,0 +1,9 @@
#ifndef JQ_PARSER_H
#define JQ_PARSER_H
#include "locfile.h"
#include "compile.h"
int jq_parse(struct locfile* source, block* answer);
int jq_parse_library(struct locfile* locations, block* answer);
#endif
+262
View File
@@ -0,0 +1,262 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "jv.h"
#include "jq.h"
static void jv_test();
static void run_jq_tests();
int jq_testsuite(int argc, char* argv[]) {
FILE *testdata = stdin;
jv_test();
if (argc > 2) {
testdata = fopen(argv[2], "r");
if (!testdata) {
perror("fopen");
exit(1);
}
}
run_jq_tests(testdata);
return 0;
}
static int skipline(const char* buf) {
int p = 0;
while (buf[p] == ' ' || buf[p] == '\t') p++;
if (buf[p] == '#' || buf[p] == '\n' || buf[p] == 0) return 1;
return 0;
}
static void run_jq_tests(FILE *testdata) {
char buf[4096];
int tests = 0, passed = 0, invalid = 0;
jq_state *jq = NULL;
jq = jq_init();
assert(jq);
while (1) {
if (!fgets(buf, sizeof(buf), testdata)) break;
if (skipline(buf)) continue;
if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = 0;
printf("Testing %s\n", buf);
int pass = 1;
tests++;
int compiled = jq_compile(jq, buf);
if (!compiled) {invalid++; continue;}
printf("Disassembly:\n");
jq_dump_disassembly(jq, 2);
printf("\n");
if (!fgets(buf, sizeof(buf), testdata)) { invalid++; break; }
jv input = jv_parse(buf);
if (!jv_is_valid(input)){ invalid++; continue; }
jq_start(jq, input, JQ_DEBUG_TRACE);
while (fgets(buf, sizeof(buf), testdata)) {
if (skipline(buf)) break;
jv expected = jv_parse(buf);
if (!jv_is_valid(expected)){ invalid++; continue; }
jv actual = jq_next(jq);
if (!jv_is_valid(actual)) {
jv_free(actual);
printf("*** Insufficient results\n");
pass = 0;
break;
} else if (!jv_equal(jv_copy(expected), jv_copy(actual))) {
printf("*** Expected ");
jv_dump(jv_copy(expected), 0);
printf(", but got ");
jv_dump(jv_copy(actual), 0);
printf("\n");
pass = 0;
}
jv as_string = jv_dump_string(jv_copy(expected), rand() & ~JV_PRINT_COLOUR);
jv reparsed = jv_parse_sized(jv_string_value(as_string), jv_string_length_bytes(jv_copy(as_string)));
assert(jv_equal(jv_copy(expected), jv_copy(reparsed)));
jv_free(as_string);
jv_free(reparsed);
jv_free(expected);
jv_free(actual);
}
if (pass) {
jv extra = jq_next(jq);
if (jv_is_valid(extra)) {
printf("*** Superfluous result: ");
jv_dump(extra, 0);
printf("\n");
pass = 0;
} else {
jv_free(extra);
}
}
passed+=pass;
}
jq_teardown(&jq);
printf("%d of %d tests passed (%d malformed)\n", passed,tests,invalid);
if (passed != tests) exit(1);
}
static void jv_test() {
/// Arrays and numbers
{
jv a = jv_array();
assert(jv_get_kind(a) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(a)) == 0);
assert(jv_array_length(jv_copy(a)) == 0);
a = jv_array_append(a, jv_number(42));
assert(jv_array_length(jv_copy(a)) == 1);
assert(jv_number_value(jv_array_get(jv_copy(a), 0)) == 42);
jv a2 = jv_array_append(jv_array(), jv_number(42));
assert(jv_equal(jv_copy(a), jv_copy(a)));
assert(jv_equal(jv_copy(a2), jv_copy(a2)));
assert(jv_equal(jv_copy(a), jv_copy(a2)));
assert(jv_equal(jv_copy(a2), jv_copy(a)));
jv_free(a2);
a2 = jv_array_append(jv_array(), jv_number(19));
assert(!jv_equal(jv_copy(a), jv_copy(a2)));
assert(!jv_equal(jv_copy(a2), jv_copy(a)));
jv_free(a2);
assert(jv_get_refcnt(a) == 1);
a = jv_array_append(a, jv_copy(a));
assert(jv_get_refcnt(a) == 1);
assert(jv_array_length(jv_copy(a)) == 2);
assert(jv_number_value(jv_array_get(jv_copy(a), 0)) == 42);
for (int i=0; i<10; i++) {
jv subarray = jv_array_get(jv_copy(a), 1);
assert(jv_get_kind(subarray) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(subarray)) == 1);
assert(jv_number_value(jv_array_get(jv_copy(subarray), 0)) == 42);
jv_free(subarray);
}
jv subarray = jv_array_get(jv_copy(a), 1);
assert(jv_get_kind(subarray) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(subarray)) == 1);
assert(jv_number_value(jv_array_get(jv_copy(subarray), 0)) == 42);
jv sub2 = jv_copy(subarray);
sub2 = jv_array_append(sub2, jv_number(19));
assert(jv_get_kind(sub2) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(sub2)) == 2);
assert(jv_number_value(jv_array_get(jv_copy(sub2), 0)) == 42);
assert(jv_number_value(jv_array_get(jv_copy(sub2), 1)) == 19);
assert(jv_get_kind(subarray) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(subarray)) == 1);
assert(jv_number_value(jv_array_get(jv_copy(subarray), 0)) == 42);
jv_free(subarray);
void* before = sub2.u.ptr;
sub2 = jv_array_append(sub2, jv_number(200));
void* after = sub2.u.ptr;
assert(before == after);
jv_free(sub2);
jv a3 = jv_array_append(jv_copy(a), jv_number(19));
assert(jv_array_length(jv_copy(a3)) == 3);
assert(jv_number_value(jv_array_get(jv_copy(a3), 0)) == 42);
assert(jv_array_length(jv_array_get(jv_copy(a3), 1)) == 1);
assert(jv_number_value(jv_array_get(jv_copy(a3), 2)) == 19);
jv_free(a3);
jv a4 = jv_array();
a4 = jv_array_append(a4, jv_number(1));
a4 = jv_array_append(a4, jv_number(2));
jv a5 = jv_copy(a4);
a4 = jv_array_append(a4, jv_number(3));
a4 = jv_array_slice(a4, 0, 1);
assert(jv_array_length(jv_copy(a4)) == 1);
a4 = jv_array_append(a4, jv_number(4));
assert(jv_array_length(jv_copy(a4)) == 2);
assert(jv_array_length(jv_copy(a5)) == 2);
jv_free(a4);
jv_free(a5);
assert(jv_array_length(jv_copy(a)) == 2);
assert(jv_number_value(jv_array_get(jv_copy(a), 0)) == 42);
assert(jv_array_length(jv_array_get(jv_copy(a), 1)) == 1);
jv_dump(jv_copy(a), 0); printf("\n");
jv_free(a);
}
/// Strings
{
assert(jv_equal(jv_string("foo"), jv_string_sized("foo", 3)));
char nasty[] = "foo\0";
jv shortstr = jv_string(nasty), longstr = jv_string_sized(nasty, sizeof(nasty));
assert(jv_string_length_bytes(jv_copy(shortstr)) == (int)strlen(nasty));
assert(jv_string_length_bytes(jv_copy(longstr)) == (int)sizeof(nasty));
jv_free(shortstr);
jv_free(longstr);
char a1s[] = "hello", a2s[] = "hello", bs[] = "goodbye";
jv a1 = jv_string(a1s), a2 = jv_string(a2s), b = jv_string(bs);
assert(jv_equal(jv_copy(a1), jv_copy(a2)));
assert(jv_equal(jv_copy(a2), jv_copy(a1)));
assert(!jv_equal(jv_copy(a1), jv_copy(b)));
assert(jv_string_hash(jv_copy(a1)) == jv_string_hash(jv_copy(a1)));
assert(jv_string_hash(jv_copy(a1)) == jv_string_hash(jv_copy(a2)));
assert(jv_string_hash(jv_copy(b)) != jv_string_hash(jv_copy(a1)));
jv_free(a1);
jv_free(a2);
jv_free(b);
assert(jv_equal(jv_string("hello42!"), jv_string_fmt("hello%d%s", 42, "!")));
char big[20000];
for (int i=0; i<(int)sizeof(big); i++) big[i] = 'a';
big[sizeof(big)-1] = 0;
jv str = jv_string_fmt("%s", big);
assert(jv_string_length_bytes(jv_copy(str)) == sizeof(big) - 1);
assert(!strcmp(big, jv_string_value(str)));
jv_free(str);
}
/// Objects
{
jv o1 = jv_object();
o1 = jv_object_set(o1, jv_string("foo"), jv_number(42));
o1 = jv_object_set(o1, jv_string("bar"), jv_number(24));
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("foo"))) == 42);
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("bar"))) == 24);
jv o2 = jv_object_set(jv_copy(o1), jv_string("foo"), jv_number(420));
o2 = jv_object_set(o2, jv_string("bar"), jv_number(240));
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("foo"))) == 42);
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("bar"))) == 24);
assert(jv_number_value(jv_object_get(jv_copy(o2), jv_string("foo"))) == 420);
jv_free(o1);
assert(jv_number_value(jv_object_get(jv_copy(o2), jv_string("bar"))) == 240);
jv_dump(jv_copy(o2), 0); printf("\n");
jv_free(o2);
}
/// Compile errors
{
jq_state *jq = jq_init();
jq_compile_args(jq, "}", jv_array());
jq_teardown(&jq);
}
}
+1300
View File
File diff suppressed because it is too large. Load diff
+183
View File
@@ -0,0 +1,183 @@
#ifndef JV_H
#define JV_H
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
typedef enum {
JV_KIND_INVALID,
JV_KIND_NULL,
JV_KIND_FALSE,
JV_KIND_TRUE,
JV_KIND_NUMBER,
JV_KIND_STRING,
JV_KIND_ARRAY,
JV_KIND_OBJECT
} jv_kind;
struct jv_refcnt;
/* All of the fields of this struct are private.
Really. Do not play with them. */
typedef struct {
unsigned char kind_flags;
unsigned char pad_;
unsigned short offset; /* array offsets */
int size;
union {
struct jv_refcnt* ptr;
double number;
} u;
} jv;
/*
* All jv_* functions consume (decref) input and produce (incref) output
* Except jv_copy
*/
jv_kind jv_get_kind(jv);
const char* jv_kind_name(jv_kind);
static int jv_is_valid(jv x) { return jv_get_kind(x) != JV_KIND_INVALID; }
jv jv_copy(jv);
void jv_free(jv);
int jv_get_refcnt(jv);
int jv_equal(jv, jv);
int jv_contains(jv, jv);
jv jv_invalid(void);
jv jv_invalid_with_msg(jv);
jv jv_invalid_get_msg(jv);
int jv_invalid_has_msg(jv);
jv jv_null(void);
jv jv_true(void);
jv jv_false(void);
jv jv_bool(int);
jv jv_number(double);
double jv_number_value(jv);
int jv_is_integer(jv);
jv jv_array(void);
jv jv_array_sized(int);
int jv_array_length(jv);
jv jv_array_get(jv, int);
jv jv_array_set(jv, int, jv);
jv jv_array_append(jv, jv);
jv jv_array_concat(jv, jv);
jv jv_array_slice(jv, int, int);
jv jv_array_indexes(jv, jv);
#define jv_array_foreach(a, i, x) \
for (int jv_len__ = jv_array_length(jv_copy(a)), i=0, jv_j__ = 1; \
jv_j__; jv_j__ = 0) \
for (jv x; \
i < jv_len__ ? \
(x = jv_array_get(jv_copy(a), i), 1) : 0; \
i++)
jv jv_string(const char*);
jv jv_string_sized(const char*, int);
jv jv_string_empty(int len);
int jv_string_length_bytes(jv);
int jv_string_length_codepoints(jv);
unsigned long jv_string_hash(jv);
const char* jv_string_value(jv);
jv jv_string_indexes(jv j, jv k);
jv jv_string_slice(jv j, int start, int end);
jv jv_string_concat(jv, jv);
jv jv_string_vfmt(const char*, va_list);
jv jv_string_fmt(const char*, ...);
jv jv_string_append_codepoint(jv a, uint32_t c);
jv jv_string_append_buf(jv a, const char* buf, int len);
jv jv_string_append_str(jv a, const char* str);
jv jv_string_split(jv j, jv sep);
jv jv_string_explode(jv j);
jv jv_string_implode(jv j);
jv jv_object(void);
jv jv_object_get(jv object, jv key);
jv jv_object_set(jv object, jv key, jv value);
jv jv_object_delete(jv object, jv key);
int jv_object_length(jv object);
jv jv_object_merge(jv, jv);
jv jv_object_merge_recursive(jv, jv);
int jv_object_iter(jv);
int jv_object_iter_next(jv, int);
int jv_object_iter_valid(jv, int);
jv jv_object_iter_key(jv, int);
jv jv_object_iter_value(jv, int);
#define jv_object_foreach(t, k, v) \
for (int jv_i__ = jv_object_iter(t), jv_j__ = 1; jv_j__; jv_j__ = 0) \
for (jv k, v; \
jv_object_iter_valid((t), jv_i__) ? \
(k = jv_object_iter_key(t, jv_i__), \
v = jv_object_iter_value(t, jv_i__), \
1) \
: 0; \
jv_i__ = jv_object_iter_next(t, jv_i__)) \
int jv_get_refcnt(jv);
enum { JV_PRINT_PRETTY = 1, JV_PRINT_ASCII = 2, JV_PRINT_COLOUR = 4, JV_PRINT_SORTED = 8 };
void jv_dumpf(jv, FILE *f, int flags);
void jv_dump(jv, int flags);
void jv_show(jv, int flags);
jv jv_dump_string(jv, int flags);
jv jv_parse(const char* string);
jv jv_parse_sized(const char* string, int length);
typedef void (*jv_nomem_handler_f)(void *);
void jv_nomem_handler(jv_nomem_handler_f, void *);
jv jv_load_file(const char *, int);
struct jv_parser;
struct jv_parser* jv_parser_new(int);
void jv_parser_set_buf(struct jv_parser*, const char*, int, int);
jv jv_parser_next(struct jv_parser*);
void jv_parser_free(struct jv_parser*);
jv jv_get(jv, jv);
jv jv_set(jv, jv, jv);
jv jv_has(jv, jv);
jv jv_setpath(jv, jv, jv);
jv jv_getpath(jv, jv);
jv jv_delpaths(jv, jv);
jv jv_keys(jv /*object or array*/);
int jv_cmp(jv, jv);
jv jv_group(jv, jv);
jv jv_sort(jv, jv);
#endif
/*
true/false/null:
check kind
number:
introduce/eliminate jv
to integer
array:
copy
free
slice
index
update
updateslice?
*/
+151
View File
@@ -0,0 +1,151 @@
#include <stdlib.h>
#include <stdio.h>
#include "jv_alloc.h"
struct nomem_handler {
jv_nomem_handler_f handler;
void *data;
};
#if !defined(HAVE_PTHREAD_KEY_CREATE) || \
!defined(HAVE_PTHREAD_ONCE) || \
!defined(HAVE_ATEXIT)
/* Try thread-local storage? */
#ifdef _MSC_VER
/* Visual C++: yes */
static __declspec(thread) struct nomem_handler nomem_handler;
#define USE_TLS
#else
#ifdef HAVE___THREAD
/* GCC and friends: yes */
static __thread struct nomem_handler nomem_handler;
#define USE_TLS
#endif /* HAVE___THREAD */
#endif /* _MSC_VER */
#endif /* !HAVE_PTHREAD_KEY_CREATE */
#ifdef USE_TLS
void jv_nomem_handler(jv_nomem_handler_f handler, void *data) {
nomem_handler.handler = handler;
}
static void memory_exhausted() {
if (nomem_handler.handler)
nomem_handler.handler(nomem_handler.data); // Maybe handler() will longjmp() to safety
// Or not
fprintf(stderr, "error: cannot allocate memory\n");
abort();
}
#else /* USE_TLS */
#ifdef HAVE_PTHREAD_KEY_CREATE
#include <pthread.h>
pthread_key_t nomem_handler_key;
pthread_once_t mem_once = PTHREAD_ONCE_INIT;
static void tsd_fini(void) {
struct nomem_handler *nomem_handler;
nomem_handler = pthread_getspecific(nomem_handler_key);
if (nomem_handler) {
(void) pthread_setspecific(nomem_handler_key, NULL);
free(nomem_handler);
}
}
static void tsd_init(void) {
if (pthread_key_create(&nomem_handler_key, NULL) != 0) {
fprintf(stderr, "error: cannot create thread specific key");
abort();
}
if (atexit(tsd_fini) != 0) {
fprintf(stderr, "error: cannot set an exit handler");
abort();
}
struct nomem_handler *nomem_handler = calloc(1, sizeof(struct nomem_handler));
if (pthread_setspecific(nomem_handler_key, nomem_handler) != 0) {
fprintf(stderr, "error: cannot set thread specific data");
abort();
}
}
void jv_nomem_handler(jv_nomem_handler_f handler, void *data) {
pthread_once(&mem_once, tsd_init); // cannot fail
struct nomem_handler *nomem_handler;
nomem_handler = pthread_getspecific(nomem_handler_key);
if (nomem_handler == NULL) {
handler(data);
fprintf(stderr, "error: cannot allocate memory\n");
abort();
}
nomem_handler->handler = handler;
nomem_handler->data = data;
}
static void memory_exhausted() {
struct nomem_handler *nomem_handler;
pthread_once(&mem_once, tsd_init);
nomem_handler = pthread_getspecific(nomem_handler_key);
if (nomem_handler)
nomem_handler->handler(nomem_handler->data); // Maybe handler() will longjmp() to safety
// Or not
fprintf(stderr, "error: cannot allocate memory\n");
abort();
}
#else
/* No thread-local storage of any kind that we know how to handle */
static struct nomem_handler nomem_handler;
void jv_nomem_handler(jv_nomem_handler_f handler, void *data) {
nomem_handler.handler = handler;
nomem_handler.data = data;
}
static void memory_exhausted() {
fprintf(stderr, "error: cannot allocate memory\n");
abort();
}
#endif /* HAVE_PTHREAD_KEY_CREATE */
#endif /* USE_TLS */
void* jv_mem_alloc(size_t sz) {
void* p = malloc(sz);
if (!p) {
memory_exhausted();
}
return p;
}
void* jv_mem_alloc_unguarded(size_t sz) {
return malloc(sz);
}
void jv_mem_free(void* p) {
free(p);
}
void* jv_mem_realloc(void* p, size_t sz) {
p = realloc(p, sz);
if (!p) {
memory_exhausted();
}
return p;
}
#ifndef NDEBUG
volatile char jv_mem_uninitialised;
__attribute__((constructor)) void jv_mem_uninit_setup(){
char* p = malloc(1);
jv_mem_uninitialised = *p;
free(p);
}
#endif
+23
View File
@@ -0,0 +1,23 @@
#ifndef JV_ALLOC_H
#define JV_ALLOC_H
#include <stddef.h>
#include "jv.h"
#ifndef NDEBUG
extern volatile char jv_mem_uninitialised;
#endif
static void jv_mem_invalidate(void* mem, size_t n) {
#ifndef NDEBUG
char* m = mem;
while (n--) *m++ ^= jv_mem_uninitialised ^ jv_mem_uninitialised;
#endif
}
void* jv_mem_alloc(size_t);
void* jv_mem_alloc_unguarded(size_t);
void jv_mem_free(void*);
__attribute__((warn_unused_result)) void* jv_mem_realloc(void*, size_t);
#endif
+593
View File
@@ -0,0 +1,593 @@
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "jv_alloc.h"
static int parse_slice(jv j, jv slice, int* pstart, int* pend) {
// Array slices
jv start_jv = jv_object_get(jv_copy(slice), jv_string("start"));
jv end_jv = jv_object_get(slice, jv_string("end"));
if (jv_get_kind(start_jv) == JV_KIND_NULL) {
jv_free(start_jv);
start_jv = jv_number(0);
}
int len;
if (jv_get_kind(j) == JV_KIND_ARRAY)
len = jv_array_length(jv_copy(j));
else
len = jv_string_length_codepoints(jv_copy(j));
if (jv_get_kind(end_jv) == JV_KIND_NULL) {
jv_free(end_jv);
end_jv = jv_number(len);
}
if (jv_get_kind(start_jv) != JV_KIND_NUMBER ||
jv_get_kind(end_jv) != JV_KIND_NUMBER) {
jv_free(start_jv);
jv_free(end_jv);
return 0;
} else {
double dstart = jv_number_value(start_jv);
double dend = jv_number_value(end_jv);
if (dstart < 0) dstart += len;
if (dend < 0) dend += len;
if (dstart < 0) dstart = 0;
if (dstart > len) dstart = len;
int start = (int)dstart;
int end = (dend > len) ? len : (int)dend;
// Ends are exclusive but e.g. 1 < 1.5 so :1.5 should be :2 not :1
if(end < dend) end += 1;
if (end > len) end = len;
if (end < start) end = start;
assert(0 <= start && start <= end && end <= len);
*pstart = start;
*pend = end;
return 1;
}
}
jv jv_get(jv t, jv k) {
jv v;
if (jv_get_kind(t) == JV_KIND_OBJECT && jv_get_kind(k) == JV_KIND_STRING) {
v = jv_object_get(t, k);
if (!jv_is_valid(v)) {
jv_free(v);
v = jv_null();
}
} else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_NUMBER) {
if(jv_is_integer(k)){
v = jv_array_get(t, (int)jv_number_value(k));
if (!jv_is_valid(v)) {
jv_free(v);
v = jv_null();
}
} else {
jv_free(t);
jv_free(k);
v = jv_null();
}
} else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_OBJECT) {
int start, end;
if (parse_slice(t, k, &start, &end)) {
v = jv_array_slice(t, start, end);
} else {
v = jv_invalid_with_msg(jv_string_fmt("Start and end indices of an array slice must be numbers"));
jv_free(t);
}
} else if (jv_get_kind(t) == JV_KIND_STRING && jv_get_kind(k) == JV_KIND_OBJECT) {
int start, end;
if (parse_slice(t, k, &start, &end)) {
v = jv_string_slice(t, start, end);
} else {
v = jv_invalid_with_msg(jv_string_fmt("Start and end indices of an string slice must be numbers"));
jv_free(t);
}
} else if (jv_get_kind(t) == JV_KIND_STRING && jv_get_kind(k) == JV_KIND_STRING) {
v = jv_string_indexes(t, k);
} else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_ARRAY) {
v = jv_array_indexes(t, k);
} else if (jv_get_kind(t) == JV_KIND_NULL &&
(jv_get_kind(k) == JV_KIND_STRING ||
jv_get_kind(k) == JV_KIND_NUMBER ||
jv_get_kind(k) == JV_KIND_OBJECT)) {
jv_free(t);
jv_free(k);
v = jv_null();
} else {
v = jv_invalid_with_msg(jv_string_fmt("Cannot index %s with %s",
jv_kind_name(jv_get_kind(t)),
jv_kind_name(jv_get_kind(k))));
jv_free(t);
jv_free(k);
}
return v;
}
jv jv_set(jv t, jv k, jv v) {
if (!jv_is_valid(v)) {
jv_free(t);
jv_free(k);
return v;
}
int isnull = jv_get_kind(t) == JV_KIND_NULL;
if (jv_get_kind(k) == JV_KIND_STRING &&
(jv_get_kind(t) == JV_KIND_OBJECT || isnull)) {
if (isnull) t = jv_object();
t = jv_object_set(t, k, v);
} else if (jv_get_kind(k) == JV_KIND_NUMBER &&
(jv_get_kind(t) == JV_KIND_ARRAY || isnull)) {
if (isnull) t = jv_array();
t = jv_array_set(t, (int)jv_number_value(k), v);
} else if (jv_get_kind(k) == JV_KIND_OBJECT &&
(jv_get_kind(t) == JV_KIND_ARRAY || isnull)) {
if (isnull) t = jv_array();
int start, end;
if (parse_slice(t, k, &start, &end)) {
if (jv_get_kind(v) == JV_KIND_ARRAY) {
int array_len = jv_array_length(jv_copy(t));
assert(0 <= start && start <= end && end <= array_len);
int slice_len = end - start;
int insert_len = jv_array_length(jv_copy(v));
if (slice_len < insert_len) {
// array is growing
int shift = insert_len - slice_len;
for (int i = array_len - 1; i >= end; i--) {
t = jv_array_set(t, i + shift, jv_array_get(jv_copy(t), i));
}
} else if (slice_len > insert_len) {
// array is shrinking
int shift = slice_len - insert_len;
for (int i = end; i < array_len; i++) {
t = jv_array_set(t, i - shift, jv_array_get(jv_copy(t), i));
}
t = jv_array_slice(t, 0, array_len - shift);
}
for (int i=0; i < insert_len; i++) {
t = jv_array_set(t, start + i, jv_array_get(jv_copy(v), i));
}
jv_free(v);
} else {
jv_free(t);
jv_free(v);
t = jv_invalid_with_msg(jv_string_fmt("A slice of an array can only be assigned another array"));
}
} else {
jv_free(t);
jv_free(k);
jv_free(v);
t = jv_invalid_with_msg(jv_string_fmt("Start and end indices of an array slice must be numbers"));
}
} else {
jv err = jv_invalid_with_msg(jv_string_fmt("Cannot update field at %s index of %s",
jv_kind_name(jv_get_kind(k)),
jv_kind_name(jv_get_kind(t))));
jv_free(t);
jv_free(k);
jv_free(v);
t = err;
}
return t;
}
jv jv_has(jv t, jv k) {
assert(jv_is_valid(t));
assert(jv_is_valid(k));
jv ret;
if (jv_get_kind(t) == JV_KIND_NULL) {
jv_free(t);
jv_free(k);
ret = jv_false();
} else if (jv_get_kind(t) == JV_KIND_OBJECT &&
jv_get_kind(k) == JV_KIND_STRING) {
jv elem = jv_object_get(t, k);
ret = jv_bool(jv_is_valid(elem));
jv_free(elem);
} else if (jv_get_kind(t) == JV_KIND_ARRAY &&
jv_get_kind(k) == JV_KIND_NUMBER) {
jv elem = jv_array_get(t, (int)jv_number_value(k));
ret = jv_bool(jv_is_valid(elem));
jv_free(elem);
} else {
ret = jv_invalid_with_msg(jv_string_fmt("Cannot check whether %s has a %s key",
jv_kind_name(jv_get_kind(t)),
jv_kind_name(jv_get_kind(k))));
jv_free(t);
jv_free(k);
}
return ret;
}
// assumes keys is a sorted array
jv jv_dels(jv t, jv keys) {
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_is_valid(t));
if (jv_get_kind(t) == JV_KIND_NULL || jv_array_length(jv_copy(keys)) == 0) {
// no change
} else if (jv_get_kind(t) == JV_KIND_ARRAY) {
// extract slices, they must be handled differently
jv orig_keys = keys;
keys = jv_array();
jv new_array = jv_array();
jv starts = jv_array(), ends = jv_array();
jv_array_foreach(orig_keys, i, key) {
if (jv_get_kind(key) == JV_KIND_NUMBER) {
keys = jv_array_append(keys, key);
} else if (jv_get_kind(key) == JV_KIND_OBJECT) {
int start, end;
if (parse_slice(t, key, &start, &end)) {
starts = jv_array_append(starts, jv_number(start));
ends = jv_array_append(ends, jv_number(end));
} else {
jv_free(new_array);
jv_free(key);
new_array = jv_invalid_with_msg(jv_string_fmt("Start and end indices of an array slice must be numbers"));
goto arr_out;
}
} else {
jv_free(new_array);
new_array = jv_invalid_with_msg(jv_string_fmt("Cannot delete %s element of array",
jv_kind_name(jv_get_kind(key))));
jv_free(key);
goto arr_out;
}
}
int kidx = 0;
jv_array_foreach(t, i, elem) {
int del = 0;
while (kidx < jv_array_length(jv_copy(keys))) {
int delidx = (int)jv_number_value(jv_array_get(jv_copy(keys), kidx));
if (i == delidx) {
del = 1;
}
if (i < delidx) {
break;
}
kidx++;
}
for (int sidx=0; !del && sidx<jv_array_length(jv_copy(starts)); sidx++) {
if ((int)jv_number_value(jv_array_get(jv_copy(starts), sidx)) <= i &&
i < (int)jv_number_value(jv_array_get(jv_copy(ends), sidx))) {
del = 1;
}
}
if (!del)
new_array = jv_array_append(new_array, elem);
else
jv_free(elem);
}
arr_out:
jv_free(starts);
jv_free(ends);
jv_free(orig_keys);
jv_free(t);
t = new_array;
} else if (jv_get_kind(t) == JV_KIND_OBJECT) {
jv_array_foreach(keys, i, k) {
if (jv_get_kind(k) != JV_KIND_STRING) {
jv_free(t);
t = jv_invalid_with_msg(jv_string_fmt("Cannot delete %s field of object",
jv_kind_name(jv_get_kind(k))));
jv_free(k);
break;
}
t = jv_object_delete(t, k);
}
} else {
jv err = jv_invalid_with_msg(jv_string_fmt("Cannot delete fields from %s",
jv_kind_name(jv_get_kind(t))));
jv_free(t);
t = err;
}
jv_free(keys);
return t;
}
jv jv_setpath(jv root, jv path, jv value) {
if (jv_get_kind(path) != JV_KIND_ARRAY) {
jv_free(value);
jv_free(root);
jv_free(path);
return jv_invalid_with_msg(jv_string("Path must be specified as an array"));
}
if (!jv_is_valid(root)){
jv_free(value);
jv_free(path);
return root;
}
if (jv_array_length(jv_copy(path)) == 0) {
jv_free(path);
jv_free(root);
return value;
}
jv pathcurr = jv_array_get(jv_copy(path), 0);
jv pathrest = jv_array_slice(path, 1, jv_array_length(jv_copy(path)));
return jv_set(root, pathcurr,
jv_setpath(jv_get(jv_copy(root), jv_copy(pathcurr)), pathrest, value));
}
jv jv_getpath(jv root, jv path) {
if (jv_get_kind(path) != JV_KIND_ARRAY) {
jv_free(root);
jv_free(path);
return jv_invalid_with_msg(jv_string("Path must be specified as an array"));
}
if (!jv_is_valid(root)) {
jv_free(path);
return root;
}
if (jv_array_length(jv_copy(path)) == 0) {
jv_free(path);
return root;
}
jv pathcurr = jv_array_get(jv_copy(path), 0);
jv pathrest = jv_array_slice(path, 1, jv_array_length(jv_copy(path)));
return jv_getpath(jv_get(root, pathcurr), pathrest);
}
// assumes paths is a sorted array of arrays
static jv delpaths_sorted(jv object, jv paths, int start) {
jv delkeys = jv_array();
for (int i=0; i<jv_array_length(jv_copy(paths));) {
int j = i;
assert(jv_array_length(jv_array_get(jv_copy(paths), i)) > start);
int delkey = jv_array_length(jv_array_get(jv_copy(paths), i)) == start + 1;
jv key = jv_array_get(jv_array_get(jv_copy(paths), i), start);
while (j < jv_array_length(jv_copy(paths)) &&
jv_equal(jv_copy(key), jv_array_get(jv_array_get(jv_copy(paths), j), start)))
j++;
// if i <= entry < j, then entry starts with key
if (delkey) {
// deleting this entire key, we don't care about any more specific deletions
delkeys = jv_array_append(delkeys, key);
} else {
// deleting certain sub-parts of this key
jv subobject = jv_get(jv_copy(object), jv_copy(key));
if (!jv_is_valid(subobject)) {
jv_free(key);
jv_free(object);
object = subobject;
break;
} else if (jv_get_kind(subobject) == JV_KIND_NULL) {
jv_free(key);
jv_free(subobject);
} else {
jv newsubobject = delpaths_sorted(subobject, jv_array_slice(jv_copy(paths), i, j), start+1);
if (!jv_is_valid(newsubobject)) {
jv_free(key);
jv_free(object);
object = newsubobject;
break;
}
object = jv_set(object, key, newsubobject);
}
if (!jv_is_valid(object)) break;
}
i = j;
}
jv_free(paths);
if (jv_is_valid(object))
object = jv_dels(object, delkeys);
else
jv_free(delkeys);
return object;
}
jv jv_delpaths(jv object, jv paths) {
paths = jv_sort(paths, jv_copy(paths));
jv_array_foreach(paths, i, elem) {
if (jv_get_kind(elem) != JV_KIND_ARRAY) {
jv_free(object);
jv_free(paths);
jv err = jv_invalid_with_msg(jv_string_fmt("Path must be specified as array, not %s",
jv_kind_name(jv_get_kind(elem))));
jv_free(elem);
return err;
}
jv_free(elem);
}
if (jv_array_length(jv_copy(paths)) == 0) {
// nothing is being deleted
jv_free(paths);
return object;
}
if (jv_array_length(jv_array_get(jv_copy(paths), 0)) == 0) {
// everything is being deleted
jv_free(paths);
jv_free(object);
return jv_null();
}
return delpaths_sorted(object, paths, 0);
}
static int string_cmp(const void* pa, const void* pb){
const jv* a = pa;
const jv* b = pb;
int lena = jv_string_length_bytes(jv_copy(*a));
int lenb = jv_string_length_bytes(jv_copy(*b));
int minlen = lena < lenb ? lena : lenb;
int r = memcmp(jv_string_value(*a), jv_string_value(*b), minlen);
if (r == 0) r = lena - lenb;
return r;
}
jv jv_keys(jv x) {
if (jv_get_kind(x) == JV_KIND_OBJECT) {
int nkeys = jv_object_length(jv_copy(x));
jv* keys = jv_mem_alloc(sizeof(jv) * nkeys);
int kidx = 0;
jv_object_foreach(x, key, value) {
keys[kidx++] = key;
jv_free(value);
}
qsort(keys, nkeys, sizeof(jv), string_cmp);
jv answer = jv_array_sized(nkeys);
for (int i = 0; i<nkeys; i++) {
answer = jv_array_append(answer, keys[i]);
}
jv_mem_free(keys);
jv_free(x);
return answer;
} else if (jv_get_kind(x) == JV_KIND_ARRAY) {
int n = jv_array_length(x);
jv answer = jv_array();
for (int i=0; i<n; i++){
answer = jv_array_set(answer, i, jv_number(i));
}
return answer;
} else {
assert(0 && "jv_keys passed something neither object nor array");
return jv_invalid();
}
}
int jv_cmp(jv a, jv b) {
if (jv_get_kind(a) != jv_get_kind(b)) {
int r = (int)jv_get_kind(a) - (int)jv_get_kind(b);
jv_free(a);
jv_free(b);
return r;
}
int r = 0;
switch (jv_get_kind(a)) {
default:
assert(0 && "invalid kind passed to jv_cmp");
case JV_KIND_NULL:
case JV_KIND_FALSE:
case JV_KIND_TRUE:
// there's only one of each of these values
r = 0;
break;
case JV_KIND_NUMBER: {
double da = jv_number_value(a), db = jv_number_value(b);
// handle NaN as though it were null
if (da != da) r = jv_cmp(jv_null(), jv_number(db));
else if (db != db) r = jv_cmp(jv_number(da), jv_null());
else if (da < db) r = -1;
else if (da == db) r = 0;
else r = 1;
break;
}
case JV_KIND_STRING: {
r = string_cmp(&a, &b);
break;
}
case JV_KIND_ARRAY: {
// Lexical ordering of arrays
int i = 0;
while (r == 0) {
int a_done = i >= jv_array_length(jv_copy(a));
int b_done = i >= jv_array_length(jv_copy(b));
if (a_done || b_done) {
r = b_done - a_done; //suddenly, logic
break;
}
jv xa = jv_array_get(jv_copy(a), i);
jv xb = jv_array_get(jv_copy(b), i);
r = jv_cmp(xa, xb);
i++;
}
break;
}
case JV_KIND_OBJECT: {
jv keys_a = jv_keys(jv_copy(a));
jv keys_b = jv_keys(jv_copy(b));
r = jv_cmp(jv_copy(keys_a), keys_b);
if (r == 0) {
jv_array_foreach(keys_a, i, key) {
jv xa = jv_object_get(jv_copy(a), jv_copy(key));
jv xb = jv_object_get(jv_copy(b), key);
r = jv_cmp(xa, xb);
if (r) break;
}
}
jv_free(keys_a);
break;
}
}
jv_free(a);
jv_free(b);
return r;
}
struct sort_entry {
jv object;
jv key;
};
static int sort_cmp(const void* pa, const void* pb) {
const struct sort_entry* a = pa;
const struct sort_entry* b = pb;
int r = jv_cmp(jv_copy(a->key), jv_copy(b->key));
// comparing by address if r == 0 makes the sort stable
return r ? r : (int)(a - b);
}
static struct sort_entry* sort_items(jv objects, jv keys) {
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
struct sort_entry* entries = jv_mem_alloc(sizeof(struct sort_entry) * n);
for (int i=0; i<n; i++) {
entries[i].object = jv_array_get(jv_copy(objects), i);
entries[i].key = jv_array_get(jv_copy(keys), i);
}
jv_free(objects);
jv_free(keys);
qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
return entries;
}
jv jv_sort(jv objects, jv keys) {
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
struct sort_entry* entries = sort_items(objects, keys);
jv ret = jv_array();
for (int i=0; i<n; i++) {
jv_free(entries[i].key);
ret = jv_array_set(ret, i, entries[i].object);
}
jv_mem_free(entries);
return ret;
}
jv jv_group(jv objects, jv keys) {
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
int n = jv_array_length(jv_copy(objects));
struct sort_entry* entries = sort_items(objects, keys);
jv ret = jv_array();
if (n > 0) {
jv curr_key = entries[0].key;
jv group = jv_array_append(jv_array(), entries[0].object);
for (int i = 1; i < n; i++) {
if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
jv_free(entries[i].key);
} else {
jv_free(curr_key);
curr_key = entries[i].key;
ret = jv_array_append(ret, group);
group = jv_array();
}
group = jv_array_append(group, entries[i].object);
}
jv_free(curr_key);
ret = jv_array_append(ret, group);
}
jv_mem_free(entries);
return ret;
}
+4276
View File
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
#ifndef JV_DTOA_H
#define JV_DTOA_H
#define Kmax 7
struct Bigint;
struct dtoa_context {
struct Bigint *freelist[Kmax+1];
struct Bigint *p5s;
};
void jvp_dtoa_context_init(struct dtoa_context* ctx);
void jvp_dtoa_context_free(struct dtoa_context* ctx);
double jvp_strtod(struct dtoa_context* C, const char* s, char** se);
char* jvp_dtoa(struct dtoa_context* C, double dd, int mode, int ndigits, int *decpt, int *sign, char **rve);
void jvp_freedtoa(struct dtoa_context* C, char *s);
#define JVP_DTOA_FMT_MAX_LEN 64
char* jvp_dtoa_fmt(struct dtoa_context* C, register char *b, double x);
#endif
+46
View File
@@ -0,0 +1,46 @@
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jv.h"
jv jv_load_file(const char* filename, int raw) {
FILE* file = fopen(filename, "r");
struct jv_parser* parser;
jv data;
if (!file) {
return jv_invalid_with_msg(jv_string_fmt("Could not open %s: %s",
filename,
strerror(errno)));
}
if (raw) {
data = jv_string("");
} else {
data = jv_array();
parser = jv_parser_new(0);
}
while (!feof(file) && !ferror(file)) {
char buf[4096];
size_t n = fread(buf, 1, sizeof(buf), file);
if (raw) {
data = jv_string_concat(data, jv_string_sized(buf, (int)n));
} else {
jv_parser_set_buf(parser, buf, n, !feof(file));
jv value;
while (jv_is_valid((value = jv_parser_next(parser))))
data = jv_array_append(data, value);
jv_free(value);
}
}
if (!raw)
jv_parser_free(parser);
int badread = ferror(file);
fclose(file);
if (badread) {
jv_free(data);
return jv_invalid_with_msg(jv_string_fmt("Error reading from %s",
filename));
}
return data;
}
+486
View File
@@ -0,0 +1,486 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "jv.h"
#include "jv_dtoa.h"
#include "jv_unicode.h"
#include "jv_alloc.h"
#include "jv_dtoa.h"
typedef const char* presult;
#define TRY(x) do {presult msg__ = (x); if (msg__) return msg__; } while(0)
#ifdef __GNUC__
#define pfunc __attribute__((warn_unused_result)) presult
#else
#define pfunc presult
#endif
struct jv_parser {
const char* curr_buf;
int curr_buf_length;
int curr_buf_pos;
int curr_buf_is_partial;
unsigned bom_strip_position;
jv* stack;
int stackpos;
int stacklen;
jv next;
char* tokenbuf;
int tokenpos;
int tokenlen;
int line, column;
struct dtoa_context dtoa;
enum {
JV_PARSER_NORMAL,
JV_PARSER_STRING,
JV_PARSER_STRING_ESCAPE
} st;
};
static void parser_init(struct jv_parser* p) {
p->stack = 0;
p->stacklen = p->stackpos = 0;
p->next = jv_invalid();
p->tokenbuf = 0;
p->tokenlen = p->tokenpos = 0;
p->st = JV_PARSER_NORMAL;
p->curr_buf = 0;
p->curr_buf_length = p->curr_buf_pos = p->curr_buf_is_partial = 0;
p->bom_strip_position = 0;
p->line = 1;
p->column = 0;
jvp_dtoa_context_init(&p->dtoa);
}
static void parser_free(struct jv_parser* p) {
jv_free(p->next);
for (int i=0; i<p->stackpos; i++)
jv_free(p->stack[i]);
jv_mem_free(p->stack);
jv_mem_free(p->tokenbuf);
jvp_dtoa_context_free(&p->dtoa);
}
static pfunc value(struct jv_parser* p, jv val) {
if (jv_is_valid(p->next)) return "Expected separator between values";
jv_free(p->next);
p->next = val;
return 0;
}
static void push(struct jv_parser* p, jv v) {
assert(p->stackpos <= p->stacklen);
if (p->stackpos == p->stacklen) {
p->stacklen = p->stacklen * 2 + 10;
p->stack = jv_mem_realloc(p->stack, p->stacklen * sizeof(jv));
}
assert(p->stackpos < p->stacklen);
p->stack[p->stackpos++] = v;
}
static pfunc token(struct jv_parser* p, char ch) {
switch (ch) {
case '[':
if (jv_is_valid(p->next)) return "Expected separator between values";
push(p, jv_array());
break;
case '{':
if (jv_is_valid(p->next)) return "Expected separator between values";
push(p, jv_object());
break;
case ':':
if (!jv_is_valid(p->next))
return "Expected string key before ':'";
if (p->stackpos == 0 || jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_OBJECT)
return "':' not as part of an object";
if (jv_get_kind(p->next) != JV_KIND_STRING)
return "Object keys must be strings";
push(p, p->next);
p->next = jv_invalid();
break;
case ',':
if (!jv_is_valid(p->next))
return "Expected value before ','";
if (p->stackpos == 0)
return "',' not as part of an object or array";
if (jv_get_kind(p->stack[p->stackpos-1]) == JV_KIND_ARRAY) {
p->stack[p->stackpos-1] = jv_array_append(p->stack[p->stackpos-1], p->next);
p->next = jv_invalid();
} else if (jv_get_kind(p->stack[p->stackpos-1]) == JV_KIND_STRING) {
assert(p->stackpos > 1 && jv_get_kind(p->stack[p->stackpos-2]) == JV_KIND_OBJECT);
p->stack[p->stackpos-2] = jv_object_set(p->stack[p->stackpos-2],
p->stack[p->stackpos-1], p->next);
p->stackpos--;
p->next = jv_invalid();
} else {
// this case hits on input like {"a", "b"}
return "Objects must consist of key:value pairs";
}
break;
case ']':
if (p->stackpos == 0 || jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_ARRAY)
return "Unmatched ']'";
if (jv_is_valid(p->next)) {
p->stack[p->stackpos-1] = jv_array_append(p->stack[p->stackpos-1], p->next);
p->next = jv_invalid();
} else {
if (jv_array_length(jv_copy(p->stack[p->stackpos-1])) != 0) {
// this case hits on input like [1,2,3,]
return "Expected another array element";
}
}
jv_free(p->next);
p->next = p->stack[--p->stackpos];
break;
case '}':
if (p->stackpos == 0)
return "Unmatched '}'";
if (jv_is_valid(p->next)) {
if (jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_STRING)
return "Objects must consist of key:value pairs";
assert(p->stackpos > 1 && jv_get_kind(p->stack[p->stackpos-2]) == JV_KIND_OBJECT);
p->stack[p->stackpos-2] = jv_object_set(p->stack[p->stackpos-2],
p->stack[p->stackpos-1], p->next);
p->stackpos--;
p->next = jv_invalid();
} else {
if (jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_OBJECT)
return "Unmatched '}'";
if (jv_object_length(jv_copy(p->stack[p->stackpos-1])) != 0)
return "Expected another key-value pair";
}
jv_free(p->next);
p->next = p->stack[--p->stackpos];
break;
}
return 0;
}
static void tokenadd(struct jv_parser* p, char c) {
assert(p->tokenpos <= p->tokenlen);
if (p->tokenpos == p->tokenlen) {
p->tokenlen = p->tokenlen*2 + 256;
p->tokenbuf = jv_mem_realloc(p->tokenbuf, p->tokenlen);
}
assert(p->tokenpos < p->tokenlen);
p->tokenbuf[p->tokenpos++] = c;
}
static int unhex4(char* hex) {
int r = 0;
for (int i=0; i<4; i++) {
char c = *hex++;
int n;
if ('0' <= c && c <= '9') n = c - '0';
else if ('a' <= c && c <= 'f') n = c - 'a' + 10;
else if ('A' <= c && c <= 'F') n = c - 'A' + 10;
else return -1;
r <<= 4;
r |= n;
}
return r;
}
static pfunc found_string(struct jv_parser* p) {
char* in = p->tokenbuf;
char* out = p->tokenbuf;
char* end = p->tokenbuf + p->tokenpos;
while (in < end) {
char c = *in++;
if (c == '\\') {
if (in >= end)
return "Expected escape character at end of string";
c = *in++;
switch (c) {
case '\\':
case '"':
case '/': *out++ = c; break;
case 'b': *out++ = '\b'; break;
case 'f': *out++ = '\f'; break;
case 't': *out++ = '\t'; break;
case 'n': *out++ = '\n'; break;
case 'r': *out++ = '\r'; break;
case 'u':
/* ahh, the complicated case */
if (in + 4 > end)
return "Invalid \\uXXXX escape";
int hexvalue = unhex4(in);
if (hexvalue < 0)
return "Invalid characters in \\uXXXX escape";
unsigned long codepoint = (unsigned long)hexvalue;
in += 4;
if (0xD800 <= codepoint && codepoint <= 0xDBFF) {
/* who thought UTF-16 surrogate pairs were a good idea? */
if (in + 6 > end || in[0] != '\\' || in[1] != 'u')
return "Invalid \\uXXXX\\uXXXX surrogate pair escape";
unsigned long surrogate = unhex4(in+2);
if (!(0xDC00 <= surrogate && surrogate <= 0xDFFF))
return "Invalid \\uXXXX\\uXXXX surrogate pair escape";
in += 6;
codepoint = 0x10000 + (((codepoint - 0xD800) << 10)
|(surrogate - 0xDC00));
}
// FIXME assert valid codepoint
out += jvp_utf8_encode(codepoint, out);
break;
default:
return "Invalid escape";
}
} else {
*out++ = c;
}
}
TRY(value(p, jv_string_sized(p->tokenbuf, out - p->tokenbuf)));
p->tokenpos = 0;
return 0;
}
static pfunc check_literal(struct jv_parser* p) {
if (p->tokenpos == 0) return 0;
const char* pattern = 0;
int plen;
jv v;
switch (p->tokenbuf[0]) {
case 't': pattern = "true"; plen = 4; v = jv_true(); break;
case 'f': pattern = "false"; plen = 5; v = jv_false(); break;
case 'n': pattern = "null"; plen = 4; v = jv_null(); break;
}
if (pattern) {
if (p->tokenpos != plen) return "Invalid literal";
for (int i=0; i<plen; i++)
if (p->tokenbuf[i] != pattern[i])
return "Invalid literal";
TRY(value(p, v));
} else {
// FIXME: better parser
p->tokenbuf[p->tokenpos] = 0; // FIXME: invalid
char* end = 0;
double d = jvp_strtod(&p->dtoa, p->tokenbuf, &end);
if (end == 0 || *end != 0)
return "Invalid numeric literal";
TRY(value(p, jv_number(d)));
}
p->tokenpos = 0;
return 0;
}
typedef enum {
LITERAL,
WHITESPACE,
STRUCTURE,
QUOTE,
INVALID
} chclass;
static chclass classify(char c) {
switch (c) {
case ' ':
case '\t':
case '\r':
case '\n':
return WHITESPACE;
case '"':
return QUOTE;
case '[':
case ',':
case ']':
case '{':
case ':':
case '}':
return STRUCTURE;
default:
return LITERAL;
}
}
static const presult OK = "output produced";
static int check_done(struct jv_parser* p, jv* out) {
if (p->stackpos == 0 && jv_is_valid(p->next)) {
*out = p->next;
p->next = jv_invalid();
return 1;
} else {
return 0;
}
}
static pfunc scan(struct jv_parser* p, char ch, jv* out) {
p->column++;
if (ch == '\n') {
p->line++;
p->column = 0;
}
presult answer = 0;
if (p->st == JV_PARSER_NORMAL) {
chclass cls = classify(ch);
if (cls != LITERAL) {
TRY(check_literal(p));
if (check_done(p, out)) answer = OK;
}
switch (cls) {
case LITERAL:
tokenadd(p, ch);
break;
case WHITESPACE:
break;
case QUOTE:
p->st = JV_PARSER_STRING;
break;
case STRUCTURE:
TRY(token(p, ch));
break;
case INVALID:
return "Invalid character";
}
if (check_done(p, out)) answer = OK;
} else {
if (ch == '"' && p->st == JV_PARSER_STRING) {
TRY(found_string(p));
p->st = JV_PARSER_NORMAL;
if (check_done(p, out)) answer = OK;
} else {
tokenadd(p, ch);
if (ch == '\\' && p->st == JV_PARSER_STRING) {
p->st = JV_PARSER_STRING_ESCAPE;
} else {
p->st = JV_PARSER_STRING;
}
}
}
return answer;
}
struct jv_parser* jv_parser_new(int flags) {
struct jv_parser* p = jv_mem_alloc(sizeof(struct jv_parser));
parser_init(p);
return p;
}
void jv_parser_free(struct jv_parser* p) {
parser_free(p);
jv_mem_free(p);
}
static const unsigned char UTF8_BOM[] = {0xEF,0xBB,0xBF};
void jv_parser_set_buf(struct jv_parser* p, const char* buf, int length, int is_partial) {
assert((p->curr_buf == 0 || p->curr_buf_pos == p->curr_buf_length)
&& "previous buffer not exhausted");
while (p->bom_strip_position < sizeof(UTF8_BOM)) {
if ((unsigned char)*buf == UTF8_BOM[p->bom_strip_position]) {
// matched a BOM character
buf++;
length--;
p->bom_strip_position++;
} else {
if (p->bom_strip_position == 0) {
// no BOM in this document
p->bom_strip_position = sizeof(UTF8_BOM);
} else {
// malformed BOM (prefix present, rest missing)
p->bom_strip_position = 0xff;
}
}
}
p->curr_buf = buf;
p->curr_buf_length = length;
p->curr_buf_pos = 0;
p->curr_buf_is_partial = is_partial;
}
jv jv_parser_next(struct jv_parser* p) {
assert(p->curr_buf && "a buffer must be provided");
if (p->bom_strip_position == 0xff) return jv_invalid_with_msg(jv_string("Malformed BOM"));
jv value;
presult msg = 0;
while (!msg && p->curr_buf_pos < p->curr_buf_length) {
char ch = p->curr_buf[p->curr_buf_pos++];
msg = scan(p, ch, &value);
}
if (msg == OK) {
return value;
} else if (msg) {
return jv_invalid_with_msg(jv_string_fmt("%s at line %d, column %d", msg, p->line, p->column));
} else if (p->curr_buf_is_partial) {
assert(p->curr_buf_pos == p->curr_buf_length);
// need another buffer
return jv_invalid();
} else {
assert(p->curr_buf_pos == p->curr_buf_length);
// at EOF
if (p->st != JV_PARSER_NORMAL)
return jv_invalid_with_msg(jv_string("Unfinished string"));
if ((msg = check_literal(p)))
return jv_invalid_with_msg(jv_string(msg));
if (p->stackpos != 0)
return jv_invalid_with_msg(jv_string("Unfinished JSON term"));
// p->next is either invalid (nothing here but no syntax error)
// or valid (this is the value). either way it's the thing to return
value = p->next;
p->next = jv_invalid();
return value;
}
}
jv jv_parse_sized(const char* string, int length) {
struct jv_parser parser;
parser_init(&parser);
jv_parser_set_buf(&parser, string, length, 0);
jv value = jv_parser_next(&parser);
if (jv_is_valid(value)) {
jv next = jv_parser_next(&parser);
if (jv_is_valid(next)) {
// multiple JSON values, we only wanted one
jv_free(value);
jv_free(next);
value = jv_invalid_with_msg(jv_string("Unexpected extra JSON values"));
} else if (jv_invalid_has_msg(jv_copy(next))) {
// parser error after the first JSON value
jv_free(value);
value = next;
} else {
// a single valid JSON value
jv_free(next);
}
} else if (jv_invalid_has_msg(jv_copy(value))) {
// parse error, we'll return it
} else {
// no value at all
jv_free(value);
value = jv_invalid_with_msg(jv_string("Expected JSON value"));
}
parser_free(&parser);
if (!jv_is_valid(value) && jv_invalid_has_msg(jv_copy(value))) {
jv msg = jv_invalid_get_msg(value);
value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%s')",
jv_string_value(msg),
string));
jv_free(msg);
}
return value;
}
jv jv_parse(const char* string) {
return jv_parse_sized(string, strlen(string));
}
+288
View File
@@ -0,0 +1,288 @@
#include "jv.h"
#include <stdio.h>
#include <float.h>
#include <string.h>
#include <assert.h>
#include "jv_dtoa.h"
#include "jv_unicode.h"
#define ESC "\033"
#define COL(c) (ESC "[" c "m")
#define COLRESET (ESC "[0m")
// Colour table. See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
// for how to choose these.
static const jv_kind colour_kinds[] =
{JV_KIND_NULL, JV_KIND_FALSE, JV_KIND_TRUE, JV_KIND_NUMBER,
JV_KIND_STRING, JV_KIND_ARRAY, JV_KIND_OBJECT};
static const char* const colours[] =
{COL("30;1"), COL("0"), COL("0"), COL("0"),
COL("32"), COL("37"), COL("37")};
#define FIELD_COLOUR COL("34;1")
static void put_buf(const char* s, int len, FILE* fout, jv* strout) {
if (strout) {
*strout = jv_string_append_buf(*strout, s, len);
} else {
fwrite(s, 1, len, fout);
}
}
static void put_char(char c, FILE* fout, jv* strout) {
put_buf(&c, 1, fout, strout);
}
static void put_str(const char* s, FILE* fout, jv* strout) {
put_buf(s, strlen(s), fout, strout);
}
static void put_space(int n, FILE* fout, jv* strout) {
while (n--) {
put_char(' ', fout, strout);
}
}
static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S) {
assert(jv_get_kind(str) == JV_KIND_STRING);
const char* i = jv_string_value(str);
const char* end = i + jv_string_length_bytes(jv_copy(str));
const char* cstart;
int c = 0;
char buf[32];
put_char('"', F, S);
while ((i = jvp_utf8_next((cstart = i), end, &c))) {
assert(c != -1);
int unicode_escape = 0;
if (0x20 <= c && c <= 0x7E) {
// printable ASCII
if (c == '"' || c == '\\') {
put_char('\\', F, S);
}
put_char(c, F, S);
} else if (c < 0x20 || c == 0x7F) {
// ASCII control character
switch (c) {
case '\b':
put_char('\\', F, S);
put_char('b', F, S);
break;
case '\t':
put_char('\\', F, S);
put_char('t', F, S);
break;
case '\r':
put_char('\\', F, S);
put_char('r', F, S);
break;
case '\n':
put_char('\\', F, S);
put_char('n', F, S);
break;
case '\f':
put_char('\\', F, S);
put_char('f', F, S);
break;
default:
unicode_escape = 1;
break;
}
} else {
if (ascii_only) {
unicode_escape = 1;
} else {
put_buf(cstart, i - cstart, F, S);
}
}
if (unicode_escape) {
if (c <= 0xffff) {
sprintf(buf, "\\u%04x", c);
} else {
c -= 0x10000;
sprintf(buf, "\\u%04x\\u%04x",
0xD800 | ((c & 0xffc00) >> 10),
0xDC00 | (c & 0x003ff));
}
put_str(buf, F, S);
}
}
assert(c != -1);
put_char('"', F, S);
}
enum { INDENT = 2 };
static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FILE* F, jv* S) {
char buf[JVP_DTOA_FMT_MAX_LEN];
const char* colour = 0;
if (flags & JV_PRINT_COLOUR) {
for (unsigned i=0; i<sizeof(colour_kinds)/sizeof(colour_kinds[0]); i++) {
if (jv_get_kind(x) == colour_kinds[i]) {
colour = colours[i];
put_str(colour, F, S);
break;
}
}
}
switch (jv_get_kind(x)) {
default:
case JV_KIND_INVALID:
assert(0 && "Invalid value");
break;
case JV_KIND_NULL:
put_str("null", F, S);
break;
case JV_KIND_FALSE:
put_str("false", F, S);
break;
case JV_KIND_TRUE:
put_str("true", F, S);
break;
case JV_KIND_NUMBER: {
double d = jv_number_value(x);
if (d != d) {
// JSON doesn't have NaN, so we'll render it as "null"
put_str("null", F, S);
} else {
// Normalise infinities to something we can print in valid JSON
if (d > DBL_MAX) d = DBL_MAX;
if (d < -DBL_MAX) d = -DBL_MAX;
put_str(jvp_dtoa_fmt(C, buf, d), F, S);
}
break;
}
case JV_KIND_STRING:
jvp_dump_string(x, flags & JV_PRINT_ASCII, F, S);
break;
case JV_KIND_ARRAY: {
if (jv_array_length(jv_copy(x)) == 0) {
put_str("[]", F, S);
break;
}
put_str("[", F, S);
if (flags & JV_PRINT_PRETTY) {
put_char('\n', F, S);
put_space(indent+INDENT, F, S);
}
jv_array_foreach(x, i, elem) {
if (i!=0) {
if (flags & JV_PRINT_PRETTY) {
put_str(",\n", F, S);
put_space(indent+INDENT, F, S);
} else {
put_str(",", F, S);
}
}
jv_dump_term(C, elem, flags, indent + INDENT, F, S);
if (colour) put_str(colour, F, S);
}
if (flags & JV_PRINT_PRETTY) {
put_char('\n', F, S);
put_space(indent, F, S);
}
if (colour) put_str(colour, F, S);
put_char(']', F, S);
break;
}
case JV_KIND_OBJECT: {
if (jv_object_length(jv_copy(x)) == 0) {
put_str("{}", F, S);
break;
}
put_char('{', F, S);
if (flags & JV_PRINT_PRETTY) {
put_char('\n', F, S);
put_space(indent+INDENT, F, S);
}
int first = 1;
int i = 0;
jv keyset = jv_null();
while (1) {
jv key, value;
if (flags & JV_PRINT_SORTED) {
if (first) {
keyset = jv_keys(jv_copy(x));
i = 0;
} else {
i++;
}
if (i >= jv_array_length(jv_copy(keyset))) {
jv_free(keyset);
break;
}
key = jv_array_get(jv_copy(keyset), i);
value = jv_object_get(jv_copy(x), jv_copy(key));
} else {
if (first) {
i = jv_object_iter(x);
} else {
i = jv_object_iter_next(x, i);
}
if (!jv_object_iter_valid(x, i)) break;
key = jv_object_iter_key(x, i);
value = jv_object_iter_value(x, i);
}
if (!first) {
if (flags & JV_PRINT_PRETTY){
put_str(",\n", F, S);
put_space(indent+INDENT, F, S);
} else {
put_str(",", F, S);
}
}
if (colour) put_str(COLRESET, F, S);
first = 0;
if (colour) put_str(FIELD_COLOUR, F, S);
jvp_dump_string(key, flags & JV_PRINT_ASCII, F, S);
jv_free(key);
if (colour) put_str(COLRESET, F, S);
if (colour) put_str(colour, F, S);
put_str((flags & JV_PRINT_PRETTY) ? ": " : ":", F, S);
if (colour) put_str(COLRESET, F, S);
jv_dump_term(C, value, flags, indent + INDENT, F, S);
if (colour) put_str(colour, F, S);
}
if (flags & JV_PRINT_PRETTY) {
put_char('\n', F, S);
put_space(indent, F, S);
}
if (colour) put_str(colour, F, S);
put_char('}', F, S);
}
}
jv_free(x);
if (colour) {
put_str(COLRESET, F, S);
}
}
void jv_dumpf(jv x, FILE *f, int flags) {
struct dtoa_context C;
jvp_dtoa_context_init(&C);
jv_dump_term(&C, x, flags, 0, f, 0);
jvp_dtoa_context_free(&C);
}
void jv_dump(jv x, int flags) {
jv_dumpf(x, stdout, flags);
}
void jv_show(jv x, int flags) {
if (flags == -1)
flags = JV_PRINT_PRETTY | JV_PRINT_COLOUR;
jv_dumpf(jv_copy(x), stderr, flags);
fflush(stderr);
}
jv jv_dump_string(jv x, int flags) {
struct dtoa_context C;
jvp_dtoa_context_init(&C);
jv s = jv_string("");
jv_dump_term(&C, x, flags, 0, 0, &s);
jvp_dtoa_context_free(&C);
return s;
}
+89
View File
@@ -0,0 +1,89 @@
#include <stdio.h>
#include <assert.h>
#include "jv_unicode.h"
#include "jv_utf8_tables.h"
const char* jvp_utf8_next(const char* in, const char* end, int* codepoint_ret) {
assert(in <= end);
if (in == end) {
return 0;
}
int codepoint = -1;
unsigned char first = (unsigned char)in[0];
int length = utf8_coding_length[first];
if ((first & 0x80) == 0) {
/* Fast-path for ASCII */
codepoint = first;
length = 1;
} else if (length == 0 || length == UTF8_CONTINUATION_BYTE) {
/* Bad single byte - either an invalid byte or an out-of-place continuation byte */
length = 1;
} else if (in + length > end) {
/* String ends before UTF8 sequence ends */
length = end - in;
} else {
codepoint = ((unsigned)in[0]) & utf8_coding_bits[first];
for (int i=1; i<length; i++) {
unsigned ch = (unsigned char)in[i];
if (utf8_coding_length[ch] != UTF8_CONTINUATION_BYTE){
/* Invalid UTF8 sequence - not followed by the right number of continuation bytes */
codepoint = -1;
length = i;
break;
}
codepoint = (codepoint << 6) | (ch & 0x3f);
}
if (codepoint < utf8_first_codepoint[length]) {
/* Overlong UTF8 sequence */
codepoint = -1;
}
if (0xD800 <= codepoint && codepoint <= 0xDFFF) {
/* Surrogate codepoints can't be encoded in UTF8 */
codepoint = -1;
}
if (codepoint > 0x10FFFF) {
/* Outside Unicode range */
codepoint = -1;
}
}
assert(length > 0);
*codepoint_ret = codepoint;
return in + length;
}
int jvp_utf8_is_valid(const char* in, const char* end) {
int codepoint;
while ((in = jvp_utf8_next(in, end, &codepoint))) {
if (codepoint == -1) return 0;
}
return 1;
}
int jvp_utf8_encode_length(int codepoint) {
if (codepoint <= 0x7F) return 1;
else if (codepoint <= 0x7FF) return 2;
else if (codepoint <= 0xFFFF) return 3;
else return 4;
}
int jvp_utf8_encode(int codepoint, char* out) {
assert(codepoint >= 0 && codepoint <= 0x10FFFF);
char* start = out;
if (codepoint <= 0x7F) {
*out++ = codepoint;
} else if (codepoint <= 0x7FF) {
*out++ = 0xC0 + ((codepoint & 0x7C0) >> 6);
*out++ = 0x80 + ((codepoint & 0x03F));
} else if(codepoint <= 0xFFFF) {
*out++ = 0xE0 + ((codepoint & 0xF000) >> 12);
*out++ = 0x80 + ((codepoint & 0x0FC0) >> 6);
*out++ = 0x80 + ((codepoint & 0x003F));
} else {
*out++ = 0xF0 + ((codepoint & 0x1C0000) >> 18);
*out++ = 0x80 + ((codepoint & 0x03F000) >> 12);
*out++ = 0x80 + ((codepoint & 0x000FC0) >> 6);
*out++ = 0x80 + ((codepoint & 0x00003F));
}
assert(out - start == jvp_utf8_encode_length(codepoint));
return out - start;
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef JV_UNICODE_H
#define JV_UNICODE_H
const char* jvp_utf8_next(const char* in, const char* end, int* codepoint);
int jvp_utf8_is_valid(const char* in, const char* end);
int jvp_utf8_decode_length(char startchar);
int jvp_utf8_encode_length(int codepoint);
int jvp_utf8_encode(int codepoint, char* out);
#endif
+37
View File
@@ -0,0 +1,37 @@
#define UTF8_CONTINUATION_BYTE ((unsigned char)255)
static const unsigned char utf8_coding_length[] =
{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const unsigned char utf8_coding_bits[] =
{0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x07, 0x07, 0x07, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const int utf8_first_codepoint[] =
{0x00, 0x00, 0x80, 0x800, 0x10000};
+175
View File
@@ -0,0 +1,175 @@
%{
#include <assert.h>
#include "jv_alloc.h"
#include "compile.h"
struct lexer_param;
#include "parser.h" /* Generated by bison. */
#define YY_USER_ACTION \
do { \
yylloc->start = yyget_extra(yyscanner); \
yylloc->end = yylloc->start + yyleng; \
yyset_extra(yylloc->end, yyscanner); \
} while (0);
%}
%s IN_PAREN
%s IN_BRACKET
%s IN_BRACE
%s IN_QQINTERP
%x IN_QQSTRING
%{
static int enter(int opening, int state, yyscan_t yyscanner);
static int try_exit(int closing, int state, yyscan_t yyscanner);
%}
%option noyywrap nounput noinput nodefault
%option noyyalloc noyyrealloc noyyfree
%option reentrant
%option extra-type="int"
%option bison-bridge bison-locations
%option prefix="jq_yy"
%option stack
%%
"#"[^\r\n]* { /* comments */ }
"!=" { return NEQ; }
"==" { return EQ; }
"as" { return AS; }
"def" { return DEF; }
"if" { return IF; }
"then" { return THEN; }
"else" { return ELSE; }
"elif" { return ELSE_IF; }
"and" { return AND; }
"or" { return OR; }
"end" { return END; }
"reduce" { return REDUCE; }
"//" { return DEFINEDOR; }
"|=" { return SETPIPE; }
"+=" { return SETPLUS; }
"-=" { return SETMINUS; }
"*=" { return SETMULT; }
"/=" { return SETDIV; }
"%=" { return SETMOD; }
"//=" { return SETDEFINEDOR; }
"<=" { return LESSEQ; }
">=" { return GREATEREQ; }
".." { return REC; }
"."|"?"|"="|";"|","|":"|"|"|"+"|"-"|"*"|"/"|"%"|"\$"|"<"|">" { return yytext[0];}
"["|"{"|"(" {
return enter(yytext[0], YY_START, yyscanner);
}
"]"|"}"|")" {
return try_exit(yytext[0], YY_START, yyscanner);
}
"@"[a-zA-Z0-9_]+ {
yylval->literal = jv_string_sized(yytext + 1, yyleng - 1); return FORMAT;
}
[0-9.]+([eE][+-]?[0-9]+)? {
yylval->literal = jv_parse_sized(yytext, yyleng); return LITERAL;
}
"\"" {
yy_push_state(IN_QQSTRING, yyscanner);
return QQSTRING_START;
}
<IN_QQSTRING>{
"\\(" {
return enter(QQSTRING_INTERP_START, YY_START, yyscanner);
}
"\"" {
yy_pop_state(yyscanner);
return QQSTRING_END;
}
(\\[^u(]|\\u[a-zA-Z0-9]{0,4})+ {
/* pass escapes to the json parser */
jv escapes = jv_string_fmt("\"%.*s\"", yyleng, yytext);
yylval->literal = jv_parse_sized(jv_string_value(escapes), jv_string_length_bytes(jv_copy(escapes)));
jv_free(escapes);
return QQSTRING_TEXT;
}
[^\\\"]+ {
yylval->literal = jv_string_sized(yytext, yyleng);
return QQSTRING_TEXT;
}
. {
return INVALID_CHARACTER;
}
}
[a-zA-Z_][a-zA-Z_0-9]* { yylval->literal = jv_string(yytext); return IDENT;}
\.[a-zA-Z_][a-zA-Z_0-9]* { yylval->literal = jv_string(yytext+1); return FIELD;}
[ \n\t]+ {}
. { return INVALID_CHARACTER; }
%%
/* perhaps these should be calls... */
/*
"true" { return TRUE; }
"false" { return FALSE; }
"null" { return NULL; }
*/
static int try_exit(int c, int state, yyscan_t yyscanner) {
char match = 0;
int ret;
switch (state) {
case IN_PAREN: match = ret = ')'; break;
case IN_BRACKET: match = ret = ']'; break;
case IN_BRACE: match = ret = '}'; break;
case IN_QQINTERP:
match = ')';
ret = QQSTRING_INTERP_END;
break;
default:
// may not be the best error to give
return INVALID_CHARACTER;
}
assert(match);
if (match == c) {
yy_pop_state(yyscanner);
return ret;
} else {
// FIXME: should we pop? Give a better error at least
return INVALID_CHARACTER;
}
}
static int enter(int c, int currstate, yyscan_t yyscanner) {
int state = 0;
switch (c) {
case '(': state = IN_PAREN; break;
case '[': state = IN_BRACKET; break;
case '{': state = IN_BRACE; break;
case QQSTRING_INTERP_START: state = IN_QQINTERP; break;
}
assert(state);
yy_push_state(state, yyscanner);
return c;
}
void* yyalloc(size_t sz, void* extra) {
return jv_mem_alloc(sz);
}
void* yyrealloc(void* p, size_t sz, void* extra) {
return jv_mem_realloc(p, sz);
}
void yyfree(void* p, void* extra) {
jv_mem_free(p);
}
+34
View File
@@ -0,0 +1,34 @@
LIBM_DD(acos)
LIBM_DD(acosh)
LIBM_DD(asin)
LIBM_DD(asinh)
LIBM_DD(atan)
LIBM_DD(atanh)
LIBM_DD(cbrt)
LIBM_DD(cos)
LIBM_DD(cosh)
LIBM_DD(exp2)
LIBM_DD(exp)
LIBM_DD(floor)
LIBM_DD(j0)
LIBM_DD(j1)
LIBM_DD(log10)
LIBM_DD(log2)
LIBM_DD(log)
LIBM_DD(sin)
LIBM_DD(sinh)
LIBM_DD(sqrt)
LIBM_DD(tan)
LIBM_DD(tanh)
LIBM_DD(tgamma)
LIBM_DD(y0)
LIBM_DD(y1)
/* LIBM_DID(jn) */
/* LIBM_DID(yn) */
/* LIBM_DDD(pow) */
/* LIBM_DDD(atan2) */
/* LIBM_DDD(hypot) */
/* LIBM_DDD(remainder) */
/* LIBM_DDI(scalbn) */
/* LIBM_DDIP(lgamma_r) */
+103
View File
@@ -0,0 +1,103 @@
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "jq.h"
#include "jv_alloc.h"
#include "locfile.h"
void locfile_init(struct locfile* l, jq_state *jq, const char* data, int length) {
l->jq = jq;
l->data = data;
l->length = length;
l->nlines = 1;
for (int i=0; i<length; i++) {
if (data[i] == '\n') l->nlines++;
}
l->linemap = jv_mem_alloc(sizeof(int) * (l->nlines + 1));
l->linemap[0] = 0;
int line = 1;
for (int i=0; i<length; i++) {
if (data[i] == '\n') {
l->linemap[line] = i+1; // at start of line, not of \n
line++;
}
}
l->linemap[l->nlines] = length+1; // virtual last \n
}
void locfile_free(struct locfile* l) {
jv_mem_free(l->linemap);
}
static int locfile_get_line(struct locfile* l, int pos) {
assert(pos < l->length);
int line = 1;
while (l->linemap[line] <= pos) line++; // == if pos at start (before, never ==, because pos never on \n)
assert(line-1 < l->nlines);
return line-1;
}
static int locfile_line_length(struct locfile* l, int line) {
assert(line < l->nlines);
return l->linemap[line+1] - l->linemap[line] -1; // -1 to omit \n
}
void locfile_locate(struct locfile* l, location loc, const char* fmt, ...) {
jq_err_cb cb;
void *cb_data;
va_list fmtargs;
va_start(fmtargs, fmt);
int startline;
int offset;
if (loc.start != -1) {
startline = locfile_get_line(l, loc.start);
offset = l->linemap[startline];
}
jq_get_error_cb(l->jq, &cb, &cb_data);
jv m1 = jv_string_vfmt(fmt, fmtargs);
if (!jv_is_valid(m1)) {
jv_free(m1);
goto enomem;
}
jv m2;
if (loc.start == -1) {
m2 = jv_string_fmt("%s\n<unknown location>", jv_string_value(m1));
if (cb)
cb(cb_data, m2);
else
fprintf(stderr, "%s", jv_string_value(m2));
jv_free(m1);
jv_free(m2);
return;
}
m2 = jv_string_fmt("%s\n%.*s%*s", jv_string_value(m1),
locfile_line_length(l, startline), l->data + offset,
loc.start - offset, "");
jv_free(m1);
if (!jv_is_valid(m2)) {
jv_free(m2);
goto enomem;
}
if (cb)
cb(cb_data, m2);
else
fprintf(stderr, "%s", jv_string_value(m2));
jv_free(m2);
return;
enomem:
if (cb != NULL)
cb(cb_data, jv_invalid());
else if (errno == ENOMEM || errno == 0)
fprintf(stderr, "Error formatting jq compilation error: %s", strerror(errno ? errno : ENOMEM));
else
fprintf(stderr, "Error formatting jq compilation error: %s", strerror(errno));
return;
}
+27
View File
@@ -0,0 +1,27 @@
#ifndef _LOCFILE_H
#define _LOCFILE_H
#include "jq.h"
typedef struct {
int start, end;
} location;
static const location UNKNOWN_LOCATION = {-1, -1};
struct locfile {
const char* data;
int length;
int* linemap;
int nlines;
char *error;
jq_state *jq;
};
void locfile_init(struct locfile* l, jq_state *jq, const char* data, int length);
void locfile_free(struct locfile* l);
void locfile_locate(struct locfile* l, location loc, const char* fmt, ...);
#endif
+332
View File
@@ -0,0 +1,332 @@
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "compile.h"
#include "jv.h"
#include "jq.h"
#include "jv_alloc.h"
#include "version.h"
int jq_testsuite(int argc, char* argv[]);
static const char* progname;
static void usage() {
fprintf(stderr, "\njq - commandline JSON processor [version %s]\n", JQ_VERSION);
fprintf(stderr, "Usage: %s [options] <jq filter> [file...]\n\n", progname);
fprintf(stderr, "For a description of the command line options and\n");
fprintf(stderr, "how to write jq filters (and why you might want to)\n");
fprintf(stderr, "see the jq manpage, or the online documentation at\n");
fprintf(stderr, "http://stedolan.github.com/jq\n\n");
exit(2);
}
static void die() {
fprintf(stderr, "Use %s --help for help with command-line options,\n", progname);
fprintf(stderr, "or see the jq documentation at http://stedolan.github.com/jq\n");
exit(2);
}
static int isoptish(const char* text) {
return text[0] == '-' && (text[1] == '-' || isalpha(text[1]));
}
static int isoption(const char* text, char shortopt, const char* longopt) {
if (text[0] != '-') return 0;
if (strlen(text) == 2 && text[1] == shortopt) return 1;
if (text[1] == '-' && !strcmp(text+2, longopt)) return 1;
return 0;
}
enum {
SLURP = 1,
RAW_INPUT = 2,
PROVIDE_NULL = 4,
RAW_OUTPUT = 8,
COMPACT_OUTPUT = 16,
ASCII_OUTPUT = 32,
COLOUR_OUTPUT = 64,
NO_COLOUR_OUTPUT = 128,
SORTED_OUTPUT = 256,
UNBUFFERED_OUTPUT = 4096,
FROM_FILE = 512,
EXIT_STATUS = 8192,
/* debugging only */
DUMP_DISASM = 2048,
};
static int options = 0;
static int process(jq_state *jq, jv value, int flags) {
int ret = 14; // No valid results && -e -> exit(4)
jq_start(jq, value, flags);
jv result;
while (jv_is_valid(result = jq_next(jq))) {
if ((options & RAW_OUTPUT) && jv_get_kind(result) == JV_KIND_STRING) {
fwrite(jv_string_value(result), 1, jv_string_length_bytes(jv_copy(result)), stdout);
ret = 0;
jv_free(result);
} else {
int dumpopts;
/* Disable colour by default on Windows builds as Windows
terminals tend not to display it correctly */
#ifdef WIN32
dumpopts = 0;
#else
dumpopts = isatty(fileno(stdout)) ? JV_PRINT_COLOUR : 0;
#endif
if (options & SORTED_OUTPUT) dumpopts |= JV_PRINT_SORTED;
if (!(options & COMPACT_OUTPUT)) dumpopts |= JV_PRINT_PRETTY;
if (options & ASCII_OUTPUT) dumpopts |= JV_PRINT_ASCII;
if (options & COLOUR_OUTPUT) dumpopts |= JV_PRINT_COLOUR;
if (options & NO_COLOUR_OUTPUT) dumpopts &= ~JV_PRINT_COLOUR;
if (jv_get_kind(result) == JV_KIND_FALSE || jv_get_kind(result) == JV_KIND_NULL)
ret = 11;
else
ret = 0;
jv_dump(result, dumpopts);
}
printf("\n");
if (options & UNBUFFERED_OUTPUT)
fflush(stdout);
}
jv_free(result);
return ret;
}
FILE* current_input;
const char** input_filenames = NULL;
int ninput_files;
int next_input_idx;
static int read_more(char* buf, size_t size) {
while (!current_input || feof(current_input)) {
if (current_input) {
fclose(current_input);
current_input = 0;
}
if (next_input_idx == ninput_files) {
return 0;
}
if (!strcmp(input_filenames[next_input_idx], "-")) {
current_input = stdin;
} else {
current_input = fopen(input_filenames[next_input_idx], "r");
}
if (!current_input) {
fprintf(stderr, "%s: %s: %s\n", progname, input_filenames[next_input_idx], strerror(errno));
}
next_input_idx++;
}
if (!fgets(buf, size, current_input)) buf[0] = 0;
return 1;
}
int main(int argc, char* argv[]) {
jq_state *jq = NULL;
int ret = 0;
int compiled = 0;
if (argc) progname = argv[0];
if (argc > 1 && !strcmp(argv[1], "--run-tests")) {
return jq_testsuite(argc, argv);
}
jq = jq_init();
if (jq == NULL) {
perror("malloc");
ret = 2;
goto out;
}
const char* program = 0;
input_filenames = jv_mem_alloc(sizeof(const char*) * argc);
ninput_files = 0;
int further_args_are_files = 0;
int jq_flags = 0;
jv program_arguments = jv_array();
for (int i=1; i<argc; i++) {
if (further_args_are_files) {
input_filenames[ninput_files++] = argv[i];
} else if (!strcmp(argv[i], "--")) {
if (!program) usage();
further_args_are_files = 1;
} else if (!isoptish(argv[i])) {
if (program) {
input_filenames[ninput_files++] = argv[i];
} else {
program = argv[i];
}
} else if (isoption(argv[i], 's', "slurp")) {
options |= SLURP;
} else if (isoption(argv[i], 'r', "raw-output")) {
options |= RAW_OUTPUT;
} else if (isoption(argv[i], 'c', "compact-output")) {
options |= COMPACT_OUTPUT;
} else if (isoption(argv[i], 'C', "color-output")) {
options |= COLOUR_OUTPUT;
} else if (isoption(argv[i], 'M', "monochrome-output")) {
options |= NO_COLOUR_OUTPUT;
} else if (isoption(argv[i], 'a', "ascii-output")) {
options |= ASCII_OUTPUT;
} else if (isoption(argv[i], 0, "unbuffered")) {
options |= UNBUFFERED_OUTPUT;
} else if (isoption(argv[i], 'S', "sort-keys")) {
options |= SORTED_OUTPUT;
} else if (isoption(argv[i], 'R', "raw-input")) {
options |= RAW_INPUT;
} else if (isoption(argv[i], 'n', "null-input")) {
options |= PROVIDE_NULL;
} else if (isoption(argv[i], 'f', "from-file")) {
options |= FROM_FILE;
} else if (isoption(argv[i], 'e', "exit-status")) {
options |= EXIT_STATUS;
} else if (isoption(argv[i], 0, "arg")) {
if (i >= argc - 2) {
fprintf(stderr, "%s: --arg takes two parameters (e.g. -a varname value)\n", progname);
die();
}
jv arg = jv_object();
arg = jv_object_set(arg, jv_string("name"), jv_string(argv[i+1]));
arg = jv_object_set(arg, jv_string("value"), jv_string(argv[i+2]));
program_arguments = jv_array_append(program_arguments, arg);
i += 2; // skip the next two arguments
} else if (isoption(argv[i], 0, "argfile")) {
if (i >= argc - 2) {
fprintf(stderr, "%s: --argfile takes two parameters (e.g. -a varname filename)\n", progname);
die();
}
jv arg = jv_object();
arg = jv_object_set(arg, jv_string("name"), jv_string(argv[i+1]));
jv data = jv_load_file(argv[i+2], 0);
if (!jv_is_valid(data)) {
data = jv_invalid_get_msg(data);
fprintf(stderr, "%s: Bad JSON in --argfile %s %s: %s\n", progname,
argv[i+1], argv[i+2], jv_string_value(data));
jv_free(data);
ret = 2;
goto out;
}
if (jv_get_kind(data) == JV_KIND_ARRAY && jv_array_length(jv_copy(data)) == 1)
data = jv_array_get(data, 0);
arg = jv_object_set(arg, jv_string("value"), data);
program_arguments = jv_array_append(program_arguments, arg);
i += 2; // skip the next two arguments
} else if (isoption(argv[i], 0, "debug-dump-disasm")) {
options |= DUMP_DISASM;
} else if (isoption(argv[i], 0, "debug-trace")) {
jq_flags |= JQ_DEBUG_TRACE;
} else if (isoption(argv[i], 'h', "help")) {
usage();
} else if (isoption(argv[i], 'V', "version")) {
printf("jq-%s\n", JQ_VERSION);
ret = 0;
goto out;
} else {
fprintf(stderr, "%s: Unknown option %s\n", progname, argv[i]);
die();
}
}
if (!program) usage();
if (ninput_files == 0) current_input = stdin;
if ((options & PROVIDE_NULL) && (options & (RAW_INPUT | SLURP))) {
fprintf(stderr, "%s: --null-input cannot be used with --raw-input or --slurp\n", progname);
die();
}
if (options & FROM_FILE) {
jv data = jv_load_file(program, 1);
if (!jv_is_valid(data)) {
data = jv_invalid_get_msg(data);
fprintf(stderr, "%s: %s\n", progname, jv_string_value(data));
jv_free(data);
ret = 2;
goto out;
}
compiled = jq_compile_args(jq, jv_string_value(data), program_arguments);
jv_free(data);
} else {
compiled = jq_compile_args(jq, program, program_arguments);
}
if (!compiled){
ret = 3;
goto out;
}
if (options & DUMP_DISASM) {
jq_dump_disassembly(jq, 0);
printf("\n");
}
if (options & PROVIDE_NULL) {
ret = process(jq, jv_null(), jq_flags);
} else {
jv slurped;
if (options & SLURP) {
if (options & RAW_INPUT) {
slurped = jv_string("");
} else {
slurped = jv_array();
}
}
struct jv_parser* parser = jv_parser_new(0);
char buf[4096];
while (read_more(buf, sizeof(buf))) {
if (options & RAW_INPUT) {
int len = strlen(buf);
if (len > 0) {
if (options & SLURP) {
slurped = jv_string_concat(slurped, jv_string(buf));
} else {
if (buf[len-1] == '\n') buf[len-1] = 0;
ret = process(jq, jv_string(buf), jq_flags);
}
}
} else {
jv_parser_set_buf(parser, buf, strlen(buf), !feof(stdin));
jv value;
while (jv_is_valid((value = jv_parser_next(parser)))) {
if (options & SLURP) {
slurped = jv_array_append(slurped, value);
} else {
ret = process(jq, value, jq_flags);
}
}
if (jv_invalid_has_msg(jv_copy(value))) {
jv msg = jv_invalid_get_msg(value);
fprintf(stderr, "parse error: %s\n", jv_string_value(msg));
jv_free(msg);
ret = 4;
break;
} else {
jv_free(value);
}
}
}
jv_parser_free(parser);
if (ret != 0)
goto out;
if (options & SLURP) {
ret = process(jq, slurped, jq_flags);
}
}
out:
jv_mem_free(input_filenames);
jq_teardown(&jq);
if (ret >= 10 && (options & EXIT_STATUS))
return ret - 10;
if (ret >= 10)
return 0;
return ret;
}
+34
View File
@@ -0,0 +1,34 @@
OP(LOADK, CONSTANT, 1, 1)
OP(DUP, NONE, 1, 2)
OP(DUP2, NONE, 2, 3)
OP(POP, NONE, 1, 0)
OP(LOADV, VARIABLE, 1, 1)
OP(LOADVN, VARIABLE, 1, 1)
OP(STOREV, VARIABLE, 1, 0)
OP(INDEX, NONE, 2, 1)
OP(INDEX_OPT, NONE, 2, 1)
OP(EACH, NONE, 1, 1)
OP(EACH_OPT, NONE, 1, 1)
OP(FORK, BRANCH, 0, 0)
OP(JUMP, BRANCH, 0, 0)
OP(JUMP_F,BRANCH, 1, 0)
OP(BACKTRACK, NONE, 0, 0)
OP(APPEND, VARIABLE,1, 0)
OP(INSERT, NONE, 4, 2)
OP(RANGE, VARIABLE, 1, 1)
OP(SUBEXP_BEGIN, NONE, 1, 2)
OP(SUBEXP_END, NONE, 2, 2)
OP(PATH_BEGIN, NONE, 1, 2)
OP(PATH_END, NONE, 2, 1)
OP(CALL_BUILTIN, CFUNC, -1, 1)
OP(CALL_JQ, UFUNC, 1, 1)
OP(RET, NONE, 1, 1)
OP(CLOSURE_PARAM, DEFINITION, 0, 0)
OP(CLOSURE_REF, CLOSURE_REF_IMM, 0, 0)
OP(CLOSURE_CREATE, DEFINITION, 0, 0)
OP(CLOSURE_CREATE_C, DEFINITION, 0, 0)
+664
View File
@@ -0,0 +1,664 @@
%{
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "compile.h"
#include "jv_alloc.h"
#define YYMALLOC jv_mem_alloc
#define YYFREE jv_mem_free
%}
%code requires {
#include "locfile.h"
struct lexer_param;
#define YYLTYPE location
#define YYLLOC_DEFAULT(Loc, Rhs, N) \
do { \
if (N) { \
(Loc).start = YYRHSLOC(Rhs, 1).start; \
(Loc).end = YYRHSLOC(Rhs, N).end; \
} else { \
(Loc).start = YYRHSLOC(Rhs, 0).end; \
(Loc).end = YYRHSLOC(Rhs, 0).end; \
} \
} while (0)
}
%locations
%error-verbose
%define api.pure
%union {
jv literal;
block blk;
}
%destructor { jv_free($$); } <literal>
%destructor { block_free($$); } <blk>
%parse-param {block* answer}
%parse-param {int* errors}
%parse-param {struct locfile* locations}
%parse-param {struct lexer_param* lexer_param_ptr}
%lex-param {block* answer}
%lex-param {int* errors}
%lex-param {struct locfile* locations}
%lex-param {struct lexer_param* lexer_param_ptr}
%token INVALID_CHARACTER
%token <literal> IDENT
%token <literal> FIELD
%token <literal> LITERAL
%token <literal> FORMAT
%token Q "?"
%token REC ".."
%token SETMOD "%="
%token EQ "=="
%token NEQ "!="
%token DEFINEDOR "//"
%token AS "as"
%token DEF "def"
%token IF "if"
%token THEN "then"
%token ELSE "else"
%token ELSE_IF "elif"
%token REDUCE "reduce"
%token END "end"
%token AND "and"
%token OR "or"
%token SETPIPE "|="
%token SETPLUS "+="
%token SETMINUS "-="
%token SETMULT "*="
%token SETDIV "/="
%token SETDEFINEDOR "//="
%token LESSEQ "<="
%token GREATEREQ ">="
%token QQSTRING_START
%token <literal> QQSTRING_TEXT
%token QQSTRING_INTERP_START
%token QQSTRING_INTERP_END
%token QQSTRING_END
/* revolting hack */
%left ';'
%right '|'
%left ','
%right "//"
%nonassoc '=' SETPIPE SETPLUS SETMINUS SETMULT SETDIV SETMOD SETDEFINEDOR
%left OR
%left AND
%nonassoc NEQ EQ '<' '>' LESSEQ GREATEREQ
%left '+' '-'
%left '*' '/' '%'
%type <blk> Exp Term MkDict MkDictPair ExpD ElseBody QQString FuncDef FuncDefs String
%{
#include "lexer.h"
struct lexer_param {
yyscan_t lexer;
};
#define FAIL(loc, msg) \
do { \
location l = loc; \
yyerror(&l, answer, errors, locations, lexer_param_ptr, msg); \
/*YYERROR*/; \
} while (0)
void yyerror(YYLTYPE* loc, block* answer, int* errors,
struct locfile* locations, struct lexer_param* lexer_param_ptr, const char *s){
(*errors)++;
locfile_locate(locations, *loc, "error: %s", s);
}
int yylex(YYSTYPE* yylval, YYLTYPE* yylloc, block* answer, int* errors,
struct locfile* locations, struct lexer_param* lexer_param_ptr) {
yyscan_t lexer = lexer_param_ptr->lexer;
int tok = jq_yylex(yylval, yylloc, lexer);
if ((tok == LITERAL || tok == QQSTRING_TEXT) && !jv_is_valid(yylval->literal)) {
jv msg = jv_invalid_get_msg(jv_copy(yylval->literal));
if (jv_get_kind(msg) == JV_KIND_STRING) {
FAIL(*yylloc, jv_string_value(msg));
} else {
FAIL(*yylloc, "Invalid literal");
}
jv_free(msg);
jv_free(yylval->literal);
yylval->literal = jv_null();
}
return tok;
}
static block gen_dictpair(block k, block v) {
return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT));
}
static block gen_index(block obj, block key) {
return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX));
}
static block gen_index_opt(block obj, block key) {
return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX_OPT));
}
static block gen_slice_index(block obj, block start, block end, opcode idx_op) {
block key = BLOCK(gen_subexp(gen_const(jv_object())),
gen_subexp(gen_const(jv_string("start"))),
gen_subexp(start),
gen_op_simple(INSERT),
gen_subexp(gen_const(jv_string("end"))),
gen_subexp(end),
gen_op_simple(INSERT));
return BLOCK(key, obj, gen_op_simple(idx_op));
}
static block gen_binop(block a, block b, int op) {
const char* funcname = 0;
switch (op) {
case '+': funcname = "_plus"; break;
case '-': funcname = "_minus"; break;
case '*': funcname = "_multiply"; break;
case '/': funcname = "_divide"; break;
case '%': funcname = "_mod"; break;
case EQ: funcname = "_equal"; break;
case NEQ: funcname = "_notequal"; break;
case '<': funcname = "_less"; break;
case '>': funcname = "_greater"; break;
case LESSEQ: funcname = "_lesseq"; break;
case GREATEREQ: funcname = "_greatereq"; break;
}
assert(funcname);
return gen_call(funcname, BLOCK(gen_lambda(a), gen_lambda(b)));
}
static block gen_format(block a, jv fmt) {
return BLOCK(a, gen_call("format", BLOCK(gen_lambda(gen_const(fmt)))));
}
static block gen_definedor_assign(block object, block val) {
block tmp = gen_op_var_fresh(STOREV, "tmp");
return BLOCK(gen_op_simple(DUP),
val, tmp,
gen_call("_modify", BLOCK(gen_lambda(object),
gen_lambda(gen_definedor(gen_noop(),
gen_op_bound(LOADV, tmp))))));
}
static block gen_update(block object, block val, int optype) {
block tmp = gen_op_var_fresh(STOREV, "tmp");
return BLOCK(gen_op_simple(DUP),
val,
tmp,
gen_call("_modify", BLOCK(gen_lambda(object),
gen_lambda(gen_binop(gen_noop(),
gen_op_bound(LOADV, tmp),
optype)))));
}
%}
%%
TopLevel:
Exp {
*answer = $1;
} |
FuncDefs {
*answer = $1;
}
FuncDefs:
/* empty */ {
$$ = gen_noop();
} |
FuncDef FuncDefs {
$$ = block_join($1, $2);
}
Exp:
FuncDef Exp %prec ';' {
$$ = block_bind($1, $2, OP_IS_CALL_PSEUDO);
} |
Term "as" '$' IDENT '|' Exp {
$$ = gen_var_binding($1, jv_string_value($4), $6);
jv_free($4);
} |
"reduce" Term "as" '$' IDENT '(' Exp ';' Exp ')' {
$$ = gen_reduce(jv_string_value($5), $2, $7, $9);
jv_free($5);
} |
"if" Exp "then" Exp ElseBody {
$$ = gen_cond($2, $4, $5);
} |
"if" Exp "then" error {
FAIL(@$, "Possibly unterminated 'if' statement");
$$ = $2;
} |
Exp '=' Exp {
$$ = gen_call("_assign", BLOCK(gen_lambda($1), gen_lambda($3)));
} |
Exp "or" Exp {
$$ = gen_or($1, $3);
} |
Exp "and" Exp {
$$ = gen_and($1, $3);
} |
Exp "//" Exp {
$$ = gen_definedor($1, $3);
} |
Exp "//=" Exp {
$$ = gen_definedor_assign($1, $3);
} |
Exp "|=" Exp {
$$ = gen_call("_modify", BLOCK(gen_lambda($1), gen_lambda($3)));
} |
Exp '|' Exp {
$$ = block_join($1, $3);
} |
Exp ',' Exp {
$$ = gen_both($1, $3);
} |
Exp '+' Exp {
$$ = gen_binop($1, $3, '+');
} |
Exp "+=" Exp {
$$ = gen_update($1, $3, '+');
} |
'-' Exp {
$$ = BLOCK($2, gen_call("_negate", gen_noop()));
} |
Exp '-' Exp {
$$ = gen_binop($1, $3, '-');
} |
Exp "-=" Exp {
$$ = gen_update($1, $3, '-');
} |
Exp '*' Exp {
$$ = gen_binop($1, $3, '*');
} |
Exp "*=" Exp {
$$ = gen_update($1, $3, '*');
} |
Exp '/' Exp {
$$ = gen_binop($1, $3, '/');
} |
Exp '%' Exp {
$$ = gen_binop($1, $3, '%');
} |
Exp "/=" Exp {
$$ = gen_update($1, $3, '/');
} |
Exp SETMOD Exp {
$$ = gen_update($1, $3, '%');
} |
Exp "==" Exp {
$$ = gen_binop($1, $3, EQ);
} |
Exp "!=" Exp {
$$ = gen_binop($1, $3, NEQ);
} |
Exp '<' Exp {
$$ = gen_binop($1, $3, '<');
} |
Exp '>' Exp {
$$ = gen_binop($1, $3, '>');
} |
Exp "<=" Exp {
$$ = gen_binop($1, $3, LESSEQ);
} |
Exp ">=" Exp {
$$ = gen_binop($1, $3, GREATEREQ);
} |
Term {
$$ = $1;
}
FuncDef:
"def" IDENT ':' Exp ';' {
$$ = gen_function(jv_string_value($2), gen_noop(), $4);
jv_free($2);
} |
"def" IDENT '(' IDENT ')' ':' Exp ';' {
$$ = gen_function(jv_string_value($2),
gen_param(jv_string_value($4)),
$7);
jv_free($2);
jv_free($4);
} |
"def" IDENT '(' IDENT ';' IDENT ')' ':' Exp ';' {
$$ = gen_function(jv_string_value($2),
BLOCK(gen_param(jv_string_value($4)),
gen_param(jv_string_value($6))),
$9);
jv_free($2);
jv_free($4);
jv_free($6);
} |
"def" IDENT '(' IDENT ';' IDENT ';' IDENT ')' ':' Exp ';' {
$$ = gen_function(jv_string_value($2),
BLOCK(gen_param(jv_string_value($4)),
gen_param(jv_string_value($6)),
gen_param(jv_string_value($8))),
$11);
jv_free($2);
jv_free($4);
jv_free($6);
jv_free($8);
} |
"def" IDENT '(' IDENT ';' IDENT ';' IDENT ';' IDENT ')' ':' Exp ';' {
$$ = gen_function(jv_string_value($2),
BLOCK(gen_param(jv_string_value($4)),
gen_param(jv_string_value($6)),
gen_param(jv_string_value($8)),
gen_param(jv_string_value($10))),
$13);
jv_free($2);
jv_free($4);
jv_free($6);
jv_free($8);
jv_free($10);
} |
"def" IDENT '(' IDENT ';' IDENT ';' IDENT ';' IDENT ';' IDENT ')' ':' Exp ';' {
$$ = gen_function(jv_string_value($2),
BLOCK(gen_param(jv_string_value($4)),
gen_param(jv_string_value($6)),
gen_param(jv_string_value($8)),
gen_param(jv_string_value($10)),
gen_param(jv_string_value($12))),
$15);
jv_free($2);
jv_free($4);
jv_free($6);
jv_free($8);
jv_free($10);
jv_free($12);
} |
"def" IDENT '(' IDENT ';' IDENT ';' IDENT ';' IDENT ';' IDENT ';' IDENT ')' ':' Exp ';' {
$$ = gen_function(jv_string_value($2),
BLOCK(gen_param(jv_string_value($4)),
gen_param(jv_string_value($6)),
gen_param(jv_string_value($8)),
gen_param(jv_string_value($10)),
gen_param(jv_string_value($12)),
gen_param(jv_string_value($14))),
$17);
jv_free($2);
jv_free($4);
jv_free($6);
jv_free($8);
jv_free($10);
jv_free($12);
jv_free($14);
}
String:
QQSTRING_START { $<literal>$ = jv_string("text"); } QQString QQSTRING_END {
$$ = $3;
jv_free($<literal>2);
} |
FORMAT QQSTRING_START { $<literal>$ = $1; } QQString QQSTRING_END {
$$ = $4;
jv_free($<literal>3);
}
QQString:
/* empty */ {
$$ = gen_const(jv_string(""));
} |
QQString QQSTRING_TEXT {
$$ = gen_binop($1, gen_const($2), '+');
} |
QQString QQSTRING_INTERP_START Exp QQSTRING_INTERP_END {
$$ = gen_binop($1, gen_format($3, jv_copy($<literal>0)), '+');
}
ElseBody:
"elif" Exp "then" Exp ElseBody {
$$ = gen_cond($2, $4, $5);
} |
"else" Exp "end" {
$$ = $2;
}
ExpD:
ExpD '|' ExpD {
$$ = block_join($1, $3);
} |
'-' ExpD {
$$ = BLOCK($2, gen_call("_negate", gen_noop()));
} |
Term {
$$ = $1;
}
Term:
'.' {
$$ = gen_noop();
} |
REC {
$$ = gen_call("recurse_down", gen_noop());
} |
Term FIELD '?' {
$$ = gen_index_opt($1, gen_const($2));
} |
FIELD '?' {
$$ = gen_index_opt(gen_noop(), gen_const($1));
} |
Term '.' String '?' {
$$ = gen_index_opt($1, $3);
} |
'.' String '?' {
$$ = gen_index_opt(gen_noop(), $2);
} |
Term FIELD {
$$ = gen_index($1, gen_const($2));
} |
FIELD {
$$ = gen_index(gen_noop(), gen_const($1));
} |
Term '.' String {
$$ = gen_index($1, $3);
} |
'.' String {
$$ = gen_index(gen_noop(), $2);
} |
'.' error {
FAIL(@$, "try .[\"field\"] instead of .field for unusually named fields");
$$ = gen_noop();
} |
'.' IDENT error {
jv_free($2);
FAIL(@$, "try .[\"field\"] instead of .field for unusually named fields");
$$ = gen_noop();
} |
/* FIXME: string literals */
Term '[' Exp ']' '?' {
$$ = gen_index_opt($1, $3);
} |
Term '[' Exp ']' {
$$ = gen_index($1, $3);
} |
Term '[' ']' '?' {
$$ = block_join($1, gen_op_simple(EACH_OPT));
} |
Term '[' ']' {
$$ = block_join($1, gen_op_simple(EACH));
} |
Term '[' Exp ':' Exp ']' '?' {
$$ = gen_slice_index($1, $3, $5, INDEX_OPT);
} |
Term '[' Exp ':' ']' '?' {
$$ = gen_slice_index($1, $3, gen_const(jv_null()), INDEX_OPT);
} |
Term '[' ':' Exp ']' '?' {
$$ = gen_slice_index($1, gen_const(jv_null()), $4, INDEX_OPT);
} |
Term '[' Exp ':' Exp ']' {
$$ = gen_slice_index($1, $3, $5, INDEX);
} |
Term '[' Exp ':' ']' {
$$ = gen_slice_index($1, $3, gen_const(jv_null()), INDEX);
} |
Term '[' ':' Exp ']' {
$$ = gen_slice_index($1, gen_const(jv_null()), $4, INDEX);
} |
LITERAL {
$$ = gen_const($1);
} |
String {
$$ = $1;
} |
FORMAT {
$$ = gen_format(gen_noop(), $1);
} |
'(' Exp ')' {
$$ = $2;
} |
'[' Exp ']' {
$$ = gen_collect($2);
} |
'[' ']' {
$$ = gen_const(jv_array());
} |
'{' MkDict '}' {
$$ = BLOCK(gen_subexp(gen_const(jv_object())), $2, gen_op_simple(POP));
} |
'$' IDENT {
$$ = gen_location(@$, gen_op_unbound(LOADV, jv_string_value($2)));
jv_free($2);
} |
IDENT {
$$ = gen_location(@$, gen_call(jv_string_value($1), gen_noop()));
jv_free($1);
} |
IDENT '(' Exp ')' {
$$ = gen_call(jv_string_value($1), gen_lambda($3));
$$ = gen_location(@1, $$);
jv_free($1);
} |
IDENT '(' Exp ';' Exp ')' {
$$ = gen_call(jv_string_value($1), BLOCK(gen_lambda($3), gen_lambda($5)));
$$ = gen_location(@1, $$);
jv_free($1);
} |
IDENT '(' Exp ';' Exp ';' Exp ')' {
$$ = gen_call(jv_string_value($1), BLOCK(gen_lambda($3), gen_lambda($5), gen_lambda($7)));
$$ = gen_location(@1, $$);
jv_free($1);
} |
IDENT '(' Exp ';' Exp ';' Exp ';' Exp ')' {
$$ = gen_call(jv_string_value($1), BLOCK(gen_lambda($3), gen_lambda($5), gen_lambda($7), gen_lambda($9)));
$$ = gen_location(@1, $$);
jv_free($1);
} |
IDENT '(' Exp ';' Exp ';' Exp ';' Exp ';' Exp ')' {
$$ = gen_call(jv_string_value($1), BLOCK(gen_lambda($3), gen_lambda($5), gen_lambda($7), gen_lambda($9), gen_lambda($11)));
$$ = gen_location(@1, $$);
jv_free($1);
} |
IDENT '(' Exp ';' Exp ';' Exp ';' Exp ';' Exp ';' Exp ')' {
$$ = gen_call(jv_string_value($1), BLOCK(gen_lambda($3), gen_lambda($5), gen_lambda($7), gen_lambda($9), gen_lambda($11), gen_lambda($13)));
$$ = gen_location(@1, $$);
jv_free($1);
} |
'(' error ')' { $$ = gen_noop(); } |
'[' error ']' { $$ = gen_noop(); } |
Term '[' error ']' { $$ = $1; } |
'{' error '}' { $$ = gen_noop(); }
MkDict:
{
$$=gen_noop();
} |
MkDictPair { $$ = $1; }
| MkDictPair ',' MkDict { $$=block_join($1, $3); }
| error ',' MkDict { $$ = $3; }
MkDictPair
: IDENT ':' ExpD {
$$ = gen_dictpair(gen_const($1), $3);
}
| String ':' ExpD {
$$ = gen_dictpair($1, $3);
}
| String {
$$ = gen_dictpair($1, BLOCK(gen_op_simple(POP), gen_op_simple(DUP2),
gen_op_simple(DUP2), gen_op_simple(INDEX)));
}
| IDENT {
$$ = gen_dictpair(gen_const(jv_copy($1)),
gen_index(gen_noop(), gen_const($1)));
}
| '(' Exp ')' ':' ExpD {
$$ = gen_dictpair($2, $5);
}
| '(' error ')' ':' ExpD { $$ = $5; }
%%
int jq_parse(struct locfile* locations, block* answer) {
struct lexer_param scanner;
YY_BUFFER_STATE buf;
jq_yylex_init_extra(0, &scanner.lexer);
buf = jq_yy_scan_bytes(locations->data, locations->length, scanner.lexer);
int errors = 0;
*answer = gen_noop();
yyparse(answer, &errors, locations, &scanner);
jq_yy_delete_buffer(buf, scanner.lexer);
jq_yylex_destroy(scanner.lexer);
if (errors > 0) {
block_free(*answer);
*answer = gen_noop();
}
return errors;
}
int jq_parse_library(struct locfile* locations, block* answer) {
int errs = jq_parse(locations, answer);
if (errs) return errs;
if (!block_has_only_binders(*answer, OP_IS_CALL_PSEUDO)) {
locfile_locate(locations, UNKNOWN_LOCATION, "error: library should only have function definitions, not a main expression");
return 1;
}
return 0;
}
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
# This script is used to cross-compile binaries for
# platforms other than the current one
# Usage: $0 <platformname> <configure options>
# <platformname> is arbitrary, it is the name
# of the directory which will be created to contain
# the output binaries.
# e.g. $0 win32 --host=i686-w64-mingw32
set -e
cd `dirname "$0"`/../build
jobs=-j4
case "X$1" in
X-j*) jobs=$1; shift;;
esac
plat="$1"
[ -z "$plat" ] && exit 1
shift
[ -d "$plat" ] || mkdir "$plat"
rm -rf "$plat/tmp"
mkdir "$plat/tmp"
cd "$plat/tmp"
mkdir install_other
../../../configure \
--prefix="`pwd`/install_other" \
--bindir="`pwd`/.." \
"$@"
make "$jobs" install-binaries
cd ..
rm -rf tmp
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/python
# This program was used to generate jv_utf8_tables.gen.h
mask = lambda n: (1 << n) - 1
def print_table(type, name, t):
print("static const %s %s[] =" % (type, name))
first = True
for i in range(0,len(t),16):
print ((" {" if i == 0 else " ") +
", ".join("0x%02x"%n for n in t[i:i+16]) +
("," if i + 16 < len(t) else "};"))
def utf8info(c):
if c < 0x80: return 1, mask(7)
if 0x80 <= c <= 0xBF: return 255, mask(6)
if 0xC0 <= c <= 0xC1: return 0, 0
if 0xC2 <= c <= 0xDF: return 2, mask(5)
if 0xE0 <= c <= 0xEF: return 3, mask(4)
if 0xF0 <= c <= 0xF4: return 4, mask(3)
if 0xF4 <= c <= 0xFF: return 0, 0
table = lambda i: [utf8info(c)[i] for c in range(256)]
print("#define UTF8_CONTINUATION_BYTE ((unsigned char)255)")
print_table("unsigned char", "utf8_coding_length", table(0))
print_table("unsigned char", "utf8_coding_bits", table(1))
print_table("int", "utf8_first_codepoint", [0, 0x0, 0x80, 0x800, 0x10000])
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
set -e
cd `dirname "$0"`
git rev-parse --verify -q jq-1.0 > /dev/null 2>&1
git describe --tags --match 'jq-*' --dirty | sed 's/^jq-//'
Executable
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# RUN ME after pulling the code from git!
#
# This script is handy after pulling the code out of git without having
# to remember all the typing needed to build up and tear down all the
# crap that autotools puts in your source repo. The intent is to keep
# all generated files out of git.
if [ "clean" == "$1" ]; then
make distclean || echo "continuing..."
find config/m4 -type f \( -name libtool\* -o -name lt\* \) | xargs rm -f
find config -name m4 -prune -o -type f -print | xargs rm -f
rm -rf tmp autom4te.cache
rm -f INSTALL Makefile.in aclocal.m4 configure config.h.in ChangeLog
rm -f jv_utf8_tables.gen.h lexer.c lexer.h parser.c parser.h
elif [ "superclean" == "$1" ]; then
# if autoconf errors during distcheck, it leaves files that need chmod'ing
ver=$(scripts/version|tr -d '\n')
if [ "x${ver}" != "x" ]; then
if [ -d jq-${ver} ]; then
chmod -R u+w jq-${ver}
rm -rf jq-${ver}
fi
rm -f jq-${ver}*
fi
else
autoreconf --install
./configure --prefix=/opt/junk
make check
[ -d tmp ] && mv tmp tmp-
mkdir tmp
make DESTDIR=./tmp install
make distcheck
fi
+843
View File
@@ -0,0 +1,843 @@
# Tests are groups of three lines: program, input, expected output
# Blank lines and lines starting with # are ignored
#
# Simple value tests to check parser. Input is irrelevant
#
true
null
true
false
null
false
null
42
null
1
null
1
-1
null
-1
# FIXME: much more number testing needed
{}
null
{}
[]
null
[]
{x: -1}
null
{"x": -1}
# The input line starts with a 0xFEFF (byte order mark) codepoint
# No, there is no reason to have a byte order mark in UTF8 text.
# But apparently people do, so jq shouldn't break on it.
.
"byte order mark"
"byte order mark"
# We test escapes by matching them against Unicode codepoints
# FIXME: more tests needed for weird unicode stuff (e.g. utf16 pairs)
"Aa\r\n\t\b\f\u03bc"
null
"Aa\u000d\u000a\u0009\u0008\u000c\u03bc"
.
"Aa\r\n\t\b\f\u03bc"
"Aa\u000d\u000a\u0009\u0008\u000c\u03bc"
"inter\("pol" + "ation")"
null
"interpolation"
@text,@json,([1,.] | @csv),@html,@uri,@sh,@base64
"<>&'\""
"<>&'\""
"\"<>&'\\\"\""
"1,\"<>&'\"\"\""
"&lt;&gt;&amp;&apos;&quot;"
"%3c%3e%26'%22"
"'<>&'\\''\"'"
"PD4mJyI="
@uri
"\u03bc"
"%ce%bc"
@html "<b>\(.)</b>"
"<script>hax</script>"
"<b>&lt;script&gt;hax&lt;/script&gt;</b>"
[.[]|tojson|fromjson]
["foo", 1, ["a", 1, "b", 2, {"foo":"bar"}]]
["foo",1,["a",1,"b",2,{"foo":"bar"}]]
#
# Dictionary construction syntax
#
{a: 1}
null
{"a":1}
{a,b,(.d):.a,e:.b}
{"a":1, "b":2, "c":3, "d":"c"}
{"a":1, "b":2, "c":1, "e":2}
{"a",b,"a$\(1+1)"}
{"a":1, "b":2, "c":3, "a$2":4}
{"a":1, "b":2, "a$2":4}
#
# Field access, piping
#
.foo
{"foo": 42, "bar": 43}
42
.foo | .bar
{"foo": {"bar": 42}, "bar": "badvalue"}
42
.foo.bar
{"foo": {"bar": 42}, "bar": "badvalue"}
42
.foo_bar
{"foo_bar": 2}
2
.["foo"].bar
{"foo": {"bar": 42}, "bar": "badvalue"}
42
."foo"."bar"
{"foo": {"bar": 20}}
20
[.[]|.foo?]
[1,[2],{"foo":3,"bar":4},{},{"foo":5}]
[3,null,5]
[.[]|.foo?.bar?]
[1,[2],[],{"foo":3},{"foo":{"bar":4}},{}]
[4,null]
[..]
[1,[[2]],{ "a":[1]}]
[[1,[[2]],{"a":[1]}],1,[[2]],[2],2,{"a":[1]},[1],1]
[.[]|.[]?]
[1,null,[],[1,[2,[[3]]]],[{}],[{"a":[1,[2]]}]]
[1,[2,[[3]]],{},{"a":[1,[2]]}]
[.[]|.[1:3]?]
[1,null,true,false,"abcdef",{},{"a":1,"b":2},[],[1,2,3,4,5],[1,2]]
[null,"bc",[],[2,3],[2]]
#
# Multiple outputs, iteration
#
.[]
[1,2,3]
1
2
3
1,1
[]
1
1
1,.
[]
1
[]
[.]
[2]
[[2]]
[[2]]
[3]
[[2]]
[{}]
[2]
[{}]
[.[]]
["a"]
["a"]
[(.,1),((.,.[]),(2,3))]
["a","b"]
[["a","b"],1,["a","b"],"a","b",2,3]
[([5,5][]),.,.[]]
[1,2,3]
[5,5,[1,2,3],1,2,3]
{x: (1,2)},{x:3} | .x
null
1
2
3
#
# Slices
#
[.[3:2], .[-5:4], .[:-2], .[-2:], .[3:3][1:], .[10:]]
[0,1,2,3,4,5,6]
[[], [2,3], [0,1,2,3,4], [5,6], [], []]
[.[3:2], .[-5:4], .[:-2], .[-2:], .[3:3][1:], .[10:]]
"abcdefghi"
["","","abcdefg","hi","",""]
del(.[2:4],.[0],.[-2:])
[0,1,2,3,4,5,6,7]
[1,4,5]
.[2:4] = ([], ["a","b"], ["a","b","c"])
[0,1,2,3,4,5,6,7]
[0,1,4,5,6,7]
[0,1,"a","b",4,5,6,7]
[0,1,"a","b","c",4,5,6,7]
#
# Variables
#
1 as $x | 2 as $y | [$x,$y,$x]
null
[1,2,1]
[1,2,3][] as $x | [[4,5,6,7][$x]]
null
[5]
[6]
[7]
42 as $x | . | . | . + 432 | $x + 1
34324
43
1 as $x | [$x,$x,$x as $x | $x]
null
[1,1,1]
# [.,(.[] | {x:.},.),.,.[]]
#
# Builtin functions
#
1+1
null
2
1+1
"wtasdf"
2.0
2-1
null
1
2-(-1)
null
3
1e+0+0.001e3
"I wonder what this will be?"
20e-1
.+4
15
19.0
.+null
{"a":42}
{"a":42}
null+.
null
null
.a+.b
{"a":42}
42
[1,2,3] + [.]
null
[1,2,3,null]
{"a":1} + {"b":2} + {"c":3}
"asdfasdf"
{"a":1, "b":2, "c":3}
"asdf" + "jkl;" + . + . + .
"some string"
"asdfjkl;some stringsome stringsome string"
"\u0000\u0020\u0000" + .
"\u0000\u0020\u0000"
"\u0000 \u0000\u0000 \u0000"
42 - .
11
31
[1,2,3,4,1] - [.,3]
1
[2,4]
[10 * 20, 20 / .]
4
[200, 5]
1 + 2 * 2 + 10 / 2
null
10
[16 / 4 / 2, 16 / 4 * 2, 16 - 4 - 2, 16 - 4 + 2]
null
[2, 8, 10, 14]
25 % 7
null
4
49732 % 472
null
172
1 + tonumber + ("10" | tonumber)
4
15
[{"a":42},.object,10,.num,false,true,null,"b",[1,4]] | .[] as $x | [$x == .[]]
{"object": {"a":42}, "num":10.0}
[true, true, false, false, false, false, false, false, false]
[true, true, false, false, false, false, false, false, false]
[false, false, true, true, false, false, false, false, false]
[false, false, true, true, false, false, false, false, false]
[false, false, false, false, true, false, false, false, false]
[false, false, false, false, false, true, false, false, false]
[false, false, false, false, false, false, true, false, false]
[false, false, false, false, false, false, false, true, false]
[false, false, false, false, false, false, false, false, true ]
[.[] | length]
[[], {}, [1,2], {"a":42}, "asdf", "\u03bc"]
[0, 0, 2, 1, 4, 1]
map(keys)
[{}, {"abcd":1,"abc":2,"abcde":3}, {"x":1, "z": 3, "y":2}]
[[], ["abc","abcd","abcde"], ["x","y","z"]]
[1,2,empty,3,empty,4]
null
[1,2,3,4]
map(add)
[[], [1,2,3], ["a","b","c"], [[3],[4,5],[6]], [{"a":1}, {"b":2}, {"a":3}]]
[null, 6, "abc", [3,4,5,6], {"a":3, "b": 2}]
#
# User-defined functions
# Oh god.
#
def f: . + 1; def g: def g: . + 100; f | g | f; (f | g), g
3.0
106.0
105.0
def f: (1000,2000); f
123412345
1000
2000
def f(a;b;c;d;e;f): [a+1,b,c,d,e,f]; f(.[0];.[1];.[0];.[0];.[0];.[0])
[1,2]
[2,2,1,1,1,1]
([1,2] + [4,5])
[1,2,3]
[1,2,4,5]
true
[1]
true
null,1,null
"hello"
null
1
null
[1,2,3]
[5,6]
[1,2,3]
[.[]|floor]
[-1.1,1.1,1.9]
[-2, 1, 1]
[.[]|sqrt]
[4,9]
[2,3]
(add / length) as $m | map((. - $m) as $d | $d * $d) | add / length | sqrt
[2,4,4,4,5,5,7,9]
2
# Should write a test that calls the -lm function from C (or bc(1)) to
# check that they match the corresponding jq functions. However,
# there's so little template code standing between that it suffices to
# test a handful of these. The results were checked by eye against
# bc(1).
atan * 4 * 1000000|floor / 1000000
1
3.141592
[(3.141592 / 2) * (range(0;20) / 20)|cos * 1000000|floor / 1000000]
null
[1,0.996917,0.987688,0.972369,0.951056,0.923879,0.891006,0.85264,0.809017,0.760406,0.707106,0.649448,0.587785,0.522498,0.45399,0.382683,0.309017,0.233445,0.156434,0.078459]
[(3.141592 / 2) * (range(0;20) / 20)|sin * 1000000|floor / 1000000]
null
[0,0.078459,0.156434,0.233445,0.309016,0.382683,0.45399,0.522498,0.587785,0.649447,0.707106,0.760405,0.809016,0.85264,0.891006,0.923879,0.951056,0.972369,0.987688,0.996917]
def f(x): x | x; f([.], . + [42])
[1,2,3]
[[[1,2,3]]]
[[1,2,3],42]
[[1,2,3,42]]
[1,2,3,42,42]
# test closures and lexical scoping
def id(x):x; 2000 as $x | def f(x):1 as $x | id([$x, x, x]); def g(x): 100 as $x | f($x,$x+x); g($x)
"more testing"
[1,100,2100.0,100,2100.0]
# test backtracking through function calls and returns
# this test is *evil*
[[20,10][1,0] as $x | def f: (100,200) as $y | def g: [$x + $y, .]; . + $x | g; f[0] | [f][0][1] | f]
999999999
[[110.0, 130.0], [210.0, 130.0], [110.0, 230.0], [210.0, 230.0], [120.0, 160.0], [220.0, 160.0], [120.0, 260.0], [220.0, 260.0]]
# test recursion
def fac: if . == 1 then 1 else . * (. - 1 | fac) end; [.[] | fac]
[1,2,3,4]
[1,2,6,24]
# test stack overflow and reallocation
# this test is disabled for now, it takes a realllllly long time.
# def f: if length > 1000 then . else .+[1]|f end; f | length
# []
# 1001
reduce .[] as $x (0; . + $x)
[1,2,4]
7
#
# Paths
#
path(.foo[0,1])
null
["foo", 0]
["foo", 1]
path(.[] | select(.>3))
[1,5,3]
[1]
path(.)
42
[]
[paths]
[1,[[],{"a":2}]]
[[0],[1],[1,0],[1,1],[1,1,"a"]]
[leaf_paths]
[1,[[],{"a":2}]]
[[0],[1,1,"a"]]
["foo",1] as $p | getpath($p), setpath($p; 20), delpaths([$p])
{"bar": 42, "foo": ["a", "b", "c", "d"]}
"b"
{"bar": 42, "foo": ["a", 20, "c", "d"]}
{"bar": 42, "foo": ["a", "c", "d"]}
map(getpath([2])), map(setpath([2]; 42)), map(delpaths([[2]]))
[[0], [0,1], [0,1,2]]
[null, null, 2]
[[0,null,42], [0,1,42], [0,1,42]]
[[0], [0,1], [0,1]]
map(delpaths([[0,"foo"]]))
[[{"foo":2, "x":1}], [{"bar":2}]]
[[{"x":1}], [{"bar":2}]]
["foo",1] as $p | getpath($p), setpath($p; 20), delpaths([$p])
{"bar":false}
null
{"bar":false, "foo": [null, 20]}
{"bar":false}
delpaths([[-200]])
[1,2,3]
[1,2,3]
del(.), del(empty), del((.foo,.bar,.baz) | .[2,3,0]), del(.foo[0], .bar[0], .foo, .baz.bar[0].x)
{"foo": [0,1,2,3,4], "bar": [0,1]}
null
{"foo": [0,1,2,3,4], "bar": [0,1]}
{"foo": [1,4], "bar": [1]}
{"bar": [1]}
#
# Assignment
#
.message = "goodbye"
{"message": "hello"}
{"message": "goodbye"}
.foo = .bar
{"bar":42}
{"foo":42, "bar":42}
.foo |= .+1
{"foo": 42}
{"foo": 43}
.[] += 2, .[] *= 2, .[] -= 2, .[] /= 2, .[] %=2
[1,3,5]
[3,5,7]
[2,6,10]
[-1,1,3]
[0.5, 1.5, 2.5]
[1,1,1]
[.[] % 7]
[-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7]
[0,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,0]
.foo += .foo
{"foo":2}
{"foo":4}
.[0].a |= {"old":., "new":(.+1)}
[{"a":1,"b":2}]
[{"a":{"old":1, "new":2},"b":2}]
def inc(x): x |= .+1; inc(.[].a)
[{"a":1,"b":2},{"a":2,"b":4},{"a":7,"b":8}]
[{"a":2,"b":2},{"a":3,"b":4},{"a":8,"b":8}]
.[2][3] = 1
[4]
[4, null, [null, null, null, 1]]
.foo[2].bar = 1
{"foo":[11], "bar":42}
{"foo":[11,null,{"bar":1}], "bar":42}
#
# Conditionals
#
[.[] | if .foo then "yep" else "nope" end]
[{"foo":0},{"foo":1},{"foo":[]},{"foo":true},{"foo":false},{"foo":null},{"foo":"foo"},{}]
["yep","yep","yep","yep","nope","nope","yep","nope"]
[.[] | if .baz then "strange" elif .foo then "yep" else "nope" end]
[{"foo":0},{"foo":1},{"foo":[]},{"foo":true},{"foo":false},{"foo":null},{"foo":"foo"},{}]
["yep","yep","yep","yep","nope","nope","yep","nope"]
# FIXME: define/test behaviour of 'if (.foo,.bar) then A else B end'
[.[] | [.foo[] // .bar]]
[{"foo":[1,2], "bar": 42}, {"foo":[1], "bar": null}, {"foo":[null,false,3], "bar": 18}, {"foo":[], "bar":42}, {"foo": [null,false,null], "bar": 41}]
[[1,2], [1], [3], [42], [41]]
.[] //= .[0]
["hello",true,false,[false],null]
["hello",true,"hello",[false],"hello"]
.[] | [.[0] and .[1], .[0] or .[1]]
[[true,[]], [false,1], [42,null], [null,false]]
[true,true]
[false,true]
[false,true]
[false,false]
[.[] | not]
[1,0,false,null,true,"hello"]
[false,false,true,true,false,false]
# Check numeric comparison binops
[10 > 0, 10 > 10, 10 > 20, 10 < 0, 10 < 10, 10 < 20]
{}
[true,false,false,false,false,true]
[10 >= 0, 10 >= 10, 10 >= 20, 10 <= 0, 10 <= 10, 10 <= 20]
{}
[true,true,false,false,true,true]
# And some in/equality tests
[ 10 == 10, 10 != 10, 10 != 11, 10 == 11]
{}
[true,false,true,false]
["hello" == "hello", "hello" != "hello", "hello" == "world", "hello" != "world" ]
{}
[true,false,false,true]
[[1,2,3] == [1,2,3], [1,2,3] != [1,2,3], [1,2,3] == [4,5,6], [1,2,3] != [4,5,6]]
{}
[true,false,false,true]
[{"foo":42} == {"foo":42},{"foo":42} != {"foo":42}, {"foo":42} != {"bar":42}, {"foo":42} == {"bar":42}]
{}
[true,false,true,false]
# ugly complicated thing
[{"foo":[1,2,{"bar":18},"world"]} == {"foo":[1,2,{"bar":18},"world"]},{"foo":[1,2,{"bar":18},"world"]} == {"foo":[1,2,{"bar":19},"world"]}]
{}
[true,false]
# containment operator
[("foo" | contains("foo")), ("foobar" | contains("foo")), ("foo" | contains("foobar"))]
{}
[true, true, false]
# string operations
[.[]|startswith("foo")]
["fo", "foo", "barfoo", "foobar", "barfoob"]
[false, true, false, true, false]
[.[]|endswith("foo")]
["fo", "foo", "barfoo", "foobar", "barfoob"]
[false, true, true, false, false]
[.[]|ltrimstr("foo")]
["fo", "foo", "barfoo", "foobar", "afoo"]
["fo","","barfoo","bar","afoo"]
[.[]|rtrimstr("foo")]
["fo", "foo", "barfoo", "foobar", "foob"]
["fo","","bar","foobar","foob"]
.[","]
"a,bc,def,ghij,klmno"
[1,4,8,13]
.[", "]
"a,bc,def,ghij,klmno"
[]
.[", "]
"a,b,, c, d,ef, , ghi, jklmn, o"
[4,7,13,15,20,27]
[(index(","), rindex(",")), indices(",")]
"a,bc,def,ghij,klmno"
[1,13,[1,4,8,13]]
indices(1)
[0,1,1,2,3,4,1,5]
[1,2,6]
indices([1,2])
[0,1,2,3,1,4,2,5,1,2,6,7]
[1,8]
indices([1,2])
[1]
[]
indices(", ")
"a,b, cd,e, fgh, ijkl"
[3,9,14]
[.[]|split(",")]
["a, bc, def, ghij, jklmn, a,b, c,d, e,f", "a,b,c,d, e,f,g,h"]
[["a"," bc"," def"," ghij"," jklmn"," a","b"," c","d"," e","f"],["a","b","c","d"," e","f","g","h"]]
[.[]|split(", ")]
["a, bc, def, ghij, jklmn, a,b, c,d, e,f", "a,b,c,d, e,f,g,h"]
[["a","bc","def","ghij","jklmn","a,b","c,d","e,f"],["a,b,c,d","e,f,g,h"]]
[.[] * 3]
["a", "ab", "abc"]
["aaa", "ababab", "abcabcabc"]
[.[] / ","]
["a, bc, def, ghij, jklmn, a,b, c,d, e,f", "a,b,c,d, e,f,g,h"]
[["a"," bc"," def"," ghij"," jklmn"," a","b"," c","d"," e","f"],["a","b","c","d"," e","f","g","h"]]
[.[] / ", "]
["a, bc, def, ghij, jklmn, a,b, c,d, e,f", "a,b,c,d, e,f,g,h"]
[["a","bc","def","ghij","jklmn","a,b","c,d","e,f"],["a,b,c,d","e,f,g,h"]]
map(.[1] as $needle | .[0] | contains($needle))
[[[],[]], [[1,2,3], [1,2]], [[1,2,3], [3,1]], [[1,2,3], [4]], [[1,2,3], [1,4]]]
[true, true, true, false, false]
map(.[1] as $needle | .[0] | contains($needle))
[[["foobar", "foobaz"], ["baz", "bar"]], [["foobar", "foobaz"], ["foo"]], [["foobar", "foobaz"], ["blap"]]]
[true, true, false]
[({foo: 12, bar:13} | contains({foo: 12})), ({foo: 12} | contains({})), ({foo: 12, bar:13} | contains({baz:14}))]
{}
[true, true, false]
{foo: {baz: 12, blap: {bar: 13}}, bar: 14} | contains({bar: 14, foo: {blap: {}}})
{}
true
{foo: {baz: 12, blap: {bar: 13}}, bar: 14} | contains({bar: 14, foo: {blap: {bar: 14}}})
{}
false
sort
[42,[2,5,3,11],10,{"a":42,"b":2},{"a":42},true,2,[2,6],"hello",null,[2,5,6],{"a":[],"b":1},"abc","ab",[3,10],{},false,"abcd",null]
[null,null,false,true,2,10,42,"ab","abc","abcd","hello",[2,5,3,11],[2,5,6],[2,6],[3,10],{},{"a":42},{"a":42,"b":2},{"a":[],"b":1}]
(sort_by(.b) | sort_by(.a)), sort_by(.a, .b), sort_by(.b, .c), group_by(.b), group_by(.a + .b - .c == 2)
[{"a": 1, "b": 4, "c": 14}, {"a": 4, "b": 1, "c": 3}, {"a": 1, "b": 4, "c": 3}, {"a": 0, "b": 2, "c": 43}]
[{"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}, {"a": 4, "b": 1, "c": 3}]
[{"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}, {"a": 4, "b": 1, "c": 3}]
[{"a": 4, "b": 1, "c": 3}, {"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 3}, {"a": 1, "b": 4, "c": 14}]
[[{"a": 4, "b": 1, "c": 3}], [{"a": 0, "b": 2, "c": 43}], [{"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}]]
[[{"a": 1, "b": 4, "c": 14}, {"a": 0, "b": 2, "c": 43}], [{"a": 4, "b": 1, "c": 3}, {"a": 1, "b": 4, "c": 3}]]
unique
[1,2,5,3,5,3,1,3]
[1,2,3,5]
unique
[]
[]
[min, max, min_by(.[1]), max_by(.[1]), min_by(.[2]), max_by(.[2])]
[[4,2,"a"],[3,1,"a"],[2,4,"a"],[1,3,"a"]]
[[1,3,"a"],[4,2,"a"],[3,1,"a"],[2,4,"a"],[4,2,"a"],[1,3,"a"]]
[min,max,min_by(.),max_by(.)]
[]
[null,null,null,null]
.foo[.baz]
{"foo":{"bar":4},"baz":"bar"}
4
.[] | .error = "no, it's OK"
[{"error":true}]
{"error": "no, it's OK"}
[{a:1}] | .[] | .a=999
null
{"a": 999}
to_entries
{"a": 1, "b": 2}
[{"key":"a", "value":1}, {"key":"b", "value":2}]
from_entries
[{"key":"a", "value":1}, {"key":"b", "value":2}]
{"a": 1, "b": 2}
with_entries(.key |= "KEY_" + .)
{"a": 1, "b": 2}
{"KEY_a": 1, "KEY_b": 2}
map(has("foo"))
[{"foo": 42}, {}]
[true, false]
map(has(2))
[[0,1], ["a","b","c"]]
[false, true]
keys
[42,3,35]
[0,1,2]
[][.]
1000000000000000000
null
map([1,2][0:.])
[-1, 1, 2, 3, 1000000000000000000]
[[1], [1], [1,2], [1,2], [1,2]]
# Test recursive object merge
{"k": {"a": 1, "b": 2}} * .
{"k": {"a": 0,"c": 3}}
{"k": {"a": 0, "b": 2, "c": 3}}
{"k": {"a": 1, "b": 2}, "hello": {"x": 1}} * .
{"k": {"a": 0,"c": 3}, "hello": 1}
{"k": {"a": 0, "b": 2, "c": 3}, "hello": 1}
{"k": {"a": 1, "b": 2}, "hello": 1} * .
{"k": {"a": 0,"c": 3}, "hello": {"x": 1}}
{"k": {"a": 0, "b": 2, "c": 3}, "hello": {"x": 1}}
{"a": {"b": 1}, "c": {"d": 2}, "e": 5} * .
{"a": {"b": 2}, "c": {"d": 3, "f": 9}}
{"a": {"b": 2}, "c": {"d": 3, "f": 9}, "e": 5}
[.[]|arrays]
[1,2,"foo",[],[3,[]],{},true,false,null]
[[],[3,[]]]
[.[]|objects]
[1,2,"foo",[],[3,[]],{},true,false,null]
[{}]
[.[]|iterables]
[1,2,"foo",[],[3,[]],{},true,false,null]
[[],[3,[]],{}]
[.[]|scalars]
[1,2,"foo",[],[3,[]],{},true,false,null]
[1,2,"foo",true,false,null]
[.[]|values]
[1,2,"foo",[],[3,[]],{},true,false,null]
[1,2,"foo",[],[3,[]],{},true,false]
[.[]|booleans]
[1,2,"foo",[],[3,[]],{},true,false,null]
[true,false]
[.[]|nulls]
[1,2,"foo",[],[3,[]],{},true,false,null]
[null]
Executable
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
if which valgrind > /dev/null; then
VALGRIND='valgrind --error-exitcode=1 -q --leak-check=full'
else
VALGRIND=
fi
cat $@ | $VALGRIND ./jq --run-tests