Repository: ppelleti/json65 Branch: master Commit: 878d295f1d51 Files: 43 Total size: 196.7 KB Directory structure: gitextract_3i7gmx_0/ ├── .gitignore ├── LICENSE.txt ├── README.md ├── examples/ │ ├── example.c │ └── input.json ├── run-tests.pl ├── src/ │ ├── json65-file.c │ ├── json65-file.h │ ├── json65-print.c │ ├── json65-print.h │ ├── json65-quote.h │ ├── json65-quote.s │ ├── json65-string.h │ ├── json65-string.s │ ├── json65-tree.c │ ├── json65-tree.h │ ├── json65.h │ └── json65.s ├── tests/ │ ├── create-testfile-disk-image.pl │ ├── file00.json │ ├── file01.json │ ├── file02.json │ ├── file03.json │ ├── file04.json │ ├── file05.json │ ├── file06.json │ ├── file07.json │ ├── test-file.c │ ├── test-print.c │ ├── test-print.json │ ├── test-quote.c │ ├── test-string.c │ ├── test-tree.c │ ├── test-tree.json │ └── test.c └── tools/ ├── README.md ├── asm-heading.hs ├── check-endproc.pl ├── convert_enums.pl ├── debug.inc ├── make_charprops.pl ├── make_dispatch.pl └── random-json.hs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *~ *.o *.map *.tmp test test-string test-tree test-quote test-print testfile.system testfile.po example.system ================================================ FILE: LICENSE.txt ================================================ Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: README.md ================================================ I was watching TV, and there was a commercial which proclaimed, "It's time to do what *you* want!" I replied to the TV, "It's time to write a JSON parser in 6502 assembly language?" Somehow I don't think that's what they had in mind, but the TV is right, I *should* do what I want. So, here is my JSON parser. The core parser is written entirely in 6502 assembly language, and is meant to be assembled with [ca65][1]. However, it is meant to be called from C, and uses the [cc65 calling convention][2] (specifically, the `fastcall` convention). JSON65 should work on any processor in the 6502 family. (It does not use any 65C02 instructions.) The assembly language parts of JSON65 use the zero page locations used by `cc65`, in a way which is compatible with the C calling convention. JSON65 should work on any target supported by the `cc65` toolchain. I have tested it on `sim65` and on an unenhanced Apple //e. ## Parser (json65.h) JSON65 is an event-driven (SAX-style) parser, so the parser is given a callback function, which it calls for each event. JSON65 supports incremental parsing, so you can freely feed it any sized chunks of input, and you don't need to have the whole file in memory at once. JSON65 is fully reentrant, so you can incrementally parse several files at once if you so desire. JSON65 does have a couple of limits: strings are limited to 255 bytes, and the nesting depth (of nested arrays or objects) is limited to 224. However, there is no limit on the length of a line, or the length of a file. JSON65 uses 512 bytes of memory for each parser, which must be allocated by the caller. JSON65 does not use dynamic memory allocation. In accordance with the [JSON specification][3], JSON65 assumes its input is UTF-8 encoded. However JSON65 does not validate the UTF-8, so any encoding can be used, as long as all bytes with the high bit clear represent ASCII characters. Bytes with the high bit set are only allowed inside strings. The only place where JSON65 assumes UTF-8 is in the processing of `\u` escape sequences. In accordance with the JSON specification, a single `\u` escape can be used to specify code points in the Basic Multilingual Plane, and two consecutive `\u` escapes (a UTF-16 surrogate pair) can be used to specify a code point outside the Basic Multilingual Plane. These escapes will be translated into the proper UTF-8. Because JSON only allows newlines in places where arbitrary whitespace is allowed, JSON65 is agnostic to the type of line ending. (CR, LF, or CRLF.) For the purposes of counting line numbers for error reporting, JSON65 handles CR, LF, or CRLF line endings. JSON65 will parse numbers which fit into a 32-bit signed long, and will provide the long to the callback. All other numbers (i. e. floating point numbers, or integers which overflow a 32-bit long) are provided to the callback as a string. (Like strings, numbers cannot be more than 255 digits long.) The callback function may return an error if it wishes. This will cause parsing to stop immediately, and the error code returned by the callback will be returned by `j65_parse()`. Error codes are negative numbers, and the user may use the codes from `J65_USER_ERROR` to `-1`, inclusive, for their own error codes. ## Tree interface (json65-tree.h) If you use the event-driver parser, you'll need to build your own data structure (or otherwise handle the data somehow) as the events come in. If you don't want to do that, you can use the tree interface (`json65-tree.h`) instead, which builds up a data structure for you. This only works for small files, because the entire tree has to fit in memory at once. Unlike the event-based parser, the tree interface uses dynamic memory allocation. ## Printing JSON (json65-print.h) Mostly, JSON65 is a parser. However, it does have some support for printing JSON back to a file, in `json65-print.h`. The function `j65_print_tree()` will print a JSON tree (from the tree interface in `json65-tree.h`) to a given filehandle. It prints the entire JSON tree on a single line with no whitespace. This is the most compact format for a machine-readable JSON file, but it is not particularly human-readable. If you write your own code to print JSON, either because you want to pretty-print it, or because you are using a data structure other than `j65_node`, you may still want to use the function `j65_print_escaped()` from `json65-quote.h`. It handles escaping a string using the JSON escape sequences. ## API documentation I don't have any fancy Doxygen documentation, but the API is documented by comments in the header files. If you wish to use the event-driven parser, read [json65.h](src/json65.h). If you wish to use the tree interface, read [json65-tree.h](src/json65-tree.h). ## Library organization If you simply wish to use the event-driven (SAX-style) parser, you only need one header file (`json65.h`) and one assembly file (`json65.s`). However, there are some helper functions in other files, which you can optionally use with JSON65 if you like. Most notable is the tree interface to JSON65, which you may use instead of the event-driven interface for small files. Each header file corresponds directly to one implementation file. Some of the implementation files are written in assembly language, and some are written in C. Here is a description of each, along with the size of the machine code of the implementation (`CODE` section plus `RODATA` section; none of the implementation files have any `DATA` or `BSS`). * [json65.h](src/json65.h) (2240 bytes) - The core, event-driven parser. This is the only file that is required if you wish to build your own data structure. * [json65-string.h](src/json65-string.h) (291 bytes) - This implements a [string intern pool][4] which is used by the tree interface. * [json65-tree.h](src/json65-tree.h) (1300 bytes) - The tree interface, which builds up a tree data structure as the file is parsed. You may then traverse the tree to your heart's content. * [json65-quote.h](src/json65-quote.h) (226 bytes) - This has a function which prints strings, replacing special characters with the escape sequences from the JSON specification. It is used by the tree printer, but can also be used standalone if you are printing JSON files yourself without using the tree interface. * [json65-print.h](src/json65-print.h) (710 bytes) - Prints a tree to a file as JSON. Use this if you are using the tree interface, and wish to write JSON files as well as read them. * [json65-file.h](src/json65-file.h) (1378 bytes) - Provides a helper function to feed data to the parser from a file, in chunks, and to display error messages to the user (including printing the offending line, and printing a caret to indicate the offending position of the line). I hate build systems (or at least, build systems for C code), so I have not provided one. (Other than a lame little Perl script to build and run the tests using [sim65][5].) Instead, I encourage you to copy the source files and header files you need into your own project, and use whatever build system you are already using for your project. (Such as the GNU Make based [cc65 build system][6].) You can use the following dependency graph to determine which source files you will need to copy into your project. (For each source file, you will also need to copy the corresponding header file.) Source files with no dependencies (such as `json65.s`) are at the top of the graph, while the source file with the most dependencies (`json65-print.c`) is at the bottom of the graph. ``` json65.s json65-string.s / \ / / \ / / \ / json65-file.c json65-tree.c json65-quote.s \ / \ / \ / json65-print.c ``` If you wish to build and run the tests, simply run the `run-test.pl` Perl script at the top level of the repository. (It takes no arguments.) You'll need to have the [cc65][7] toolchain installed. Note: version 2.17 and earlier of [sim65][5] have a [bug in the implementation of the BIT instruction][8], so the tests will fail. You'll need a more recent version to get the tests to pass. (This only affects the simulation of the tests. If you plan on running JSON65 on real hardware, or on an emulator *other* than sim65, then you'll be fine with an older version of cc65.) ## License JSON65 is licensed under the [zlib/libpng license](LICENSE.txt), which is approved by the [OSI][9] and the [FSF][10]. ## Background For more information about how and why I wrote JSON65, see [my blog post][11]. [1]: https://cc65.github.io/doc/ca65.html [2]: https://cc65.github.io/doc/cc65-intern.html [3]: https://tools.ietf.org/html/rfc8259#section-8.1 [4]: https://en.wikipedia.org/wiki/String_interning [5]: https://cc65.github.io/doc/sim65.html [6]: https://github.com/cc65/wiki/wiki/Bigger-Projects [7]: https://cc65.github.io/cc65/ [8]: https://github.com/cc65/cc65/pull/712 [9]: https://opensource.org/licenses/Zlib [10]: https://www.gnu.org/licenses/license-list.en.html#ZLib [11]: https://funwithsoftware.org/posts/2021-03-03-json-on-the-apple-ii.html ================================================ FILE: examples/example.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include #include #include #include "json65.h" #include "json65-file.h" #include "json65-tree.h" #include "json65-print.h" static uint8_t scratch[2048]; static j65_tree tree; static void my_exit (int code) __attribute__ ((noreturn)) { if (doesclrscrafterexit ()) { cursor (true); cgetc (); } exit (code); } int main (int argc, char **argv) { const char *filename; FILE *f; int8_t ret; j65_node *banana, *banana_value; uint8_t width, height; if (argc >= 2) { filename = argv[1]; } else { filename = "input.json"; } /* Find out the width of the screen. (This will be used ** when formatting error messages.) */ screensize (&width, &height); /* Initialize the tree data structure. */ j65_init_tree (&tree); /* Open the input file. */ f = fopen (filename, "r"); if (!f) { perror (filename); my_exit (EXIT_FAILURE); } /* Call j65_parse_file() to parse the file, and it will in turn ** call j65_tree_callback() to build up the tree. */ ret = j65_parse_file (f, /* file to parse */ scratch, /* pointer to a scratch buffer */ sizeof (scratch), /* length of scratch buffer */ &tree, /* "context" for callback */ j65_tree_callback, /* the callback function */ 0, /* 0 means use max nesting depth */ stderr, /* where to print errors */ width, /* width of screen (for errors) */ filename, /* used in error messages */ NULL); /* no custom error func */ if (ret < 0) { /* Don't need to print any error message here, because ** j65_parse_file() already printed an error message before ** returning a negative number. */ fclose (f); my_exit (EXIT_FAILURE); } /* We're done reading the file, so we can close it now. */ fclose (f); /* Look for a key named "banana". */ banana = j65_find_key (&tree, tree.root, "banana"); if (banana == NULL) { printf ("Could not find banana.\n"); j65_free_tree (&tree); my_exit (EXIT_FAILURE); } /* The variable "banana" now points to the key "banana" in ** the tree. If we want to know the value associated with ** the key "banana", we need to look at the key's child node. */ banana_value = banana->child; /* Now print the value associated with the key "banana". */ printf ("Here are some fun facts about bananas, from line %lu of %s:\n", banana_value->location.line_number + 1, filename); ret = j65_print_tree (banana_value, stdout); if (ret < 0) { perror ("error printing tree"); j65_free_tree (&tree); my_exit (EXIT_FAILURE); } /* j65_print_tree() prints everything on one line (so, not ** particularly human readable) without a newline at the ** end, so we must print the newline ourselves. */ printf ("\n"); /* We are done, so we can free the tree now. */ j65_free_tree (&tree); my_exit (EXIT_SUCCESS); } ================================================ FILE: examples/input.json ================================================ { "apple": 2, "banana": { "color": "yellow", "edible": true, "sieverts": 9.82e-8 }, "raspberry": 3.14519 } ================================================ FILE: run-tests.pl ================================================ #!/usr/bin/perl -w # JSON65 - A JSON parser for the 6502 microprocessor. # # https://github.com/ppelleti/json65 # # Copyright © 2018 Patrick Pelletier # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. use FindBin; my $root = $FindBin::Bin; chdir ($root); my $src = "src"; my $test = "tests"; my $example = "examples"; my $blue = "\e[34m"; my $green = "\e[32m"; my $red = "\e[31m"; my $off = "\e[0m"; my %test_results = (); my $total_bytes = 0; sub mysystem { my @cmd = @_; print join(" ", @cmd), "\n"; if (system (@cmd) != 0) { if ($? == -1) { die "$red*** fatal: $!$off\n"; } elsif ($? & 127) { die (sprintf ("$red*** fatal: signal %d$off\n", $? & 127)); } else { die (sprintf ("$red*** fatal: exit code %d$off\n", $? >> 8)); } } } sub mysystem_nonfatal { my @cmd = @_; print join(" ", @cmd), "\n"; if (system (@cmd) != 0) { if ($? == -1) { die "$red*** fatal: $!$off\n"; } elsif ($? & 127) { die (sprintf ("$red*** fatal: signal %d$off\n", $? & 127)); } else { return "fail"; } } return "pass"; } sub print_heading { my $str = $_[0]; print "$blue*** $str$off\n"; } sub build_program { my ($hash, @sources) = @_; my $prog = $hash->{"prog"}; my $map = $prog . ".map"; my $target = "sim6502"; $target = $hash->{"target"} if (exists $hash->{"target"}); my @cmd = ("cl65", "-I$src", "-W-unused-param", "-O"); push @cmd, '-t', $target; push @cmd, '-C', $hash->{"config"} if (exists $hash->{"config"}); push @cmd, '-o', $prog; push @cmd, '-m', $map; push @cmd, @sources; mysystem (@cmd); } sub run_test { my $t = $_[0]; print_heading "Running $t"; $test_results{$t} = mysystem_nonfatal ("sim65", $t); } sub parse_map { my $map = $_[0]; my $name = ""; my $size = 0; my %result = (); open F, $map or die; while () { chomp; if (/^([^:\s]+):$/) { $name = $1; $size = 0; } elsif (/^\s+[A-Z].* Size\=(\w+)/) { my $sz = hex ($1); $size += $sz; $result{$name} = $size; } elsif (/^$/) { last; } } close F; return \%result; } sub print_size { my ($sizes, $obj) = @_; my $size = $sizes->{$obj}; printf "%-15s %4u bytes\n", $obj, $size; $total_bytes += $size; } print_heading "Building tests"; # Tests which can be built for sim65 build_program({'prog' => "$test/test"}, "$src/json65.s", "$test/test.c"); build_program({'prog' => "$test/test-string"}, "$src/json65-string.s", "$test/test-string.c"); build_program({'prog' => "$test/test-tree"}, "$src/json65.s", "$src/json65-string.s", "$src/json65-tree.c", "$test/test-tree.c"); build_program({'prog' => "$test/test-quote"}, "$src/json65-quote.s", "$test/test-quote.c"); build_program({'prog' => "$test/test-print"}, "$src/json65.s", "$src/json65-string.s", "$src/json65-tree.c", "$src/json65-quote.s", "$src/json65-print.c", "$test/test-print.c"); # test-file uses library functions (ftell and fseek) which are not available # on sim65, so we build it for Apple II instead. This means that we cannot # test it automatically, though. (But it's still worth building, to make # sure it builds.) build_program({'prog' => "$test/testfile.system", 'target' => 'apple2', 'config' => 'apple2-system.cfg'}, "$src/json65.s", "$src/json65-file.c", "$test/test-file.c"); build_program({'prog' => "$example/example.system", 'target' => 'apple2', 'config' => 'apple2-system.cfg'}, "$src/json65.s", "$src/json65-file.c", "$src/json65-string.s", "$src/json65-tree.c", "$src/json65-quote.s", "$src/json65-print.c", "$example/example.c"); chdir ($test); # Run tests on sim65 run_test ("test"); run_test ("test-string"); run_test ("test-tree"); run_test ("test-print"); # test-quote is not self-checking, and its functionality is subsumed # by test-print, so there's no need to run it #run_test ("test-quote"); print_heading "Size summary"; my $print_map = parse_map ("test-print.map"); my $file_map = parse_map ("testfile.system.map"); print_size ($print_map, "json65.o"); print_size ($print_map, "json65-string.o"); print_size ($print_map, "json65-tree.o"); print_size ($print_map, "json65-quote.o"); print_size ($print_map, "json65-print.o"); print_size ($file_map, "json65-file.o"); printf "%-15s %4u bytes\n", "total", $total_bytes; my $failures = 0; print_heading "Test summary"; foreach my $t (sort keys %test_results) { printf "%-11s ", $t; if ($test_results{$t} eq "pass") { print $green, "PASS", $off, "\n"; } else { print $red, "FAIL", $off, "\n"; $failures++; } } exit $failures; ================================================ FILE: src/json65-file.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include "json65-file.h" static const char insufficient_memory[] = "insufficient memory"; static const char *get_errmsg (int8_t n) { switch (n) { case J65_PARSE_ERROR: return "parse error"; case J65_ILLEGAL_CHAR: return "illegal character"; case J65_ILLEGAL_ESCAPE: return "illegal escape sequence"; case J65_STRING_TOO_LONG: return "string longer than 255 bytes"; case J65_EXPECTED_STRING: return "keys must be strings"; case J65_EXPECTED_COLON: return "expected ':'"; case J65_EXPECTED_COMMA: return "expected ','"; case J65_EXPECTED_OBJ_END: return "expected '}'"; case J65_EXPECTED_ARRAY_END: return "expected ']'"; default: return NULL; } } int8_t __fastcall__ j65_parse_file (FILE *f, void *scratch, size_t scratch_len, void *ctx, j65_callback cb, uint8_t max_depth, FILE *err, uint8_t width, const char *filename, j65_err_func user_err_func) { j65_parser *p; char *buf; size_t buflen; long origin; int8_t ret; size_t size; uint32_t line_num, column_num; const char *errmsg; uint32_t offset; if (scratch_len < sizeof (j65_parser) + width + 1) { fprintf (err, "%s %u\n", insufficient_memory, scratch_len); return J65_INSUFFICIENT_MEMORY; } p = (j65_parser *) scratch; buf = ((char *) scratch) + sizeof (j65_parser); buflen = scratch_len - sizeof (j65_parser); j65_init (p, ctx, cb, max_depth); /* I am having a weird problem with ftell returning 512 when ** I am at the beginning of the file. Until I can figure that ** out, just assume that f is at the beginning of the file. ** ** The bug report is here: ** https://github.com/cc65/cc65/issues/722 */ #if 1 origin = 0; #else origin = ftell (f); if (origin < 0) goto io_error; #endif do { size = fread (buf, 1, buflen, f); if (ferror (f)) goto io_error; ret = j65_parse (p, buf, size); } while (ret == J65_WANT_MORE && !feof (f)); if (ret == J65_WANT_MORE) { fprintf (err, "%s: Unexpected end of file\n", filename); return J65_UNEXPECTED_END_OF_FILE; } if (ret == J65_DONE) { return ret; } line_num = j65_get_line_number (p); column_num = j65_get_column_number (p); fprintf (err, "%s:%lu:%lu: ", filename, line_num + 1, column_num + 1); errmsg = get_errmsg (ret); if (errmsg != NULL) { fputs (errmsg, err); } else if (ret == J65_NESTING_TOO_DEEP) { fprintf (err, "exceeded max nesting depth of %u levels", j65_get_max_depth (p)); } else { if (user_err_func == NULL) user_err_func = j65_default_err_func; user_err_func (err, ctx, ret); } fputc ('\n', err); width--; offset = j65_get_line_offset (p); if (column_num >= width) { offset += (column_num - (width - 1)); column_num = width - 1; } if (fseek (f, origin + offset, SEEK_SET) < 0) goto io_error; size = fread (buf, 1, width, f); buf[size] = 0; size = strcspn (buf, "\r\n"); buf[size] = 0; fprintf (err, "%s\n", buf); memset (buf, ' ', column_num); buf[column_num] = '^'; buf[column_num + 1] = 0; fprintf (err, "%s\n", buf); return ret; io_error: if (_oserror) errmsg = _stroserror (_oserror); else errmsg = strerror (errno); fprintf (err, "%s: %s\n", filename, errmsg); return J65_IO_ERROR; } void __fastcall__ j65_default_err_func (FILE *err, void *ctx, int8_t status) { if (status == J65_INSUFFICIENT_MEMORY) { fputs (insufficient_memory, err); } else { fprintf (err, "Unknown error code %d", status); } } ================================================ FILE: src/json65-file.h ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef J65_FILE_H #define J65_FILE_H #include #include #include "json65.h" /* These are additional error codes that can be returned, besides the ones in j65_status. */ enum { J65_INSUFFICIENT_MEMORY = -1, /* scratch_len was too small */ J65_IO_ERROR = -2, /* file I/O returned an error */ J65_UNEXPECTED_END_OF_FILE = -3, /* incomplete JSON value */ }; /* This optional callback function should translate your custom error code (in the status argument) to a human-readable string, and print it on the err filehandle. The ctx argument is supplied in case you want your error message to contain additional information from the context. If you do not recognize the error code, you can pass it along to j65_default_err_func(). */ typedef void __fastcall__ (*j65_err_func) (FILE *err, void *ctx, int8_t status); /* Parses JSON from a file. If an error occurs, prints a nice human-readable error message, including the line number and column number, and prints the offending line with a caret pointing to the offending position. Does not do any dynamic memory allocation. However, it requires a chunk of scratch memory. The scratch memory must be at least 513 + width bytes, and I would recommend at least 768 bytes, though 1K or 2K might be preferable, from a performance standpoint. (But, experiment and see what works best on your operating system.) The scratch buffer does not need to be big enough to hold the whole file, though. Returns J65_DONE if the file is parsed successfully. If an error occurs, returns a negative number which will either be one of the error codes from j65_status in json65.h, or one of the error codes in the enumation at the top of this file. In the case of an error, a human-readable message will have been printed to the err filehandle (so you may not need to care which negative value was returned, just that it was negative). Never returns J65_WANT_MORE. (Feeding chunks of the file is handled automatically, and if J65_WANT_MORE occurs at the end of the file, that is translated into J65_UNEXPECTED_END_OF_FILE.) Here are the arguments: f - The file to parse. scratch - Pointer to scratch memory. scratch_len - Size of scratch memory, in bytes. If it is not at least 513 + width bytes, the function returns J65_INSUFFICIENT_MEMORY. ctx - Context argument which will be passed to callback. (To pass a pointer to any data structure you desire.) cb - The callback which is called for each event in the JSON file. For more information, see documentation in json65.h. max_depth - The maximum nesting depth (of nested arrays and objects) allowed. If you do not care, set it to 0. See documentation for j65_init() in json.h for additional information. err - The file handle (such as stdout, stderr, or an actual file) to print the error message to, if an error occurs. width - The width, in characters, of the display device for the err filehandle. If the err filehandle represents the screen, you can get the width with the screensize() function from the cc65 standard header file conio.h. If err is a real file, then just choose something reasonable, like 80. filename - The name of the file represented by the f filehandle. This name is only used in generating an error message; we do not attempt to access the file by name. user_err_func - If you return custom error codes from your callback function, you can supply a user_err_func which translates your custom error codes to a human-readable string. Passing NULL is the same as passing j65_default_err_func. */ int8_t __fastcall__ j65_parse_file (FILE *f, void *scratch, size_t scratch_len, void *ctx, j65_callback cb, uint8_t max_depth, FILE *err, uint8_t width, const char *filename, j65_err_func user_err_func); /* A default implementation of j65_err_func, which prints unknown error codes numerically. */ void __fastcall__ j65_default_err_func (FILE *err, void *ctx, int8_t status); #endif /* J65_FILE_H */ ================================================ FILE: src/json65-print.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include "json65-print.h" #include "json65-quote.h" enum { ASCENDING, DESCENDING, }; /* This is a non-recursive implementation because the tree might be deep * and the 6502 stack is not very deep. */ int __fastcall__ j65_print_tree (j65_node *root, FILE *f) { j65_node *n = root; uint8_t direction = DESCENDING; bool done = false; char c1, c2; j65_node *next; uint8_t next_direction; bool print_comma; uint8_t node_type; if (root == NULL) return 0; do { node_type = n->node_type; /* the default (for scalars) is to visit the next sibling */ if (n->next) { next = n->next; next_direction = DESCENDING; print_comma = true; } else { next = n->parent; next_direction = ASCENDING; print_comma = false; } switch (node_type) { case J65_NULL: fputs ("null", f); break; case J65_FALSE: fputs ("false", f); break; case J65_TRUE: fputs ("true", f); break; case J65_INTEGER: fprintf (f, "%ld", n->integer); break; case J65_NUMBER: fputs (n->string, f); break; case J65_STRING: fputc ('\"', f); j65_print_escaped (n->string, f); fputc ('\"', f); break; case J65_KEY: if (direction == DESCENDING) { fputc ('\"', f); j65_print_escaped (n->string, f); fputs ("\":", f); next = n->child; next_direction = DESCENDING; print_comma = false; } break; case J65_START_OBJ: case J65_START_ARRAY: if (node_type == J65_START_OBJ) { c1 = '{'; c2 = '}'; } else { c1 = '['; c2 = ']'; } if (direction == DESCENDING) { fputc (c1, f); next = n->child; if (next) { next_direction = DESCENDING; } else { next = n; next_direction = ASCENDING; } print_comma = false; } else { fputc (c2, f); } break; default: /* should never happen... */ fprintf (f, "?%u(%p)", node_type, n); break; } if (n == root && (direction == ASCENDING || (node_type != J65_START_OBJ && node_type != J65_START_ARRAY))) { done = true; } else if (print_comma) { fputc (',', f); } n = next; direction = next_direction; } while (!done && !ferror(f)); if (ferror (f)) return -1; else return 0; } ================================================ FILE: src/json65-print.h ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef J65_PRINT_H #define J65_PRINT_H #include #include "json65-tree.h" /* Prints the specified tree to the specified file handle as JSON. Returns 0 on success. If an error occurs on the file handle, returns -1. In that case, look at errno and/or _oserror to see what the error was. */ int __fastcall__ j65_print_tree (j65_node *n, FILE *f); #endif /* J65_PRINT_H */ ================================================ FILE: src/json65-quote.h ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef J65_QUOTE_H #define J65_QUOTE_H #include /* Prints the specified string to the specified file handle. Special characters are escaped using the escape sequences from the JSON specification. Does not return a value, so to check for an error, you should call ferror(f). */ void __fastcall__ j65_print_escaped (const char *str, FILE *f); #endif /* J65_QUOTE_H */ ================================================ FILE: src/json65-quote.s ================================================ ;; JSON65 - A JSON parser for the 6502 microprocessor. ;; ;; https://github.com/ppelleti/json65 ;; ;; Copyright © 2018 Patrick Pelletier ;; ;; This software is provided 'as-is', without any express or implied ;; warranty. In no event will the authors be held liable for any damages ;; arising from the use of this software. ;; ;; Permission is granted to anyone to use this software for any purpose, ;; including commercial applications, and to alter it and redistribute it ;; freely, subject to the following restrictions: ;; ;; 1. The origin of this software must not be misrepresented; you must not ;; claim that you wrote the original software. If you use this software ;; in a product, an acknowledgment in the product documentation would be ;; appreciated but is not required. ;; 2. Altered source versions must be plainly marked as such, and must not be ;; misrepresented as being the original software. ;; 3. This notice may not be removed or altered from any source distribution. .macpack generic .include "zeropage.inc" .import _fprintf .import _fputc .import _fwrite .import popax .import pushax .export _j65_print_escaped fileptr = regbank strptr = regbank + 2 startidx = regbank + 4 saveidx = regbank + 5 t1 = tmp1 len = tmp2 character = tmp3 ;; pushes regbank (caller-saved registers) onto 6502 stack .macro save_regbank .repeat 6, i lda regbank+i pha .endrep .endmacro ; save_regbank ;; pops regbank (caller-saved registers) off of 6502 stack .macro restore_regbank .repeat 6, i pla sta regbank+5-i .endrep .endmacro ; restore_regbank ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_print_escaped ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; void __fastcall__ j65_print_escaped (const char *str, FILE *f) .proc _j65_print_escaped sta t1 save_regbank lda t1 sta fileptr stx fileptr+1 jsr popax sta strptr stx strptr+1 ldy #0 loop1: sty startidx loop2: lda (strptr),y cmp #'#' ; check whether character is special bge higher and #$fe cmp #' ' bne special_char okay_char: ; character does not need to be escaped iny jmp loop2 higher: cmp #$5c ; backslash bne okay_char special_char: ; character needs to be escaped sty saveidx tya sub startidx beq skip_fwrite ; skip fwrite if "count" would be 0 sta len ldx strptr+1 ; add strptr to startidx to make "buf" argument lda strptr add startidx bcc skip_inx inx skip_inx: jsr pushax ; push argument "buf" lda #1 ldx #0 jsr pushax ; push argument "size" lda len ldx #0 jsr pushax ; push argument "count" lda fileptr ; argument "f" passed in ax ldx fileptr+1 jsr _fwrite skip_fwrite: ldy saveidx lda (strptr),y beq done ; terminating NUL character pha ; save character to be escaped lda #$5c ; print backslash ldx #0 jsr pushax ; push argument "c" lda fileptr ; argument "f" passed in ax ldx fileptr+1 jsr _fputc pla ; restore character to be escaped sta character ldx #6 ; see if there is a short escape for character esc_loop: lda escaped_chars,x cmp character beq found_short_escape dex bpl esc_loop lda fileptr ; there is not a short escape, use \uxxxx ldx fileptr+1 jsr pushax ; push argument "f" lda #fmt_str jsr pushax ; push argument "format" lda character ldx #0 jsr pushax ; push varargs argument (character as int) ldy #6 ; bytes of arguments on the stack jsr _fprintf continue: ldy saveidx iny jmp loop1 found_short_escape: lda escape_codes,x ; print escape code character ldx #0 jsr pushax ; push argument "c" lda fileptr ; argument "f" passed in ax ldx fileptr+1 jsr _fputc jmp continue done: restore_regbank rts .rodata escape_codes: .byte $22, $5c, "bfnrt" escaped_chars: .byte $22, $5c, $08, $0c, $0a, $0d, $09 fmt_str: .asciiz "u%04x" .endproc ; _j65_print_escaped ================================================ FILE: src/json65-string.h ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef J65_STRING_H #define J65_STRING_H #include /* j65_strings is a "string intern pool" used for "interning" string values: https://en.wikipedia.org/wiki/String_interning This saves space by only keeping a single copy of each string, and it also means interned strings can be compared by pointer value, without having to compare character-by-character. j65_strings is too large to fit on the stack, so it should be allocated statically or on the heap. To initialize the intern pool, call j65_init_strings(). Since j65_intern_string() will allocate memory with malloc() for each unique interned string, you must call j65_free_strings() to free that memory when you are done with the intern pool. Internally, j65_strings is implemented as a fixed-size, 256-entry hash table, where each hash bucket is a linked list to handle collisions. j65_strings has an implementation limit: It only supports strings of 255 bytes or less. If a string is longer than 255 bytes, the interned string is truncated at 255 bytes. This should not be an issue when interning strings returned by the JSON65 parser, since the JSON65 parser also has a limit of 255 bytes for strings. */ typedef struct { uint8_t opaque[512]; } j65_strings; /* Initialize a string intern pool for use. Once you have initialized it, you may call j65_intern_string() on it. */ void __fastcall__ j65_init_strings (j65_strings *strs); /* Intern a string in the specified pool. The returned pointer will strcmp() equal to the str argument, as long as the str argument is 255 bytes or less in length. (If str is longer, then the interned string will be truncated to 255 bytes.) The returned pointer will be valid until j65_free_strings() is called on the pool. If the specified string does not already exist in the pool, and malloc() returns NULL when allocating memory for the new string, then j65_intern_string() will return NULL. */ const char * __fastcall__ j65_intern_string (j65_strings *strs, const char *str); /* Frees all memory used by the given string pool. Once j65_free_strings() is called, all of the pointers returned by j65_intern_string() for this pool become invalid. */ void __fastcall__ j65_free_strings (j65_strings *strs); #endif /* J65_STRING_H */ ================================================ FILE: src/json65-string.s ================================================ ;; JSON65 - A JSON parser for the 6502 microprocessor. ;; ;; https://github.com/ppelleti/json65 ;; ;; Copyright © 2018 Patrick Pelletier ;; ;; This software is provided 'as-is', without any express or implied ;; warranty. In no event will the authors be held liable for any damages ;; arising from the use of this software. ;; ;; Permission is granted to anyone to use this software for any purpose, ;; including commercial applications, and to alter it and redistribute it ;; freely, subject to the following restrictions: ;; ;; 1. The origin of this software must not be misrepresented; you must not ;; claim that you wrote the original software. If you use this software ;; in a product, an acknowledgment in the product documentation would be ;; appreciated but is not required. ;; 2. Altered source versions must be plainly marked as such, and must not be ;; misrepresented as being the original software. ;; 3. This notice may not be removed or altered from any source distribution. .macpack generic .include "zeropage.inc" .import _free .import _malloc .import popax .export _j65_init_strings .export _j65_intern_string .export _j65_free_strings ;; take advantage of the fact that malloc and free don't ;; modify regsave or sreg loptr = regsave hiptr = regsave + 2 linkptr = sreg ;; they don't modify tmp1 through tmp4, either t1 = tmp1 t2 = tmp2 hash_val = tmp3 len = tmp4 idx = tmp4 ;; ptr4 is also preserved across malloc (but not free) strptr = ptr4 ;; the following doesn't need to be preserved across ;; a malloc or free tmpptr = ptr3 ;; bucket format: ;; lo byte of ptr to next bucket ;; hi byte of ptr to next bucket ;; length of string ;; 0-255 bytes of string ;; NUL byte ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_init_strings ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; void __fastcall__ j65_init_strings (j65_strings *strs); .proc _j65_init_strings sta ptr1 stx ptr1+1 ldy #0 tya jsr loop inc ptr1+1 loop: sta (ptr1),y iny bne loop rts .endproc ; _j65_init_strings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_intern_string ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; const char *j65_intern_string (j65_strings *strs, const char *str); .proc _j65_intern_string sta strptr stx strptr+1 jsr hash_str sta hash_val sty len jsr popax ; get pointer to j65_strings structure sta loptr stx loptr+1 sta hiptr inx stx hiptr+1 ldy hash_val ; lookup hash bucket lda (loptr),y sta linkptr lda (hiptr),y linkloop: ; traverse linked list sta linkptr+1 ora linkptr beq not_found ldy #2 lda (linkptr),y cmp len bne nextlink lda linkptr ; compare string add #3 sta tmpptr lda linkptr+1 adc #0 sta tmpptr+1 ldy #0 strloop: lda (strptr),y cmp (tmpptr),y bne nextlink iny cpy len bne strloop lda tmpptr ; return pointer to string from table ldx tmpptr+1 fail: rts nextlink: ldy #0 lda (linkptr),y tax iny lda (linkptr),y stx linkptr jmp linkloop not_found: ; so we need to add it to the hash table ldx #0 lda len add #4 bcc skip inx skip: jsr _malloc sta tmpptr stx tmpptr+1 ora tmpptr+1 beq fail ; if malloc returned null, we return null ldy hash_val lda (hiptr),y ; add new memory to linked list tax lda (loptr),y ldy #0 sta (tmpptr),y iny txa sta (tmpptr),y ldy hash_val lda tmpptr sta (loptr),y lda tmpptr+1 sta (hiptr),y ldy #2 ; store length lda len sta (tmpptr),y lda tmpptr ; copy string add #3 sta tmpptr bcc skip2 inc tmpptr+1 skip2: ldy #0 copyloop: cpy len beq copydone lda (strptr),y sta (tmpptr),y iny jmp copyloop copydone: lda #0 sta (tmpptr),y lda tmpptr ; return pointer to new string ldx tmpptr+1 rts .endproc ; _j65_intern_string ;; rotate accumulator left by 1. .macro rotate_left cmp #$80 rol .endmacro ; rotate_left ;; hash the (NUL terminated) string pointed at by strptr. ;; return hash in a and length in y. .proc hash_str lda #0 tay loop: sta t1 lda (strptr),y beq done iny beq done0 add t1 rotate_left rotate_left rotate_left jmp loop done0: dey done: lda t1 rts .endproc ; hash_str ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_free_strings ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; void __fastcall__ j65_free_strings (j65_strings *strs); .proc _j65_free_strings sta loptr stx loptr+1 sta hiptr inx stx hiptr+1 ldy #0 loop: sty idx lda (hiptr),y tax lda (loptr),y jsr freelink ldy idx lda #0 sta (loptr),y sta (hiptr),y iny bne loop rts .endproc ; _j65_free_strings ;; free the chain of buckets pointed to by ax .proc freelink sta linkptr stx linkptr+1 ora linkptr+1 beq done ldy #0 lda (linkptr),y sta t1 iny lda (linkptr),y sta t2 lda linkptr ldx linkptr+1 jsr _free lda t1 ldx t2 jmp freelink done: rts .endproc ; freelink ================================================ FILE: src/json65-tree.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include /* malloc and free */ #include "json65-tree.h" typedef struct { j65_strings strings; j65_node *root; j65_node *current; bool add_child; } j65_tree_internal; void __fastcall__ j65_init_tree (j65_tree *t) { j65_tree_internal *tree = (j65_tree_internal *) t; j65_init_strings (&tree->strings); tree->root = NULL; tree->current = NULL; tree->add_child = true; } int8_t __fastcall__ j65_tree_callback (j65_parser *p, uint8_t event) { j65_tree_internal *tree = (j65_tree_internal *) j65_get_context (p); const char *str = NULL; j65_node *n; switch (event) { case J65_END_OBJ: case J65_END_ARRAY: if (tree->add_child) { tree->add_child = false; } else { tree->current = tree->current->parent; } if (tree->current->parent && tree->current->parent->node_type == J65_KEY) { tree->current = tree->current->parent; } return 0; case J65_NUMBER: case J65_STRING: case J65_KEY: str = j65_intern_string (&tree->strings, j65_get_string (p)); if (str == NULL) return J65_OUT_OF_MEMORY; } n = (j65_node *) malloc (sizeof (j65_node)); if (n == NULL) return J65_OUT_OF_MEMORY; n->node_type = event; n->location.line_offset = j65_get_line_offset (p); n->location.line_number = j65_get_line_number (p); n->location.column_number = j65_get_column_number (p); if (tree->add_child) n->parent = tree->current; else n->parent = tree->current->parent; n->next = NULL; n->integer = 0; /* clears all fields of union to NULL */ switch (event) { case J65_INTEGER: n->integer = j65_get_integer (p); break; case J65_KEY: case J65_NUMBER: case J65_STRING: n->string = str; break; } if (tree->current == NULL) { tree->root = n; tree->current = n; } else if (tree->add_child) { tree->current->child = n; if (tree->current->node_type == J65_KEY) { if (event == J65_START_OBJ || event == J65_START_ARRAY) tree->current = n; } else { tree->current = n; } } else { tree->current->next = n; tree->current = n; } switch (event) { case J65_KEY: case J65_START_OBJ: case J65_START_ARRAY: tree->add_child = true; break; default: tree->add_child = false; break; } return 0; } j65_node * __fastcall__ j65_find_key (j65_tree *t, j65_node *object, const char *key) { const char *k = j65_intern_string (&t->strings, key); if (k == NULL) return NULL; return j65_find_interned_key (object, k); } j65_node * __fastcall__ j65_find_interned_key (j65_node *object, const char *key) { j65_node *n; if (object->node_type == J65_START_OBJ) n = object->child; else if (object->node_type == J65_KEY) n = object; else return NULL; while (n != NULL) { if (n->string == key) return n; n = n->next; } return NULL; } void __fastcall__ j65_free_tree (j65_tree *t) { j65_tree_internal *tree = (j65_tree_internal *) t; j65_node *n = tree->root; j65_node *follow; /* traverse the entire tree without recursion */ /* (since 6502 stack is limited) */ while (n != NULL) { switch (n->node_type) { case J65_KEY: case J65_START_OBJ: case J65_START_ARRAY: follow = n->child; n->child = NULL; if (follow != NULL) break; /* fall thru */ default: /* node has no children (either a leaf or empty container) */ follow = n->next; if (follow == NULL) /* no remaining siblings, either */ follow = n->parent; free (n); } n = follow; } tree->root = NULL; tree->current = NULL; tree->add_child = true; j65_free_strings (&tree->strings); } ================================================ FILE: src/json65-tree.h ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef J65_TREE_H #define J65_TREE_H #include "json65.h" #include "json65-string.h" /* in addition to the status codes from j65_status */ enum { J65_OUT_OF_MEMORY = -1, /* malloc returned NULL */ }; /* Specifies the location in the input JSON file that corresponds to a particular node. line_number and column_number are 0-based, so you should probably add 1 before displaying them to the user. line_offset is the byte position in the file where the line specified by line_number begins. This is useful if you want to display the line in question, because you can seek directly to the beginning of the line in the file. For arrays and objects, the position specified is that of the open square bracket or open curly brace. For scalars, the position specified is at the end of the scalar. */ typedef struct { uint32_t line_offset; uint32_t line_number; uint32_t column_number; } j65_source_location; typedef struct j65_node j65_node; /* j65_node represents a value parsed from the JSON file. The node_type is recycled from the j65_event enumeration in json65.h. All event types are legal node types, with the exception of J65_END_OBJ and J65_END_ARRAY. Since the tree structure treats objects and arrays as containers rather than events, they do not have a separate start and end. Rather, J65_START_OBJ and J65_START_ARRAY are used to specify object nodes and array nodes, respectively. There are three types of container nodes: J65_START_ARRAY, J65_START_OBJ, and J65_KEY. Array nodes may contain any number of children, of any type except J65_KEY. Object nodes may contain any number of children, but they may only be of type J65_KEY. Key nodes must have exactly one child, which may be of any type except J65_KEY. Every node except the root node has a parent, which is the J65_START_ARRAY, J65_START_OBJ, or J65_KEY node which contains it. For the root node, the parent pointer is NULL. Container nodes point to their first child. Each child node points to its next sibling via the next pointer. The final sibling has a NULL next pointer. */ struct j65_node { uint8_t node_type; j65_source_location location; j65_node *parent; j65_node *next; union { int32_t integer; /* J65_INTEGER */ struct { const char *string; /* J65_KEY, J65_NUMBER, or J65_STRING */ j65_node *child; /* J65_KEY, J65_START_OBJ, or J65_START_ARRAY */ }; }; }; /* This structure represents an entire tree of nodes read from a JSON file. The j65_tree should be initialized with j65_init_tree(), and then it can be passed as the context argument to j65_parse(), with j65_tree_callback() as the callback argument. Once parsing has completed, the tree will be populated. The root pointer points to the root (top-level) node. All of the strings contained in the tree (in J65_STRING, J65_NUMBER, and J65_KEY nodes) will be interned in the strings member. Besides the string pool and the root pointer, j65_tree also contains a small amount of bookkeeping information used by the callback, which is unused once parsing is complete. */ typedef struct { j65_strings strings; j65_node *root; uint8_t internal[3]; } j65_tree; /* Initializes the j65_tree structure for use. */ void __fastcall__ j65_init_tree (j65_tree *t); /* This should be specified as the callback to j65_parse(), and the j65_tree structure should be specified as the context. This callback builds up the tree as the parser generates events. */ int8_t __fastcall__ j65_tree_callback (j65_parser *p, uint8_t event); /* Interns the given string into the tree's intern pool, and then searches for a J65_KEY child node of the given J65_START_OBJ node whose key matches the given string. The matching J65_KEY node is returned, or NULL if there is no match. */ j65_node * __fastcall__ j65_find_key (j65_tree *t, j65_node *object, const char *key); /* Searches for a key node which matches the given string. The string is expected to have already been interned in the tree's string intern pool. The node passed in should be of type J65_START_OBJ, and its J65_KEY children are searched. The matching J65_KEY node is returned, or NULL if there is no match. */ j65_node * __fastcall__ j65_find_interned_key (j65_node *object, const char *key); /* Frees all the memory used by this tree. The tree is traversed, and all of the nodes are freed. Additionally, j65_free_strings() is called on the string intern pool contained within the j65_tree structure. */ void __fastcall__ j65_free_tree (j65_tree *t); #endif /* J65_TREE_H */ ================================================ FILE: src/json65.h ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifndef J65_H #define J65_H #include #include /* for size_t */ /* An event which is passed to the callback function, indicating what has happened in the parser. Most events have no associated data, but a few have a string and/or an integer, as noted below, which may be retrieved with j65_get_string() or j65_get_integer(). */ enum j65_event { J65_NULL = 0, J65_FALSE = 1, J65_TRUE = 2, J65_INTEGER = 3, /* integer and string */ J65_NUMBER = 4, /* string */ J65_STRING = 5, /* string */ J65_KEY = 6, /* string */ J65_START_OBJ = 7, J65_END_OBJ = 8, J65_START_ARRAY = 9, J65_END_ARRAY = 10, }; /* A status value returned by j65_parse(). J65_DONE indicates that a complete JSON value has been parsed successfully. J65_WANT_MORE indicates that j65_parse() should be called again with more input. (If the end of the file has been reached, this can be considered an "unexpected end of file" error.) Negative values indicate errors. The error may be one of the predefined errors below, or it may be an error returned by the callback. User-defined error numbers should be between J65_USER_ERROR and -1, inclusive. If you want to define your own error codes, I recommend doing it something like this: enum { MYERR_MISSING_REQUIRED_KEY = J65_USER_ERROR, MYERR_UNSUPPORTED_KEY, MYERR_VALUE_OUT_OF_RANGE, MYERR_SOME_OTHER_ERROR, }; This way, your error codes will begin at J65_USER_ERROR, and grow upwards (towards 0). */ enum j65_status { J65_DONE = 1, J65_WANT_MORE = 2, /* errors */ J65_PARSE_ERROR = -128, J65_ILLEGAL_CHAR, J65_ILLEGAL_ESCAPE, J65_NESTING_TOO_DEEP, J65_STRING_TOO_LONG, J65_EXPECTED_STRING, J65_EXPECTED_COLON, J65_EXPECTED_COMMA, J65_EXPECTED_OBJ_END, J65_EXPECTED_ARRAY_END, J65_USER_ERROR, /* must be last. not generated by parser. */ }; /* The state for a JSON parser. Initialize it by calling j65_init(). It is too big to be allocated on the cc65 stack, so you should either allocate it statically, or on the heap. */ typedef struct { uint8_t dummy[512]; } j65_parser; /* The type of the callback function passed to j65_init. This is called from within j65_parse() whenever a parsing "event" occurs. The event type (the j65_event enumeration, but passed as a uint8_t) indicates which event occurred. Depending on the event type, additional information may be available by calling one of the accessor functions on the j65_parser. (See functions below.) If the event was processed successfully, the callback should return zero. If the callback wishes to terminate the parsing early, and cause j65_parse() to return immediately, the callback should return a negative value. (Specifically, it should return an integer between J65_USER_ERROR and -1, inclusive.) */ typedef int8_t __fastcall__ (*j65_callback)(j65_parser *p, uint8_t event); /* Initializes a j65_parser structure. The arguments passed will be stored in the j65_parser structure. Specifically: ctx is a pointer with no predefined meaning. It may be retrieved by calling j65_get_context() on the parser. cb is the callback which should be called by j65_parse() when an event occurs. max_depth is the maximum depth of nested objects and arrays allowed when parsing the JSON. max_depth will automatically be capped at 224. It may sometimes be helpful to limit max_depth to a smaller value. (For example, if you are going to build up a tree and then walk it recursively, the 6502 stack cannot hold 224 return addresses, so you could limit max_depth to a value somewhat less than 128, to prevent overflowing the stack.) To obtain the value actually used for max_depth, call j65_get_max_depth() on the parser. If max_depth is 0, then it will be set to the maximum allowable value, which is 224. So, if you do not wish to further limit the maximum depth, pass 0 for max_depth. This means that the smallest value you can actually set max_depth to is 1, which means that you can only have a top-level array or object, but no arrays or objects inside of it. (Hypothetically, a max_depth of 0 would mean only top-level scalars are accepted, and no arrays or objects would be allowed at all. But we do not let you set max_depth to 0.) Once the j65_parser has been initialized, you may call j65_parse() on it. */ void __fastcall__ j65_init (j65_parser *p, void *ctx, j65_callback cb, uint8_t max_depth); /* Parses the JSON in buf, of length len. j65_parse() may be called multiple times (as long as it returns J65_WANT_MORE) to parse input incrementally. j65_parse() calls the callback (supplied to j65_init()) as events occur. The return value is the j65_status enumeration, returned as an int8_t. J65_DONE indicates the parsing completed successfully. J65_WANT_MORE indicates that j65_parse() should be called again with more input. Any negative value indicates an error, either one generated by the parser, or one returned by the callback. */ int8_t __fastcall__ j65_parse (j65_parser *p, const char *buf, size_t len); /* Returns the string associated with the current event. This call is only valid inside the callback function, and only when the event is one of J65_INTEGER, J65_NUMBER, J65_STRING, or J65_KEY. The string returned is only valid until the callback returns. The string is NUL-terminated, so it is not necessary to call j65_get_length(), unless you wish to support strings with embedded NUL characters. In the case of a J65_NUMBER OR J65_KEY event, backslash escape sequences in the string have already been substituted. The string is UTF-8 encoded. In the case of a J65_NUMBER event, beware that although the number has been validated to contain only characters that are legal in a number, the number has not been fully validated to conform to the format for a legal number (specified in section 6 of RFC 8259). For example, the string might be "10.10.10-e++", which is not a valid number. So, you will need to more fully validate the number when parsing it, and return a user error from the callback if necessary. */ const char * __fastcall__ j65_get_string (const j65_parser *p); /* In the cases where j65_get_string() is valid, j65_get_length() is valid, too. It returns the length of the string returned by j65_get_string(). This is necessary if you wish to support strings with embedded NUL characters, and it may also be useful in other situations, such as if you want to avoid the performance hit of calling strlen() on the string returned by j65_get_string(). */ uint8_t __fastcall__ j65_get_length (const j65_parser *p); /* Returns the integer associated with the current event. This call is only valid inside the callback function, and only when the event is J65_INTEGER. Note that in a J65_INTEGER event, both j65_get_integer() and j65_get_string() are valid, so you may process the integer either as an integer or a string. If a number cannot be represented as an int32_t, either because it is non-integral, or because it is too large, then a J65_NUMBER event is generated instead of J65_INTEGER. */ int32_t __fastcall__ j65_get_integer (const j65_parser *p); /* Returns the offset within the file of the beginning of the current line. (In other words, the total number of bytes processed by this j65_parser, prior to the first byte of the current line.) j65_get_line_offset() may be called either within the callback function (for any event type), or after j65_parse() returns. This can be useful if you want to print the current line when an error occurs. */ uint32_t __fastcall__ j65_get_line_offset (const j65_parser *p); /* Returns the line number of the current line. j65_get_line_number() may be called either within the callback function (for any event type), or after j65_parse() returns. The line number is 0-based, so you will probably want to add 1 before displaying it to the user. Lines may be terminated either by a linefeed (UNIX standard) or a carriage return (Apple II standard). However, a linefeed is not counted if it is immediately preceded by a carriage return. (Thus allowing MS-DOS/Windows standard line endings to be supported as well.) */ uint32_t __fastcall__ j65_get_line_number (const j65_parser *p); /* Like j65_get_line_number(), but returns the current column number within the current line. Like j65_get_line_number(), the column number is 0-based, and may be obtained at any time, either during or after parsing. When inside the callback, the column number usually indicates the last byte of the value which generated the current event, or the byte immediately after it. When j65_parse() returns with an error, the column may either point to the byte where the error occurred, or the last byte of the value which generated the error, or the byte immediately following it. The column number simply counts bytes, so the number may be unintuitive if your input string contains tabs or multibyte UTF-8 characters. */ uint32_t __fastcall__ j65_get_column_number (const j65_parser *p); /* Returns the current depth of nested arrays and objects. It can be between 0 and max_depth (which is never more than 224), inclusive. Depth 0 only occurs for top-level scalars. */ uint8_t __fastcall__ j65_get_current_depth (const j65_parser *p); /* Returns the maximum value that j65_get_current_depth() can have. This is the value of max_depth which was supplied to j65_init(), unless the value supplied was 0 or was greater than 224, in which case the max depth will be 224. If this depth is exceeded, the parser will return the error code J65_NESTING_TOO_DEEP. */ uint8_t __fastcall__ j65_get_max_depth (const j65_parser *p); /* Returns the ctx pointer which was supplied to j65_init(). This can be used for passing arbitrary data to the callback. */ void * __fastcall__ j65_get_context (const j65_parser *p); #endif /* J65_H */ ================================================ FILE: src/json65.s ================================================ ;; JSON65 - A JSON parser for the 6502 microprocessor. ;; ;; https://github.com/ppelleti/json65 ;; ;; Copyright © 2018 Patrick Pelletier ;; ;; This software is provided 'as-is', without any express or implied ;; warranty. In no event will the authors be held liable for any damages ;; arising from the use of this software. ;; ;; Permission is granted to anyone to use this software for any purpose, ;; including commercial applications, and to alter it and redistribute it ;; freely, subject to the following restrictions: ;; ;; 1. The origin of this software must not be misrepresented; you must not ;; claim that you wrote the original software. If you use this software ;; in a product, an acknowledgment in the product documentation would be ;; appreciated but is not required. ;; 2. Altered source versions must be plainly marked as such, and must not be ;; misrepresented as being the original software. ;; 3. This notice may not be removed or altered from any source distribution. .macpack generic .include "zeropage.inc" ;; routines from the cc65 runtime library .import callptr4 .import incsp4 .import incsp6 .import negeax .import pushax .import resteax .import saveeax .export _j65_init .export _j65_parse .export _j65_get_string .export _j65_get_length .export _j65_get_integer .export _j65_get_line_offset .export _j65_get_line_number .export _j65_get_column_number .export _j65_get_current_depth .export _j65_get_max_depth .export _j65_get_context ;; zero page locations state = regbank strbuf = regbank + 2 inbuf = regbank + 4 inbuflast = tmp4 ; length - 1 charidx = tmp3 ; position in inbuf evtype = tmp2 ; only used as an argument to call_callback esc_code = tmp2 ; only used as an argument to lookup_escape tmp5 = sreg tmp6 = sreg+1 long1 = regsave long2 = ptr1 ;; zero page locations (for _j65_parse) jlen = ptr3 ;; character properties prop_ws = %10000000 ; must be hi bit (we use bmi/bpl to test) prop_str = %01000000 prop_lit = %00100000 prop_int = %00010000 prop_num = %00001000 prop_sc = %00000111 ; mask for structural character field ;; j65_event .enum J65_NULL = 0 J65_FALSE = 1 J65_TRUE = 2 J65_INTEGER = 3 ; integer and string J65_NUMBER = 4 ; string J65_STRING = 5 ; string J65_KEY = 6 ; string J65_START_OBJ = 7 J65_END_OBJ = 8 J65_START_ARRAY = 9 J65_END_ARRAY = 10 .endenum ;; j65_status .enum J65_DONE = 1 J65_WANT_MORE = 2 ; errors J65_PARSE_ERROR = $80 J65_ILLEGAL_CHAR J65_ILLEGAL_ESCAPE J65_NESTING_TOO_DEEP J65_STRING_TOO_LONG J65_EXPECTED_STRING J65_EXPECTED_COLON J65_EXPECTED_COMMA J65_EXPECTED_OBJ_END J65_EXPECTED_ARRAY_END J65_USER_ERROR ; must be last. not generated by parser. .endenum ;; lexer state .enum lex_ready lex_literal lex_string lex_str_escape .endenum ;; parser state (should fit in 3 bits; otherwise need to change l_ready) ;; (order needs to match dispatch_tab and literal_errors) .enum par_ready par_ready_or_close_array par_key par_key_or_close_object par_need_colon par_need_comma_or_close_array par_need_comma_or_close_object par_done .endenum ;; structural characters (should fit in 3 bits) .enum sc_none ; an illegal character sc_lsq ; [ sc_lcur ; { sc_rsq ; ] sc_rcur ; } sc_colon ; : sc_comma ; , sc_quote ; " .endenum ;; state variables .struct st callback .word ; these two must be first and in this order context .word file_off .dword line_off .dword line_num .dword col_num .dword long_val .dword lexer_st .byte parser_st .byte parser_st2 .byte str_idx .byte stack_idx .byte flags .byte stack_min .byte prev_char .byte .endstruct ;; loads a with specified state variable. clobbers y. .macro getstate arg ldy #arg lda (state),y .endmacro ; getstate ;; stores a to specified state variable. clobbers y. .macro putstate arg ldy #arg sta (state),y .endmacro ; putstate ;; pushes regbank (caller-saved registers) onto 6502 stack .macro save_regbank .repeat 6, i lda regbank+i pha .endrep .endmacro ; save_regbank ;; pops regbank (caller-saved registers) off of 6502 stack .macro restore_regbank .repeat 6, i pla sta regbank+5-i .endrep .endmacro ; restore_regbank ;; sign-extends a into ax. clobbers y. .macro signextend tay asl lda #0 sbc #0 eor #$ff tax tya .endmacro ; signextend .code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_init ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; void __fastcall__ j65_init(j65_state *s, void *ctx, j65_callback cb, uint8_t max_depth); .proc _j65_init sta tmp1 ; save max_depth lda state ; save first 2 bytes of regbank sta ptr1 lda state+1 sta ptr1+1 ldy #4 ; get state pointer off stack lda (sp),y sta state iny lda (sp),y sta state+1 ldy #.sizeof(st) - 1 ; clear state variables to 0 lda #0 loop: sta (state),y dey bpl loop lda #par_done putstate st::parser_st2 lda #$ff putstate st::stack_idx lda #0 sub tmp1 ; subtract max depth from 256 cmp #.sizeof(st) bge depth_ok lda #.sizeof(st) depth_ok: putstate st::stack_min ldy #3 ; copy callback and context from stack to state loop1: lda (sp),y sta (state),y dey bpl loop1 lda ptr1 ; restore first 2 bytes of regbank sta state lda ptr1+1 sta state+1 jmp incsp6 ; tail call to remove args from stack .endproc ; _j65_init ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_parse ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; int8_t __fastcall__ j65_parse(j65_state *s, const char *buf, size_t len) .proc _j65_parse sta jlen stx jlen+1 save_regbank ; save regbank onto 6502 stack ldy #0 ; move state and buf from C stack to regbank lda (sp),y sta inbuf iny lda (sp),y sta inbuf+1 iny lda (sp),y sta state sta strbuf iny lda (sp),y sta state+1 add #1 ; strbuf is state+256 sta strbuf+1 jsr incsp4 ; remove state and buf from C stack loop: lda jlen+1 beq leftovers pha ; save jlen on 6502 stack lda jlen pha lda #$ff sta inbuflast jsr parse tax lda #$ff ; increment file_off by 256 jsr add_a_plus_1_to_file_off pla ; restore jlen off 6502 stack sta jlen pla sub #1 ; decrement jlen by 256 sta jlen+1 cpx #J65_WANT_MORE bne done ; error or success; don't need to parse more inc inbuf+1 ; add 256 to inbuf jmp loop leftovers: ; hi byte of jlen is zero ldx jlen beq done ; if length was a multiple of 256 dex stx inbuflast txa pha jsr parse tax pla jsr add_a_plus_1_to_file_off done: restore_regbank ; restore regbank off of 6502 stack txa ; return value signextend rts .endproc ; _j65_parse ;; adds a plus 1 to file_off. ;; clobbers a and y. preserves x. .proc add_a_plus_1_to_file_off ldy #st::file_off sec adc (state),y ; file_off sta (state),y iny lda (state),y ; file_off+1 adc #0 sta (state),y iny lda (state),y ; file_off+2 adc #0 sta (state),y iny lda (state),y ; file_off+3 adc #0 sta (state),y rts .endproc ; add_a_plus_1_to_file_off ;; x will contain character, and a will contain character properties ;; on exit. clobbers y. ;; negative flag is set based on hi bit of properties. (prop_ws) .proc getchar ldy charidx lda (inbuf),y tax bmi hiset lda charprops,x rts hiset: lda #prop_str ; non-ascii unicode char, legal only in strings rts .rodata charprops: .byte 0 ; $00 .byte 0 ; $01 .byte 0 ; $02 .byte 0 ; $03 .byte 0 ; $04 .byte 0 ; $05 .byte 0 ; $06 .byte 0 ; $07 .byte 0 ; $08 .byte prop_ws ; $09 .byte prop_ws ; $0a .byte 0 ; $0b .byte 0 ; $0c .byte prop_ws ; $0d .byte 0 ; $0e .byte 0 ; $0f .byte 0 ; $10 .byte 0 ; $11 .byte 0 ; $12 .byte 0 ; $13 .byte 0 ; $14 .byte 0 ; $15 .byte 0 ; $16 .byte 0 ; $17 .byte 0 ; $18 .byte 0 ; $19 .byte 0 ; $1a .byte 0 ; $1b .byte 0 ; $1c .byte 0 ; $1d .byte 0 ; $1e .byte 0 ; $1f .byte prop_ws|prop_str ; $20 .byte prop_str ; ! .byte prop_str|sc_quote ; " .byte prop_str ; # .byte prop_str ; $ .byte prop_str ; % .byte prop_str ; & .byte prop_str ; ' .byte prop_str ; ( .byte prop_str ; ) .byte prop_str ; * .byte prop_str|prop_num ; + .byte prop_str|sc_comma ; , .byte prop_str|prop_int|prop_num ; - .byte prop_str|prop_num ; . .byte prop_str ; / .byte prop_str|prop_int|prop_num ; 0 .byte prop_str|prop_int|prop_num ; 1 .byte prop_str|prop_int|prop_num ; 2 .byte prop_str|prop_int|prop_num ; 3 .byte prop_str|prop_int|prop_num ; 4 .byte prop_str|prop_int|prop_num ; 5 .byte prop_str|prop_int|prop_num ; 6 .byte prop_str|prop_int|prop_num ; 7 .byte prop_str|prop_int|prop_num ; 8 .byte prop_str|prop_int|prop_num ; 9 .byte prop_str|sc_colon ; : .byte prop_str ; ; .byte prop_str ; < .byte prop_str ; = .byte prop_str ; > .byte prop_str ; ? .byte prop_str ; @ .byte prop_str ; A .byte prop_str ; B .byte prop_str ; C .byte prop_str ; D .byte prop_str|prop_num ; E .byte prop_str ; F .byte prop_str ; G .byte prop_str ; H .byte prop_str ; I .byte prop_str ; J .byte prop_str ; K .byte prop_str ; L .byte prop_str ; M .byte prop_str ; N .byte prop_str ; O .byte prop_str ; P .byte prop_str ; Q .byte prop_str ; R .byte prop_str ; S .byte prop_str ; T .byte prop_str ; U .byte prop_str ; V .byte prop_str ; W .byte prop_str ; X .byte prop_str ; Y .byte prop_str ; Z .byte prop_str|sc_lsq ; [ .byte prop_str ; \ .byte prop_str|sc_rsq ; ] .byte prop_str ; ^ .byte prop_str ; _ .byte prop_str ; ` .byte prop_str|prop_lit ; a .byte prop_str ; b .byte prop_str ; c .byte prop_str ; d .byte prop_str|prop_lit|prop_num ; e .byte prop_str|prop_lit ; f .byte prop_str ; g .byte prop_str ; h .byte prop_str ; i .byte prop_str ; j .byte prop_str ; k .byte prop_str|prop_lit ; l .byte prop_str ; m .byte prop_str|prop_lit ; n .byte prop_str ; o .byte prop_str ; p .byte prop_str ; q .byte prop_str|prop_lit ; r .byte prop_str|prop_lit ; s .byte prop_str|prop_lit ; t .byte prop_str|prop_lit ; u .byte prop_str ; v .byte prop_str ; w .byte prop_str ; x .byte prop_str ; y .byte prop_str ; z .byte prop_str|sc_lcur ; { .byte prop_str ; | .byte prop_str|sc_rcur ; } .byte prop_str ; ~ .byte prop_str ; $7f .code .endproc ; getchar ;; state, strbuf, inbuf, and inbuflast should be set up upon entry. ;; returns status in a. .proc parse lda #0 sta charidx parseloop: getstate st::lexer_st tay lda lex_tab_h,y pha lda lex_tab_l,y pha rts ; jump table; not end of subroutine l_ready: jsr getchar bmi got_whitespace bit flags_prop_lit_or_num bne start_lit and #prop_sc asl asl asl sta tmp1 getstate st::parser_st ora tmp1 tay lda dispatch_tab_h,y pha lda dispatch_tab_l,y pha rts ; jump table; not end of subroutine got_whitespace: cpx #$0d beq got_newline cpx #$0a bne jmp_nextchar getstate st::prev_char cmp #$0d beq got_newline1 ; ignore LF if preceded by CR got_newline: ldy #st::line_num ; increment line number jsr inc_state_long got_newline1: lda #0 ; set column number to 0 ldy #st::col_num sta (state),y iny sta (state),y iny sta (state),y iny sta (state),y sec ; add 1 in make_byte_offset jsr make_byte_offset ; get file offset+1 into regsave ldy #st::line_off ; move regsave into line offset lda regsave sta (state),y iny lda regsave+1 sta (state),y iny lda regsave+2 sta (state),y iny lda regsave+3 sta (state),y jmp nextchar1 jmp_nextchar: jmp nextchar start_lit: getstate st::parser_st tay lda literal_errors,y bne error lda #prop_lit | prop_int | prop_num putstate st::flags lda #0 putstate st::str_idx lda #lex_literal putstate st::lexer_st ; fall thru and process same char as literal l_literal: jsr getchar ldy #st::flags and (state),y bne goodliteral lda (state),y tax getstate st::str_idx tay lda #0 sta (strbuf),y ; null-terminate string txa jsr handle_literal bcs error lda #lex_ready putstate st::lexer_st jmp parseloop ; process the same character again goodliteral: sta (state),y ; write back flags after and jmp putchar l_string: jsr getchar and #prop_str beq illegal_char cpx #$5C ; backslash beq got_backslash cpx #$22 ; double quote beq got_quote jmp putchar got_backslash: lda #lex_str_escape putstate st::lexer_st jmp nextchar got_quote: jsr handle_string bcs error lda #lex_ready putstate st::lexer_st jmp nextchar illegal_char: lda #J65_ILLEGAL_CHAR error: rts ; error exit l_str_escape: lda #lex_string putstate st::lexer_st ldy charidx ; don't need to jsr getchar; don't need props lda (inbuf),y sta esc_code cmp #$5c ; backslash beq escape_later cmp #'u' beq escape_later jsr lookup_escape bcs illegal_escape tax jmp putchar illegal_escape: lda #J65_ILLEGAL_ESCAPE rts ; error exit escape_later: lda #1 putstate st::flags ; flag indicating we need a later escape pass getstate st::str_idx ; re-insert backslash first tay lda #$5c ; backslash sta (strbuf),y iny beq strtoolong lda esc_code jmp putchar1 putchar: ; x contains char to put in string buf getstate st::str_idx tay txa putchar1: ; a contains char, y contains str_idx sta (strbuf),y iny beq strtoolong tya putstate st::str_idx nextchar: ldy #st::col_num jsr inc_state_long nextchar1: ldy charidx lda (inbuf),y putstate st::prev_char getstate st::parser_st cmp #par_done beq done lda charidx cmp inbuflast beq wantmore inc charidx jmp parseloop wantmore: lda #J65_WANT_MORE rts ; end of subroutine done: lda #J65_DONE rts ; end of subroutine strtoolong: lda #J65_STRING_TOO_LONG rts ; error exit disp_illegal_char: lda #J65_ILLEGAL_CHAR rts ; error exit disp_parse_error: lda #J65_PARSE_ERROR rts ; error exit disp_exp_string: lda #J65_EXPECTED_STRING rts ; error exit disp_exp_colon: lda #J65_EXPECTED_COLON rts ; error exit disp_exp_comma: lda #J65_EXPECTED_COMMA rts ; error exit disp_exp_obj_end: lda #J65_EXPECTED_OBJ_END rts ; error exit disp_exp_array_end: lda #J65_EXPECTED_ARRAY_END error2: rts ; error exit pop_and_error: tax pla txa rts ; error exit disp_start_obj: ldx #J65_START_OBJ lda #0 ; index into close_states descend: pha stx evtype getstate st::parser_st2 jsr push_state_stack bcs pop_and_error jsr call_callback bcs pop_and_error pla tax lda close_states,x putstate st::parser_st lda close_states+1,x putstate st::parser_st2 jmp nextchar disp_end_obj: lda #J65_END_OBJ ascend: sta evtype jsr call_callback bcs error2 jsr pop_state_stack bcs error2 putstate st::parser_st putstate st::parser_st2 jmp nextchar disp_start_array: ldx #J65_START_ARRAY lda #2 ; index into close_states jmp descend disp_end_array: lda #J65_END_ARRAY jmp ascend disp_start_string: lda #lex_string putstate st::lexer_st lda #0 putstate st::flags ; boolean for second unescape pass putstate st::str_idx jmp nextchar disp_comma_array: lda #par_ready dca1: putstate st::parser_st jmp nextchar disp_comma_object: lda #par_key jmp dca1 disp_colon: lda #par_ready jmp dca1 .rodata .define lex_tab l_ready-1, l_literal-1, l_string-1, l_str_escape-1 lex_tab_l: .lobytes lex_tab lex_tab_h: .hibytes lex_tab .undefine lex_tab flags_prop_lit_or_num: .byte prop_lit | prop_int | prop_num .define dt_none disp_illegal_char-1,disp_illegal_char-1,disp_illegal_char-1,disp_illegal_char-1,disp_illegal_char-1,disp_illegal_char-1,disp_illegal_char-1,disp_illegal_char-1 .define dt_lsq disp_start_array-1,disp_start_array-1,disp_exp_string-1,disp_exp_string-1,disp_exp_colon-1,disp_exp_comma-1,disp_exp_comma-1,disp_parse_error-1 .define dt_lcur disp_start_obj-1,disp_start_obj-1,disp_exp_string-1,disp_exp_string-1,disp_exp_colon-1,disp_exp_comma-1,disp_exp_comma-1,disp_parse_error-1 .define dt_rsq disp_parse_error-1,disp_end_array-1,disp_exp_string-1,disp_exp_obj_end-1,disp_exp_colon-1,disp_end_array-1,disp_exp_obj_end-1,disp_parse_error-1 .define dt_rcur disp_parse_error-1,disp_exp_array_end-1,disp_exp_string-1,disp_end_obj-1,disp_exp_colon-1,disp_exp_array_end-1,disp_end_obj-1,disp_parse_error-1 .define dt_colon disp_parse_error-1,disp_parse_error-1,disp_exp_string-1,disp_exp_string-1,disp_colon-1,disp_exp_comma-1,disp_exp_comma-1,disp_parse_error-1 .define dt_comma disp_parse_error-1,disp_parse_error-1,disp_exp_string-1,disp_exp_string-1,disp_exp_colon-1,disp_comma_array-1,disp_comma_object-1,disp_parse_error-1 .define dt_quote disp_start_string-1,disp_start_string-1,disp_start_string-1,disp_start_string-1,disp_exp_colon-1,disp_exp_comma-1,disp_exp_comma-1,disp_parse_error-1 dispatch_tab_l: .lobytes dt_none .lobytes dt_lsq .lobytes dt_lcur .lobytes dt_rsq .lobytes dt_rcur .lobytes dt_colon .lobytes dt_comma .lobytes dt_quote dispatch_tab_h: .hibytes dt_none .hibytes dt_lsq .hibytes dt_lcur .hibytes dt_rsq .hibytes dt_rcur .hibytes dt_colon .hibytes dt_comma .hibytes dt_quote .undefine dt_none .undefine dt_lsq .undefine dt_lcur .undefine dt_rsq .undefine dt_rcur .undefine dt_colon .undefine dt_comma .undefine dt_quote close_states: .byte par_key_or_close_object, par_need_comma_or_close_object .byte par_ready_or_close_array, par_need_comma_or_close_array literal_errors: ; needs to match parser state enum .byte 0, 0, J65_EXPECTED_STRING, J65_EXPECTED_STRING, J65_EXPECTED_COLON .byte J65_EXPECTED_COMMA, J65_EXPECTED_COMMA, J65_PARSE_ERROR .code .endproc ; parse ;; event type is in evtype. ;; Returns callback's return value in a. ;; Sets carry if return value is negative. ;; clobbers all regs. .proc call_callback lda inbuflast ; save caller-save regs pha lda charidx pha ldy #st::callback ; get callback into ptr4 lda (state),y sta ptr4 iny lda (state),y sta ptr4+1 lda state ; state ptr is passed on stack ldx state+1 jsr pushax lda evtype ; event type in ax ldx #0 jsr callptr4 ; call the C callback function (in ptr4) tax ; save return value pla ; restore caller-save regs sta charidx pla sta inbuflast txa asl ; set carry if return value is negative txa ; get return value back into a rts ; end of subroutine .endproc ; call_callback ;; add charidx plus carry flag to file_off and store result in regsave. ;; clobbers a, x, y. .proc make_byte_offset lda charidx ldy #st::file_off adc (state),y sta regsave lda #0 iny adc (state),y sta regsave+1 iny lda #0 adc (state),y sta regsave+2 iny lda #0 adc (state),y sta regsave+3 rts .endproc ; make_byte_offset ;; Takes escape code in esc_code (tmp2). ;; If legal, returns escaped char in a with carry clear. ;; If not legal, returns with carry set. ;; Clobbers y, preserves x. .proc lookup_escape ldy #0 loop: lda escape_codes,y beq notfound iny cmp esc_code bne loop dey lda escaped_chars,y clc rts notfound: sec rts .rodata escape_codes: .byte $22,"/bfnrt",0 escaped_chars: .byte $22, $2f, $08, $0c, $0a, $0d, $09 .code .endproc ; lookup_escape ;; Handle a double-quoted string. ;; Check flags to see if it needs a second unescaping pass. ;; Clobbers all registers. ;; On success, returns carry clear. ;; On error, returns carry set with error event in a. .proc handle_string getstate st::flags beq skipescape jsr unescape_unicode bcc skipescape lda #J65_ILLEGAL_ESCAPE rts ; error exit; carry is still set skipescape: getstate st::str_idx tay lda #0 sta (strbuf),y ; null-terminate string getstate st::parser_st cmp #par_ready beq p_ready cmp #par_ready_or_close_array beq p_ready cmp #par_key_or_close_object beq p_key cmp #par_key beq p_key lda #J65_PARSE_ERROR sec error: rts ; error exit p_ready: lda #J65_STRING sta evtype jsr call_callback bcs error getstate st::parser_st2 ; get next parser state in a putstate st::parser_st clc rts ; success exit p_key: lda #J65_KEY sta evtype jsr call_callback bcs error lda #par_need_colon putstate st::parser_st clc rts ; success exit .endproc ; handle_string ;; unescape \\ and \u in the string buffer. ;; returns carry set on error. clear on success. ;; clobbers all registers. .proc unescape_unicode getstate st::str_idx sta tmp2 ldy #0 sty tmp1 loop: cpy tmp2 beq done lda (strbuf),y iny cmp #$5c ; backslash beq escape loop1: sty tmp5 ldy tmp1 sta (strbuf),y inc tmp1 ldy tmp5 jmp loop escape: cpy tmp2 beq error lda (strbuf),y iny cmp #$5c ; backslash beq loop1 cmp #'u' beq unicode error: sec rts unicode: jsr read4hexintosreg bcs error jsr movesregtolong1 lda (strbuf),y cpy tmp2 beq bmp cmp #$5c ; backslash bne bmp iny cpy tmp2 beq bmp0 lda (strbuf),y cmp #'u' beq check_surrogate bmp0: dey bmp: jsr long1toutf8 jmp loop bmp1: pla tay jmp bmp check_surrogate: jsr is_sreg_left_surrogate bcc bmp0 tya sub #1 pha iny jsr read4hexintosreg bcs bmp1 jsr is_sreg_right_surrogate bcc bmp1 pla jsr combine_surrogates jmp bmp done: lda tmp1 putstate st::str_idx clc rts .endproc ; unescape_unicode ;; reads 4 hex digs from strbuf at y into sreg. ;; (buffer length is in tmp2) ;; on success, carry clear, leaves y pointing after 4 digs. ;; on failure, carry set. .proc read4hexintosreg ldx #4 loop: jsr shift_sreg_left_4bits jsr or1hexintosreg bcs done dex bne loop clc done: rts .endproc ; read4hexintosreg ;; clobbers a, preserves x and y. .proc shift_sreg_left_4bits lda sreg asl a rol sreg+1 asl a rol sreg+1 asl a rol sreg+1 asl a rol sreg+1 sta sreg rts .endproc ; shift_sreg_left_4bits ;; reads 1 hex dig from strbuf at y into low 4 bits of sreg. ;; (buffer length is in tmp2) ;; on success, carry clear, leaves y pointing after digit. ;; on failure, carry set. .proc or1hexintosreg cpy tmp2 beq fail lda (strbuf),y iny jsr hex_dig_to_nibble bcs fail ora sreg sta sreg clc rts fail: sec rts .endproc ; or1hexintosreg ;; converts ascii char in a to nibble in a. ;; sets carry if not a hex digit. ;; preserves x and y. .proc hex_dig_to_nibble cmp #'0' blt fail cmp #'9'+1 bge tryupper sub #'0' clc rts tryupper: cmp #'A' blt fail cmp #'F'+1 bge trylower sub #'A'-10 clc rts trylower: cmp #'a' blt fail cmp #'f'+1 bge fail sub #'a'-10 clc rts fail: sec rts .endproc ; hex_dig_to_nibble ;; zero-extends sreg into long1. ;; clobbers a, preserves x and y .proc movesregtolong1 lda sreg sta long1 lda sreg+1 sta long1+1 lda #0 sta long1+2 sta long1+3 rts .endproc ; movesregtolong1 ;; converts long1 to utf8 in strbuf at tmp1. ;; (output index is in tmp1) ;; preserves y. .proc long1toutf8 sty tmp5 ldy tmp1 lda long1+2 bne len4 lda long1+1 beq latin1 cmp #8 bge len3 len2: ldx #1 jsr utf8_shift lda long1 and #%00111111 ora #%10000000 sta long1 lda long1+1 and #%00011111 ora #%11000000 sta long1+1 ldx #1 jmp done len3: ldx #1 jsr utf8_shift ldx #2 jsr utf8_shift lda long1 and #%00111111 ora #%10000000 sta long1 lda long1+1 and #%00111111 ora #%10000000 sta long1+1 lda long1+2 and #%00001111 ora #%11100000 sta long1+2 ldx #2 jmp done len4: ldx #1 jsr utf8_shift ldx #2 jsr utf8_shift ldx #3 jsr utf8_shift lda long1 and #%00111111 ora #%10000000 sta long1 lda long1+1 and #%00111111 ora #%10000000 sta long1+1 lda long1+2 and #%00111111 ora #%10000000 sta long1+2 lda long1+3 and #%00000111 ora #%11110000 sta long1+3 ldx #3 jmp done latin1: lda long1 bmi len2 ldx #0 ; length 1, already in the right format done: jsr writeutf8 sty tmp1 ldy tmp5 rts .endproc ; long1toutf8 ;; writes the first x+1 bytes of long1, in reverse order, ;; to strbuf, starting at y. advances y. ;; clobbers a, x. .proc writeutf8 lda long1,x sta (strbuf),y iny dex bpl writeutf8 rts .endproc ; writeutf8 ;; shift the last 4-x bytes of long1 left by 2 bits. ;; clobbers a, x. preserves y. .proc shift_left_by_2 stx tmp6 jsr shift_left_by_1 ldx tmp6 shift_left_by_1: asl long1,x loop: php cpx #3 beq done inx plp rol long1,x jmp loop done: plp rts .endproc ; shift_left_by_2 ;; shift the last 4-x bytes of long1 left by 2 bits. ;; shifts in the top two bits from the previous byte, too. ;; clobbers a, x. preserves y. .proc utf8_shift dex txa pha jsr shift_left_by_2 pla tax lsr long1,x lsr long1,x rts .endproc ; utf8_shift ;; preserves y. ;; sets carry if sreg is a left surrogate. .proc is_sreg_left_surrogate lda sreg+1 and #$fc cmp #$d8 beq yes clc rts yes: sec rts .endproc ; is_sreg_left_surrogate ;; preserves y. ;; sets carry if sreg is a right surrogate. .proc is_sreg_right_surrogate lda sreg+1 and #$fc cmp #$dc beq yes clc rts yes: sec rts .endproc ; is_sreg_right_surrogate ;; combine left surrogate in long1 with right surrogate in sreg. ;; result in long1. preserves y. .proc combine_surrogates lda long1+1 and #3 sta long1+2 lda long1 sta long1+1 asl long1+1 rol long1+2 asl long1+1 rol long1+2 lda sreg sta long1 lda sreg+1 and #3 ora long1+1 sta long1+1 inc long1+2 rts .endproc ; combine_surrogates ;; parse signed integer in strbuf (length in str_idx). ;; on success, carry clear and result in long1 (regsave). ;; on integer overflow, carry set and overflow set. ;; on illegal character, carry set and overflow clear. ;; clobbers a, x, and y. .proc parse_signed_integer ldy #0 lda (strbuf),y cmp #'-' beq negative jsr parse_unsigned_integer bcs done ; overflow of 32-bit int, C and V are set bit long1+3 bpl done ; if hi bit is clear, it is okay not_okay: sec ; otherwise, set carry and overflow bit an_rts done: an_rts: rts negative: iny jsr parse_unsigned_integer bcs done ; overflow of 32-bit int, C and V are set lda #$7f bit long1+3 bpl okay ; if hi bit is clear, it is okay bne not_okay ; if hi byte is not $80, it is not okay lda long1+2 ora long1+1 ora long1 bne not_okay ; only okay if 3 least significant bytes are 0 okay: jsr resteax jsr negeax jsr saveeax clc jmp done .endproc ; parse_signed_integer ;; parse unsigned integer in strbuf, starting at y. ;; on success, carry clear and result in long1 (regsave). ;; on integer overflow, carry set and overflow set. ;; on illegal character, carry set and overflow clear. ;; clobbers a and y. preserves x. .proc parse_unsigned_integer lda #0 sta long1 sta long1+1 sta long1+2 sta long1+3 loop: tya ldy #st::str_idx cmp (state),y bge done tay jsr multiply_long1_by_10 bcs overflow lda (strbuf),y jsr hex_dig_to_nibble bcs error jsr add_a_to_long1 iny bcc loop overflow: bit an_rts ; bit on an rts instruction will set overflow an_rts: rts ; carry and overflow are both set done: clc rts ; success: carry clear error: clv sec rts ; carry set, overflow clear .endproc ; parse_unsigned_integer ;; multiplies long1 by 10. clobbers a and long2. preserves x y. ;; returns with carry set if result overflows a 32-bit unsigned long. .proc multiply_long1_by_10 jsr shift_long1_left_by_1 bcs done lda long1 sta long2 lda long1+1 sta long2+1 lda long1+2 sta long2+2 lda long1+3 sta long2+3 jsr shift_long1_left_by_1 bcs done jsr shift_long1_left_by_1 bcs done lda long1 add long2 sta long1 lda long1+1 adc long2+1 sta long1+1 lda long1+2 adc long2+2 sta long1+2 lda long1+3 adc long2+3 sta long1+3 done: rts .endproc ; multiply_long1_by_10 ;; shifts long1 left by 1. clobbers a; preserves x and y. .proc shift_long1_left_by_1 asl long1 rol long1+1 rol long1+2 rol long1+3 rts .endproc ; shift_long1_left_by_1 ;; add a to long1. sets carry if result overflows an unsigned long. ;; clobbers a; preserves x and y. .proc add_a_to_long1 add long1 sta long1 lda long1+1 adc #0 sta long1+1 lda long1+2 adc #0 sta long1+2 lda long1+3 adc #0 sta long1+3 rts .endproc ; add_a_to_long1 ;; parse "null", "false", or "true" in strbuf (length in str_idx). ;; on success, carry clear and a contains event number. ;; on failure, carry set. clobbers x and y. .proc identify_literal getstate st::str_idx cmp #4 beq len4 cmp #5 beq len5 fail: sec rts len4: lda #str_null ldy #3 jsr compare_strings beq got_null lda #str_true ldy #3 jsr compare_strings bne fail lda #J65_TRUE clc rts got_null: lda #J65_NULL clc rts len5: lda #str_false ldy #4 jsr compare_strings bne fail lda #J65_FALSE clc rts .rodata str_null: .byte "null" str_true: .byte "true" str_false: .byte "false" .code .endproc ; identify_literal ;; compares string (of length y+1) at strbuf with string pointed to ;; by a (lo byte) and x (hi byte). ;; returns with zero flag set if strings match, or zero flag clear ;; if they do not. ;; clobbers ptr1. .proc compare_strings sta ptr1 stx ptr1+1 loop: lda (strbuf),y cmp (ptr1),y bne done dey bpl loop lda #0 ; set zero flag done: rts .endproc ; compare_strings ;; Handle a literal (a number, or null, true, or false). ;; On entry, a should contain flags. (prop_lit, prop_int, prop_num) ;; Clobbers all registers. ;; On success, returns carry clear. ;; On error, returns carry set with error event in a. .proc handle_literal tax getstate st::parser_st cmp #par_ready beq p_ready cmp #par_ready_or_close_array beq p_ready parse_err: lda #J65_PARSE_ERROR sec error: rts ; error exit p_ready: txa bit flags_prop_lit bne keyword bit flags_prop_int bne integer number: lda #J65_NUMBER do_callback: sta evtype jsr call_callback bcs error getstate st::parser_st2 ; get next parser state in a putstate st::parser_st clc rts ; success exit integer: jsr parse_signed_integer bcs not_integer ldy #st::long_val ; copy long1 to long_val lda long1 sta (state),y iny lda long1+1 sta (state),y iny lda long1+2 sta (state),y iny lda long1+3 sta (state),y lda #J65_INTEGER jmp do_callback not_integer: bvs number jmp parse_err keyword: jsr identify_literal bcs parse_err jmp do_callback .rodata flags_prop_lit: .byte prop_lit flags_prop_int: .byte prop_int .code .endproc ; handle_literal ;; push a onto the state stack. ;; carry clear on success. ;; carry set on error, with error event in a. ;; clobbers x, y. .proc push_state_stack tax getstate st::stack_idx ldy #st::stack_min cmp (state),y blt stack_full tay txa sta (state),y dey tya putstate st::stack_idx clc rts stack_full: lda #J65_NESTING_TOO_DEEP sec rts .endproc ; push_state_stack ;; pop the state stack. ;; carry clear on success, with popped state in a. ;; carry set on error, with error event in a. ;; clobbers x, y. .proc pop_state_stack getstate st::stack_idx tay iny beq stack_empty lda (state),y tax tya putstate st::stack_idx txa clc rts stack_empty: lda #J65_PARSE_ERROR sec rts .endproc ; pop_state_stack ;; increment the long at state+y to state+y+3 by 1. ;; clobbers a and y. .proc inc_state_long lda (state),y add #1 sta (state),y bcc done ; short circuit for speed iny lda (state),y adc #0 sta (state),y iny lda (state),y adc #0 sta (state),y iny lda (state),y adc #0 sta (state),y done: rts .endproc ; inc_state_long ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_string ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; const char * __fastcall__ j65_get_string(const j65_state *s); ;; (string buffer is the second 256 bytes of state, so all we have ;; to do is increment the high byte of the argument) .proc _j65_get_string inx rts .endproc ; _j65_get_string ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_length ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; uint8_t __fastcall__ j65_get_length(const j65_state *s); .proc _j65_get_length sta ptr1 stx ptr1+1 ldy #st::str_idx lda (ptr1),y ldx #0 rts .endproc ; _j65_get_length ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_integer ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; int32_t __fastcall__ j65_get_integer(const j65_state *s); _j65_get_integer: ldy #st::long_val+3 ;; copies the value at ax+y-3 thru ax+y to eax get_long: sta ptr1 stx ptr1+1 lda (ptr1),y sta sreg+1 dey lda (ptr1),y sta sreg dey lda (ptr1),y tax dey lda (ptr1),y rts ; end _j65_get_long ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_line_offset ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; uint32_t __fastcall__ j65_get_line_offset(const j65_state *s); .proc _j65_get_line_offset ldy #st::line_off+3 jmp get_long .endproc ; _j65_get_line_offset ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_line_number ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; uint32_t __fastcall__ j65_get_line_number(const j65_state *s); .proc _j65_get_line_number ldy #st::line_num+3 jmp get_long .endproc ; _j65_get_line_number ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_column_number ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; uint32_t __fastcall__ j65_get_column_number(const j65_state *s); .proc _j65_get_column_number ldy #st::col_num+3 jmp get_long .endproc ; _j65_get_column_number ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_current_depth ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; uint8_t __fastcall__ j65_get_current_depth(const j65_state *s); .proc _j65_get_current_depth ldy #st::stack_idx sta ptr1 stx ptr1+1 lda #255 sub (ptr1),y ldx #0 rts .endproc ; _j65_get_current_depth ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_max_depth ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; uint8_t __fastcall__ j65_get_max_depth(const j65_state *s); .proc _j65_get_max_depth ldy #st::stack_min sta ptr1 stx ptr1+1 lda #0 sub (ptr1),y ldx #0 rts .endproc ; _j65_get_max_depth ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; j65_get_context ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; void * __fastcall__ j65_get_context(const j65_state *s); .proc _j65_get_context ldy #st::context+1 sta ptr1 stx ptr1+1 lda (ptr1),y tax dey lda (ptr1),y rts .endproc ; _j65_get_context ================================================ FILE: tests/create-testfile-disk-image.pl ================================================ #!/usr/bin/perl -w # JSON65 - A JSON parser for the 6502 microprocessor. # # https://github.com/ppelleti/json65 # # Copyright © 2018 Patrick Pelletier # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. use strict; use FindBin; # Assumes that the "ac" shell script wrapper for Apple Commander # is on your PATH: # https://applecommander.github.io/install/ my $ac = "ac"; my $diskimage = "testfile.po"; my $tests = $FindBin::Bin; chdir ($tests); my $blue = "\e[34m"; my $green = "\e[32m"; my $red = "\e[31m"; my $off = "\e[0m"; sub mysystem { my @cmd = @_; print join(" ", @cmd), "\n"; if (system (@cmd) != 0) { if ($? == -1) { die "$red*** fatal: $!$off\n"; } elsif ($? & 127) { die (sprintf ("$red*** fatal: signal %d$off\n", $? & 127)); } else { die (sprintf ("$red*** fatal: exit code %d$off\n", $? >> 8)); } } } sub print_heading { my $str = $_[0]; print "$blue*** $str$off\n"; } print_heading "Creating disk image"; mysystem ("rm", "-f", $diskimage); mysystem ($ac, "-pro140", $diskimage, "testfile"); print_heading "Adding test program"; my $program = "testfile.system"; mysystem ("$ac -as $diskimage $program < $program"); print_heading "Adding JSON files"; my @json = split (' ', `echo file??.json`); foreach my $json (@json) { mysystem ("$ac -p $diskimage $json TXT < $json"); } ================================================ FILE: tests/file00.json ================================================ { "hello", "error" } ================================================ FILE: tests/file01.json ================================================ { "mismatched": true ] ================================================ FILE: tests/file02.json ================================================ { null: "bad" } ================================================ FILE: tests/file03.json ================================================ { "error": $foo } ================================================ FILE: tests/file04.json ================================================ [[[[[[[[[[[[[[[[["nesting"]]]]]]]]]]]]]]]]] ================================================ FILE: tests/file05.json ================================================ { "multiple": 1, "lines": 2, "trouble":: 3 } ================================================ FILE: tests/file06.json ================================================ [ "an", "extra", "comma", ] ================================================ FILE: tests/file07.json ================================================ [ "a string which is way too long: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!!?ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210 :gnol oot yaw si hcihw gnirts a" ] ================================================ FILE: tests/test-file.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include "json65-file.h" static uint8_t scratch[1024]; static char filename[80]; static void errfunc (FILE *err, void *ctx, int8_t status) { fprintf (err, "Unknown error %d\n", status); } static int8_t callback (j65_parser *p, uint8_t event) { return 0; } int main (int argc, char **argv) { FILE *f; int8_t status; uint8_t width, height, i; screensize (&width, &height); for (i = 0 ; i < 100 ; i++) { snprintf (filename, sizeof (filename), "file%02d.json", i); printf ("\nParsing %s\n\n", filename); f = fopen (filename, "r"); if (! f) break; status = j65_parse_file (f, scratch, sizeof (scratch), NULL, callback, 16, stderr, width, filename, errfunc); fclose (f); cgetc(); } return 0; } ================================================ FILE: tests/test-print.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include "json65-print.h" static char buf1[2048]; static char buf2[2048]; static j65_parser parser; static j65_tree tree; static const char infile[] = "test-print.json"; static const char outfile[] = "json.test.print.tmp"; static int do_test (void) { int8_t status; size_t len; FILE *f; int ret; j65_init_tree (&tree); j65_init (&parser, &tree, j65_tree_callback, 255); len = strlen (buf1); status = j65_parse (&parser, buf1, len); if (status != J65_DONE) { fprintf (stderr, "status %d\n", status); return 1; } f = fopen (outfile, "w"); if (! f) { fprintf (stderr, "Couldn't open file '%s' for writing\n", outfile); return 1; } ret = j65_print_tree (tree.root, f); fputc ('\n', f); fclose (f); if (ret < 0) { fprintf (stderr, "Error writing file\n"); return 1; } f = fopen (outfile, "r"); if (! f) { fprintf (stderr, "Couldn't open file '%s' for reading\n", outfile); return 1; } if (! fgets (buf2, sizeof (buf2), f)) { fprintf (stderr, "Couldn't read file\n"); return 1; } fclose (f); if (0 != strcmp (buf1, buf2)) { fprintf (stderr, "strings not equal:\n%s%s\n", buf1, buf2); return 1; } j65_free_tree (&tree); return 0; } int main (int argc, char **argv) { FILE *f; int badness = 0; f = fopen (infile, "r"); if (! f) { fprintf (stderr, "Couldn't open file '%s' for reading\n", infile); return 1; } while (fgets (buf1, sizeof (buf1), f)) { badness += do_test (); } fclose (f); if (badness == 0) fprintf (stderr, "Success!\n"); return badness; } ================================================ FILE: tests/test-print.json ================================================ ["Hello","World!"] {"foo":"bar","nested":[1,2,3]} "Just a string" 5 false [0.32572436,true,-11186217319148845949,true,0.27938694,0.75560504] [] {"$":1.8247128e-2,"9Sz2D\r":12737293809857351644,"\u0016c\u0004\f":false} ["\u0002=\"{",null,"pb\u0005","\u0011r't",9.4976306e-2,"'"] {"":7603252728548869453,"{\u001e":"\n","<\u001536%E":274716178498089137,"&lB":false,"%(v\u001f\u0001t":-13982809748860309521} {"c+F![":"","\u0016\n\u0001\u0004UB":"<","":4.665512e-2,")\nX3\u0019":"","EK%":"\u0011\u0013\u0014","vg\u00047m":false} {"odG\u0013C":0.44743448,"@nuwx":false,"S)5aT":"","}\u0004\u001fa":-12152612225799926450,"\f0m\u0006":-10691034618040127166} [0.5863078,"OJ",0.15542328] ["Du\u0014",8827766992909995585,0.2134161,0.90028864,"C"] ["4t4(w","+BPq",false,45313040627093027] ["\u0018Q~F",0.33080858,"\u001cNu9 ","f\u001a5H2R",1707525377738296452,0.924928,true,false,false,true,0.9340122,0.5125267] ["Q4\u0004",null,"Hw","h","",-3312958782175383110,false,"`V0u~t",-17853009426579656790,true,"\u0012\u001fKe5\u0001",null] {"\t\u0001FK\u0010\u0012":"E"} [true,3213833407962205374,false,0.9740944,null,0.69657785,-12761276671025544045,false,4932447720730719695,2646858136238674420,225658451089210750] [10790624673763905503,12435287114942852151,-7403136589746519035,14505181554639941694,"'u"] {} {"O\u000e\u0019t\u0018c":-3460634944561346323,"'yXb\u001e":false,"=#+Q":0.11444086,"\u0015F\u0006":false,"V^":814108585199004714,"\rTA":0.47725976,"{\"\u0001\u001aw_":"Y.*","UT\u0018":false} {"*":0.8523169,"\u0010,":false,"\u00079":0.8049666} [-5327988121100041754,false,-13018219928679153234,-2331534658977772737,0.78117466,"#a\u00190'","",false,4.1714847e-2,0.56394154] {"":-15521982000753555023,"||\u0003e\u0013\u000b":-1690917563470919131,"?":12979257830205574254,"HL":true,"O~":7713027951886179383,"H\u0007":"r\f=P","4\u0005Ws":0.19205546,"F\u000f(ox4":-5887537840175889906,"H+\u0006l\u0010":"\u0017","N":null,"":false} [-12036274170737061087,"G,Xc~w","l\b",false,true,-14114159388375863713,-17049232511652779296] ["T","",4.8560143e-2,10885120778919441467,0.28279763,15914637999585694553,9020298222284681635,false,"\rw+m",null] [-17968810982863699742,false,null] {"j\u000b7(=":"d\u000beSa","\u001fn=8^|":""} {"":-4299274683684051628,"":0.89675933} [0.69776416,6.477058e-2,0.6377239,4.8692107e-2,true] ["B_Xm7",0.64527464,[],null,{"":0.60033214,"\u000bj:\u000b/":-17464052542706863991,"\u0014&cI\u0012+":[],"P":[]}] [false,8.072078e-3,0.7576571,null,["\u0004h",["X",0.45122665,""],0.11203796]} [true] ["\u001cDP",[0.8626665,12114414041893822438]] {"*\u0016\f1}":{"1\u001b":0.36654127,"\u0007;{\"":{"":"41e\u0018","\u0015fT;":11574806650096644037,"Z":"_0\u0006&X"},"vE+ ":2935046322892156719,"":""}} {"?+hf`":{}} [null,[true,-9913222499256921221,null,15743710446996284792],{},true] [[[-9627130801059221053,null],4065208257197815031,null],-9767422520931038936] [[false,false,0.95248777],true,0.19347006] [true,null] [false,false,0.4905488,1.1336803e-3,"J"] [0.25115472,null,"",0.8550764] {"b\tH":7491698839064921711,"":[0.9998612,false,null,0.665416,17844276928723417672],"\u0005t":null} [{"]\u001fM\u0001":0.62482804,"?y ":[false,"T",0.40800726],"":[0.2510013,false,{"AH m":[null]},{"V":"7"}],"h\u001e%C":0.30841583,"":"!s\u0013"},{"Vy\u0010@":"7>","":"\u0017I\u001d","W7M2A":{"C\u0005u:J9":{"!%lu0":{"|":1773417749156486461,"":0.7282211,"\u0016O":0.7322229,"O\u0015o%4":[5635062097326633238],"\f\"":"L\u00040\u0002"},"oF\u00110\u001es":{"@*3A0\u0010":-11941081127894283313,"\u0010":[1.052928e-2,[true,-18053543071630053160,1.9187033e-2,{}],false,[["\u0001\u0014$|",{"@V\b":"\f\u0006:\u001fv|","E\u001b\"c\u0006\n":"\u0019B%\n","5\f#*\u0014O":["","x\u0014M9",[-10952946846407602584],-17898030190354030870],"e~|5",[false,-6621870650021842955,0.105413735],null,{"m":["",false,{"i":[-11305999548229241663,"",0.7880725,"",7620683624483849005],"Ve\u0006c":[[[false,"K4\u0018",false],[[[3227070858722758620,[13811021058727279879,true,true,0.71696544,{"\u000b":15556099159842916855}],0.19199622,[]],"v",null],6.888121e-2,[14372151898541712850]]],{"A^\u0006i\u001a\t":{"":0.32023412,"":[0.55937934,17929676292327432669,-7393696966288757142,true,0.41084045],"[\u0007*":[],"}kt\u0012um":619648060789260132,"vvf":0.42513674}},"^'U5",6631621892532225136,[]]," R\u001c\u0013\u0002":"885(","\u0013{_W":" ! #c"}],"\u0016;":null,"":[{"LW\n\u0017$":-4721124271466594444,"\u001a}":16564997567000030707,"B":{"O;\u001e":[],"":{"A":{"}F":0.39923227,"\u00132`\u001en":0.85526997},"\u000fZG1H\\":true},";\u0013E\t\u0018":[[],0.21486646,{".6t":[{"e6":null,"%\u0018\f\r8W":"~dsQ\u001d"},false,0.3842417],"]\f8e\\":null},[],false]},"5+":"8","&\\4":true},"(D"],"in,=A":"DW","'\u001b":18160291427748464709}]},"$Ao>":0.79443836} ["=",0.11248773,4514021307219340329,true] [{"\u0019e\u0011zt":{"IB\u0014":true,"*E^":{},"_$~":true},"}\u0019":{"iB''K`":"+b\u0019E\u000eE","\u001c9":"k5$SK,","g":{"{":[[],false,0.93499947],"P":0.3487832,"":true,"@\\Fm/5":true,"i":[[-5619408379903070830],true,false]},"(xk9W":{}},"\u0018>":true,"":5128100828239114421,"]\u0003`5":true},true] [14162558928204775417,false,"!\u0016\u0011"] {"ctk?":[false,{"9":[],"\"b^S":false,"elOrO\u000b":{"\u0006FL?j7":{},"(":null}},false,0.21888393,"\u000113"],"SW\\":["y3\u0002=%"],"g\u0002'/gS":"=%5H\u000e","\u0002m\u0001":-5775961272864050555,"NS-#^":{}}]},"^z0 ":{"S\b":-15150394133378043565,"})\u001c\n9\u0016":null}} [""] {"z\u0016":0.65568596} [0.7439682,false,{"*\r\u0018_F":"Q\u0014Rok","8":[0.5402592,"_/A\n?(","\\5E\u000f"],"":[{"XtR1O":[{"\u0010;\u0016\\\u000bB":null,"?h\nA(?":true},[0.61750627,[true,-15962398617127831102,[],-8592170384657825370,[]],{"i":[0.24247152,null,"Vo\u0012b\u0016_"],"\u0003\u0004f":"RSZZ&"},1218096336475644177],17017452515666078705,0.46202022,0.30780977],"\u0012":"\n(H\r|s","Wl;*":{"\fR8":-8961150626459389675,";r":{"8":["o",11470537646784762752,4592061587037721434,0.50926375]},"f\u001b!=-7":"q\u000b","xHU)0":[],"&U":[]},"<<1\f#x":{"D\f":{"r\u001cLm":"= Dpj","\r\u0014d":[-17485124139144911826,[false],0.5664439],"rxJ.b":{"~\u001c;":"","G$:\n":"\u00071W"},"zEBx":0.9278375,"":[false,"Q","-\u0004",0.21181405,0.38027972]},";?E":{"\u001fz":0.73130953,"":11888366427098764180,",G|U":0.53782547}},"-ls\u000fp":"f\u000f"}]," ~.x(":[null]},0.46352834,"x"] [null,{" F7\u0010":12459903973742877697,"\u0005X":0.36702603},"\u0017R%\u0007",-13502214511406886705,"\u001db"] ["%\u001bj#\u000e'",0.29517365] {"J\u0014'6QK":[6.354201e-2],"\u0001":0.23121226,"UiN\"r":[{"B\u00136VZ":null,">":null,"y":false},["",null,true,-5570114783189784073,{"~\u0012\f:":0.37777317," ":null}],[false],true],"6N\u0012":-8156302497732879601} {"T{|7#":false,"$>":"\u001cB?8"} {"1}\fEU":{"4\u0017\u001f\u0007S":209123727378233237}} [[[{"?;\u000b3":-518309841821504454,"!FN)\u0007":["\n&\u001b",[{"":-2921903025505147040,")#X":-12463943729709922411,".\tr":false,"p$":{"luP(T":15091199490892917922,"\u0019~\u000f\u000f ":{"egYH\u000e~":-14291716192989733769,"6":-5227850293455434343,"m;S":null,")":"m\u001b@kj9"},"M\u0012%\u0011|":false,"G":[],"":"\u000b*!d","i8\u0003d":[],"X!N>":true}},"\u0014":0.7147376,"2y(":true},3.3874035e-2],"\u000b\u0010*=f":"/7\u00142-i","hm\u001c\t\nK":true,"j8dh\u0003g":{"e@ ":9390488342921961408,"'L{;":15069019766623989691,"U\r'I\u00135":-1903112358189338839}} [{"~j":"\"0","=r\u0003":"*+r@0s","":0.19081467},".h\u0019}R"] {"":"ju","m^~":null} [[0.3681597],"","9\u001ad","e\u000fw"] {"\u0015~e\u001bi%":"Kk\u0018"} {"Y":{"r\u0005L\u001c":null,"4o8":0.15798777},"@8":4998012367855424481,"z":0.3768953,"0\u0012":{"/#E=,:":"","":0.7964526,"=I\u0012q\u0012":{"":6924435281412963366,"gf":[-4456579596822184692,"\u001fT~a"]}},"#)t":"f "} {"T/({J":"GAGU,","T:":9.503329e-2} [true,{"\u0011":-12217948383421578866,"7mw;m":true,"\u001aX&":0.5159844},"1",[[[0.29598004],[true,-15095167341672935746,[null,[[["W"],13939264792053538017,false,{"kKox;":4.7398925e-2,"Y8\u0013":[]}],[{"? ":"","\u0015":"7u"},0.35944933,-17901161567319789421,null],{"fB\u0011{\\O":null,"":0.8980226,"\u001fy":0.76103854,"G&e]":"\u0005KKQ]"},true],-16258605383369635883,null],[[],0.6391701,"\u0003R\u000f[u\u001f",12642424132404311926,["\u0016:z\u0004dk",[0.96023333,{"v1w":7.775545e-3,"7/\u0012\u0007k":{"n\u0010!":2886994691609596810},"(":8669124115758688723,"T":5.8164835e-2,"":""},{"0:\t":null,"\u000ev\u000eC":6.865555e-2,"|":1458938999225059326}]]],[{"S":[],"}x6":[]},true,-7723708214213539854,0.22195089,"\u0014\u0011\u0003$"]],{"[s":null},8786201060069342327],[0.90378374,"]*",{"+:":"k/\\","*s1\n\u0012":{"":[false,true,0.614962],"VS\n":-12408716702998572025,"\u0003H":-3423065839382307351,"9":["@}X\u000e",false,0.19913095,0.7669836,{"\u001e&|mM":"\u0014UVJe"}],"PbulK":0.79707235},"P":4909128968987850267,"":{"\u0017t":{},"":-13059659663321679233,"{\u001e)y#\u0018":{"7\"}&":false,"":["#25v",{},[null,0.749951,[")%\u001c2}",{"T":[{},1704987698030816733,14422845730038874925,0.2691697],"_\nq:":[[406467608425265142],{"":{"\u0017":{"x\u0010":[],"B":"@'QQc"},"\r6$\u0015Q":"\u0004","QQ":"<-\u0007=-","\u0002l/\u0001j":false},"":false},null,0.5161205,0.35366076]},"|\u001e","*3G^",false],null]]},"\u0017In":"xd"}}]],-11794737418073577276] [-3875596124597186130,["h\u0017C \u0017-",0.51341975,[0.8909479,null,{"mLt$i":5931259876451380254,"q],\nTY":2747696704935867519,"xM\u0005":true,"\u001fON":-18301498459940935941}],0.8663333],{"GgR":{"oLH":12686260561828831420}},false,{"7":[[{}],null,true,5723369010967295510]}] {"":"h@?4~_"} {"\u001cO2":null,"ug`N":0.25825077} {":zO":-17886397144894567202} [false,{")":0.46914285,"":[null,17701263076267415360,"\u001b`",["\b":0.7487198,"yq8\u0018\u0013":0.48864508,"'vz^":["\u0016*c\u0019",true,null,[-16625719407348066219,0.27814394,[[]],-18146293756557779016,0.27746165],true]},["7z+","3qP%Y",0.8580701,-17461057325880571999,""],[[0.6738269],{"J-\u001d[":["\u00020h\u0007mf","b\u000bE\u0005\u0002\u001b",null,5861749072356890930,-6091629617043366513]},[[],2.647984e-2,0.665144,"]"]]]]],"^jQ\u0014$F":0.87910104,"Y":0.64870834},0.5446196,{"\u001c\u000f\u000bBI":false,"'U":null,"1\u0012=&":[["",true],"\u001btQ",true,"h"],"l2>":[10045547607866373384,0.12400788]},null] [{"\u0018":-9879941084256315652,"":0.5923274,"\\%\b:":16734910719493405340,"Sff":" F;"}] {"\u0018]":"\u000fS","0v":null,"":{"hqO":{"+C\u0011]":-10606520924323826758,"":["#X\\u",true],"Awxy\u0018":null,"Q+f\u0019BV":null,"H{y":0.45539778},"JRL~":"i\u001b&1","":-12146818804840233073,"7l\u0002|Y":{}}} ["\u0003"] {"l0M":{"":-15071382070707286465,"\r{kC":4969650560638343485,"P9":"1","x\t@\u001d0":14144403874232608022," Ar{j":["U\u000eM",["",{"!yEB!":{"":[],"E":0.24353248,"p\t":"08\u0012"},";+":{"\u0005V\u0013\t\t":null,"":[null,[{"":[[[-6211830736174882578,{"&E\u001bQ":false,"C":0.9126065,"^K\u001e\u0005":[false],"mk1\u001dh":0.67198795},-4756454757795884285,{"|6C":{"\u0005":"\u001a","\"?oHZ":true,"J!0T(":-3315779624432601658},"_@;":true,"{":null,"/WV9":false},{"'Z":true,"x\u0001\u0001K^\u0013":{"<\u001c\u0003":"g\u001cF\u0010\u001ex","/":-9581477031750821867,"kA\u0007":[],"":false,"XT\"h":0.9187148}},-4712406852286132207],"n\u001b%\\@:":true}]}} {"0":{"^":false,"\u001d[Y":true,"\u00016\u0011%":{"^":-13422956777875372902,"":0.6748177,"UK":-14185007865157151724,"#":[]},"lV\f":16496414788763842682,"\\1hs":0.75634325},"":"J$cjy"} {"j\u0018L\u0010":null,"~C":0.5380389,"wWfI\u001d":7214825108176793749,"\u0002*k":false} [[0.7889624,0.19233567,{"B>\u0007_YS":0.54761636,"\u0015\u000f4\\\u0003":0.68591905,"Pi":-1082999137390589628},2313694156741794768,[[{"":-10441029711331055060,"":null},0.2104497,0.91654104,[true,"\u0006\u001eIN~M",2.5443256e-2,true,"Ybj"]],true,"\u0013"]],0.686076,[[null,0.8077588,{",\b)":{"x\u0012\u0002*',":false,"":{},"\u0014":[],"\u000b\u0002\u001d<":15337849397348555006},"\u00151It":[0.8737354,"R\u001fP",{"\u0005\u000eYd":"b\\*",",\u001c\u000b":0.1559087,",\u001c[":"",">":true,"":[0.6415,3655223684131779968,{"\u0012-\u001eyh":"Y"}]},[false,true,"8H\u0007\u0013"],{"`\u0006}MN":false,"\u0014\u001fG-}":{"":"~+\u0010","\u001a@:E\u0016":true,"FV\u00173":0.83031607,"i":{}},"'":{"":0.2809633}}]}]]] {"F":[0.28045475,6395486746026534910],"":[],"=\n&@?%":0.75717854} [[""],[-4429524012470214935,-2796661001313312842]] [false] ["O\u0011ak!f",0.31498963,false,[true]] {"foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo":"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar"} {"vh#wH":null,"*l":2296691638,"W\u0002":3606753554,"`\u000e.\b":0.385041,"N3h4m":null} {"M\u00185-":[0.86986375,0.33169776,"\u0015",true,-3787920576],"":-2266678859,"":{"\u001d":3294973006,"x(\u001d|":0.41438067,"#q":true,"":null},"L$":[false],"op[bi":null} ["'{\\y[","qK",2505258107] {"\u0014\u0016":true,"7\u0013":true,"":[null]} [null,[0.75785536],""] {":":0.35312474,"*\nk":"?\u0014N)"} {"":0.50028557,"P\u0005)&\u0002":null,"=\u0002y0":-2062734622} [0.30801737,-2716449190,"6$am\u0002m",1.2181699e-2] [{"\u0007khaUg":1401686211,"\bB":true,"":{}},[[{"\u0015IE[":"\u0012T"},"mE","f",0.94850457],"+zRXV"],772415052,{"\u00182\u001b\t":{".\u001bw\u0017s7":{"\u0010":[0.44792414],"o$|*":true}},"\r\u000flp\u001fF":{"D',.\fr":"(\"WSmP","c":[0.8529478],"C\n\u000bd\u001d":{"":-1054348074,"18rf\u001a":0.6809252},",g\u000e}":{}}}] [0.9796113] [0.6591127,[""],[0.79424393],{"XE\u001ep":{".(\u0016":false},"Z?lR\u0015,":"","\nz":[false,3117789160,false],"sd#P\u0004":2807155556,"\u0010":null},-1784589105] {"+YJ(":null,"Aq#^_$":-2277615052,"":0.97045594} ["[\u001e*Pdj",null] {"<\u000f\u001cE\u0010\t":89655629,"`x\u0004\u0015\u0005H":0.13868803,"\fb{)Z\u0004":-617492212,"We\u000f\u0005z\u0001":[0.75462246,"",0.7799082,409262730],"QfQ\u0017":0.8736933} ["\th/y\"",0.6817276] {")k":[0.38475662,45616771,{"\u0002p\u0002{E":true,"O":596477863,"KoSE/C":null},0.9854614],"":-2886538406,"D+[R":"Yw\u000b{\u0015"} [0.5344141,"P\u0017sJ\u00072",0.71697176,3692993782] [{"zS{!":{"c\f,M6H":"A","5dba":{"-5\u0011\"":{},"i":2873171131,"\u0005NL":{}},"),h\u0005\bm":{"<|7T\n":null}},"\u0010":{"}\"Q #include #include "json65-string.h" #define ITERATIONS 300 static j65_strings strs; static const char *results[ITERATIONS]; static char buf1[10]; static char buf2[10]; static void print_bucket_usage (void) { uint8_t *lo; uint8_t *hi; uint16_t i, used = 0; lo = (uint8_t *) &strs; hi = lo + 256; for (i = 0 ; i < 256 ; i++) { if (lo[i] || hi[i]) used++; } printf ("used %u/256 buckets\n", used); } int main (int argc, char **argv) { uint16_t i; const char *tmp; j65_init_strings (&strs); for (i = 0 ; i < ITERATIONS ; i++) { snprintf (buf1, sizeof (buf1), "%u", i); results[i] = j65_intern_string (&strs, buf1); if (strcmp (buf1, results[i]) != 0) { printf ("first loop: '%.20s' (%p) not equal to '%s' (%p)\n", results[i], results[i], buf1, buf1); return 1; } } for (i = 0 ; i < ITERATIONS ; i++) { snprintf (buf2, sizeof (buf2), "%u", i); tmp = j65_intern_string (&strs, buf2); if (strcmp (buf2, tmp) != 0) { printf ("second loop: '%s' not equal to '%s'\n", tmp, buf2); return 1; } if (tmp != results[i]) { printf ("For '%s', %p not equal to %p\n", buf2, tmp, results[i]); return 1; } } print_bucket_usage (); j65_free_strings (&strs); printf ("Success!\n"); return 0; } ================================================ FILE: tests/test-tree.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include "json65-tree.h" static char buf[1024]; static j65_parser parser; static j65_tree tree; static const char infile[] = "test-tree.json"; static int do_test (size_t len) { int8_t status; j65_node *n; uint8_t node_type; const char *str; uint32_t line_number, column_number; j65_init_tree (&tree); j65_init (&parser, &tree, j65_tree_callback, 255); status = j65_parse (&parser, buf, len); if (status != J65_DONE) { fprintf (stderr, "j65_parse returned status %d\n", status); return 1; } n = j65_find_key (&tree, tree.root, "color"); if (n == NULL) { fprintf (stderr, "couldn't find color\n"); return 1; } n = n->child; n = j65_find_key (&tree, n, "linearCutoff"); if (n == NULL) { fprintf (stderr, "couldn't find linearCutoff\n"); return 1; } n = n->child; node_type = n->node_type; if (node_type != J65_NUMBER) { fprintf (stderr, "%u != J65_NUMBER\n", node_type); return 1; } str = n->string; if (0 != strcmp (str, "0.0078125")) { fprintf (stderr, "%s != 0.0078125\n", str); return 1; } line_number = n->location.line_number + 1; column_number = n->location.column_number; if (line_number != 8) { fprintf (stderr, "line number %lu != 8\n", line_number); return 1; } if (column_number != 33) { fprintf (stderr, "column number %lu != 33\n", column_number); return 1; } if (n->location.line_offset != 135) { fprintf (stderr, "line offset %lu != 135\n", n->location.line_offset); return 1; } n = j65_find_key (&tree, tree.root, "banana"); if (n != NULL) { fprintf (stderr, "found banana but shouldn't have\n"); return 1; } j65_free_tree (&tree); return 0; } int main (int argc, char **argv) { FILE *f; int badness = 0; size_t len; f = fopen (infile, "r"); if (! f) { fprintf (stderr, "Couldn't open file '%s' for reading\n", infile); return 1; } len = fread (buf, 1, sizeof (buf), f); if (ferror (f)) { fprintf (stderr, "Couldn't read file '%s'\n", infile); return 1; } fclose (f); badness = do_test (len); if (badness == 0) fprintf (stderr, "Success!\n"); return badness; } ================================================ FILE: tests/test-tree.json ================================================ { "listen": ["127.0.0.1", 7890], "verbose": true, "color": { "gamma": 2.5, "whitepoint": [1.0, 1.0, 1.0], "linearCutoff": 0.0078125 }, "devices": [ { "type": "fadecandy", "map": [ [ 0, 0, 0, 50, "grb" ], [ 0, 50, 64, 50, "grb" ] ] } ] } ================================================ FILE: tests/test.c ================================================ /* JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #include #include #include static j65_parser parser; static int passes, failures; static char buf[80]; #define MAGIC 0x2badbeef typedef struct { int8_t ev; int32_t integer; const char *str; uint32_t line_no; uint8_t depth; } event_check; typedef struct { uint32_t magic; const event_check *events; size_t len; size_t pos; } my_context; static const event_check test00[] = { { J65_DONE, 0, "[] ", 0, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_END_ARRAY, 0, NULL, 0, 1 }, }; static const event_check test01[] = { { J65_DONE, 1, "{} ", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, { J65_END_OBJ, 0, NULL, 0, 1 }, }; static const event_check test02[] = { { J65_DONE, 2, "1234 ", 0, 0 }, { J65_INTEGER, 1234, "1234", 0, 0 }, }; static const event_check test03[] = { { J65_DONE, 3, "-10000000 ", 0, 0 }, { J65_INTEGER, -10000000, "-10000000", 0, 0 }, }; static const event_check test04[] = { { J65_DONE, 4, "1.5 ", 0, 0 }, { J65_NUMBER, 0, "1.5", 0, 0 }, }; static const event_check test05[] = { { J65_DONE, 5, "1e-2 ", 0, 0 }, { J65_NUMBER, 0, "1e-2", 0, 0 }, }; static const event_check test06[] = { { J65_DONE, 6, "\"Hello, World\" ", 0, 0 }, { J65_STRING, 0, "Hello, World", 0, 0 }, }; static const event_check test07[] = { { J65_DONE, 7, "null ", 0, 0 }, { J65_NULL, 0, NULL, 0, 0 }, }; static const event_check test08[] = { { J65_DONE, 8, "false ", 0, 0 }, { J65_FALSE, 0, NULL, 0, 0 }, }; static const event_check test09[] = { { J65_DONE, 9, "true ", 0, 0 }, { J65_TRUE, 0, NULL, 0, 0 }, }; static const event_check test10[] = { { J65_DONE, 10, "{\"foo\": 5} ", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, { J65_KEY, 0, "foo", 0, 1 }, { J65_INTEGER, 5, "5", 0, 1 }, { J65_END_OBJ, 0, NULL, 0, 1 }, }; static const event_check test11[] = { { J65_DONE, 11, "{\"foo\": \"bar\", \"baz\": [1, 2, 3]} ", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, { J65_KEY, 0, "foo", 0, 1 }, { J65_STRING, 0, "bar", 0, 1 }, { J65_KEY, 0, "baz", 0, 1 }, { J65_START_ARRAY, 0, NULL, 0, 2 }, { J65_INTEGER, 1, "1", 0, 2 }, { J65_INTEGER, 2, "2", 0, 2 }, { J65_INTEGER, 3, "3", 0, 2 }, { J65_END_ARRAY, 0, NULL, 0, 2 }, { J65_END_OBJ, 0, NULL, 0, 1 }, }; static const event_check test12[] = { { J65_WANT_MORE, 12, "[1, 2, 3", 0, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_INTEGER, 1, "1", 0, 1 }, { J65_INTEGER, 2, "2", 0, 1 }, }; static const event_check test13[] = { { J65_DONE, 13, "\n\"slash \\/ tab \\t\"", 1, 0 }, { J65_STRING, 0, "slash / tab \t", 1, 0 }, }; static const event_check test14[] = { { J65_DONE, 14, "\"slash \\/ backslash \\\\ tab \\t\"", 0, 0 }, { J65_STRING, 0, "slash / backslash \\ tab \t", 0, 0 }, }; static const event_check test15[] = { { J65_DONE, 15, "\"have \\u0061 nice day\"", 0, 0 }, { J65_STRING, 0, "have a nice day", 0, 0 }, }; static const event_check test16[] = { { J65_DONE, 16, "\"have \\u0061\\u0020nice day\"", 0, 0 }, { J65_STRING, 0, "have a nice day", 0, 0 }, }; static const event_check test17[] = { { J65_DONE, 17, "\"have \\u0061\\\\nice day\"", 0, 0 }, { J65_STRING, 0, "have a\\nice day", 0, 0 }, }; static const event_check test18[] = { { J65_DONE, 18, "\"this \\uD834\\uDD1E is a G clef\"", 0, 0 }, { J65_STRING, 0, "this 𝄞 is a G clef", 0, 0 }, }; static const event_check test19[] = { { J65_DONE, 19, "\"\\u00a9 2018\"", 0, 0 }, { J65_STRING, 0, "© 2018", 0, 0 }, }; static const event_check test20[] = { { J65_DONE, 20, "\"cents \\u00a2 Euros \\u20ac\"", 0, 0 }, { J65_STRING, 0, "cents ¢ Euros €", 0, 0 }, }; static const event_check test21[] = { { J65_DONE, 21, "[\nnull\n,\nfalse\n,\ntrue\n]\n", 6, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_NULL, 0, NULL, 1, 1 }, { J65_FALSE, 0, NULL, 3, 1 }, { J65_TRUE, 0, NULL, 5, 1 }, { J65_END_ARRAY, 0, NULL, 6, 1 }, }; static const event_check test22[] = { { J65_EXPECTED_STRING, 22, "{false: true} ", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, }; static const event_check test23[] = { { J65_EXPECTED_COLON, 23, "{\"hello\", \"world\"} ", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, { J65_KEY, 0, "hello", 0, 1 }, }; static const event_check test24[] = { { J65_DONE, 24, "[\rnull\r,\rfalse\r,\rtrue\r]\r", 6, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_NULL, 0, NULL, 1, 1 }, { J65_FALSE, 0, NULL, 3, 1 }, { J65_TRUE, 0, NULL, 5, 1 }, { J65_END_ARRAY, 0, NULL, 6, 1 }, }; static const event_check test25[] = { { J65_DONE, 25, "[\r\nnull\r\n,\r\nfalse\r\n,\r\ntrue\r\n]\r\n", 6, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_NULL, 0, NULL, 1, 1 }, { J65_FALSE, 0, NULL, 3, 1 }, { J65_TRUE, 0, NULL, 5, 1 }, { J65_END_ARRAY, 0, NULL, 6, 1 }, }; static const event_check test26[] = { { J65_DONE, 26, "2147483647 ", 0, 0 }, { J65_INTEGER, 2147483647, "2147483647", 0, 0 }, }; static const event_check test27[] = { { J65_DONE, 27, "-2147483647 ", 0, 0 }, { J65_INTEGER, -2147483647, "-2147483647", 0, 0 }, }; static const event_check test28[] = { { J65_DONE, 28, "2147483648 ", 0, 0 }, { J65_NUMBER, 0, "2147483648", 0, 0 }, }; static const event_check test29[] = { { J65_DONE, 29, "-2147483648 ", 0, 0 }, { J65_INTEGER, -2147483648, "-2147483648", 0, 0 }, }; static const event_check test30[] = { { J65_DONE, 30, "-2147483649 ", 0, 0 }, { J65_NUMBER, 0, "-2147483649", 0, 0 }, }; static const event_check test31[] = { { J65_DONE, 31, "-4294967296 ", 0, 0 }, { J65_NUMBER, 0, "-4294967296", 0, 0 }, }; static const event_check test32[] = { { J65_DONE, 32, "4294967296 ", 0, 0 }, { J65_NUMBER, 0, "4294967296", 0, 0 }, }; static const event_check test33[] = { { J65_DONE, 33, "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ", 0, 0 }, { J65_NUMBER, 0, "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 0, 0 }, }; static const event_check test34[] = { { J65_DONE, 34, "-10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ", 0, 0 }, { J65_NUMBER, 0, "-10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 0, 0 }, }; static const event_check test35[] = { { J65_STRING_TOO_LONG, 35, "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ", 0, 0 }, }; static const event_check test36[] = { { J65_PARSE_ERROR, 36, "5-5 ", 0, 0 }, }; static const event_check test37[] = { { J65_ILLEGAL_CHAR, 37, "0x2000 ", 0, 0 }, { J65_INTEGER, 0, "0", 0, 0 }, }; static const event_check test38[] = { { J65_ILLEGAL_CHAR, 38, "barf ", 0, 0 }, }; static const event_check test39[] = { { J65_PARSE_ERROR, 39, "nue ", 0, 0 }, }; static const event_check test40[] = { { J65_PARSE_ERROR, 40, "]", 0, 0 }, }; static const event_check test41[] = { { J65_ILLEGAL_ESCAPE, 41, "\"This is \\j not allowed\"", 0, 0 }, }; static const event_check test42[] = { { J65_ILLEGAL_ESCAPE, 42, "\"This is \\uucp not either\"", 0, 0 }, }; static const event_check test43[] = { { J65_ILLEGAL_ESCAPE, 43, "\"And this? \\u\"", 0, 0 }, }; static const event_check test44[] = { { J65_DONE, 44, "{\"foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\": \"baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar\"}", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, { J65_KEY, 0, "foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo", 0, 1 }, { J65_STRING, 0, "baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar", 0, 1 }, { J65_END_OBJ, 0, NULL, 0, 1 }, }; static const event_check test45[] = { { J65_DONE, 45, "[[[[]]]]", 0, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_START_ARRAY, 0, NULL, 0, 2 }, { J65_START_ARRAY, 0, NULL, 0, 3 }, { J65_START_ARRAY, 0, NULL, 0, 4 }, { J65_END_ARRAY, 0, NULL, 0, 4 }, { J65_END_ARRAY, 0, NULL, 0, 3 }, { J65_END_ARRAY, 0, NULL, 0, 2 }, { J65_END_ARRAY, 0, NULL, 0, 1 }, }; static const event_check test46[] = { { J65_NESTING_TOO_DEEP, 46, "[[[[[]]]]]", 0, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_START_ARRAY, 0, NULL, 0, 2 }, { J65_START_ARRAY, 0, NULL, 0, 3 }, { J65_START_ARRAY, 0, NULL, 0, 4 }, }; static const event_check test47[] = { { J65_PARSE_ERROR, 47, "[\n \"an\",\n \"extra\",\n \"comma\",\n]", 4, 0 }, { J65_START_ARRAY, 0, NULL, 0, 1 }, { J65_STRING, 0, "an", 1, 1 }, { J65_STRING, 0, "extra", 2, 1 }, { J65_STRING, 0, "comma", 3, 1 }, }; static const event_check test48[] = { { J65_EXPECTED_STRING, 48, "{ \"extra\": \"comma\", }", 0, 0 }, { J65_START_OBJ, 0, NULL, 0, 1 }, { J65_KEY, 0, "extra", 0, 1 }, { J65_STRING, 0, "comma", 0, 1 }, }; static const char *event_name (uint8_t event) { switch (event) { case J65_NULL : return "J65_NULL"; case J65_FALSE : return "J65_FALSE"; case J65_TRUE : return "J65_TRUE"; case J65_INTEGER : return "J65_INTEGER"; case J65_NUMBER : return "J65_NUMBER"; case J65_STRING : return "J65_STRING"; case J65_KEY : return "J65_KEY"; case J65_START_OBJ : return "J65_START_OBJ"; case J65_END_OBJ : return "J65_END_OBJ"; case J65_START_ARRAY : return "J65_START_ARRAY"; case J65_END_ARRAY : return "J65_END_ARRAY"; default: return "?"; } } static void print_pass (void) { printf ("\033[32mPASS\033[0m\n"); passes++; } static void print_fail (void) { printf ("\033[31mFAIL\033[0m\n"); failures++; } static int8_t callback (j65_parser *p, uint8_t event) { my_context *ctx = (my_context *) j65_get_context(p); const char *ename = event_name (event); size_t pos = ctx->pos; const event_check *ec; int32_t i; const char *str; size_t len1, len2; uint32_t line_no; uint8_t depth; if (ctx->magic != MAGIC) { print_fail(); printf ("Got magic $%08lx but expected $%08lx\n", ctx->magic, MAGIC); return J65_USER_ERROR; } if (pos >= ctx->len) { print_fail(); printf ("Got extra event of type %s\n", ename); return J65_USER_ERROR; } ec = ctx->events + pos; if (ec->ev != event) { print_fail(); printf ("[%u] Got %s but expected %s\n", pos, ename, event_name(ec->ev)); return J65_USER_ERROR; } if (event == J65_INTEGER) { i = j65_get_integer(p); if (i != ec->integer) { print_fail(); printf ("[%u] Got %ld but expected %ld\n", pos, i, ec->integer); return J65_USER_ERROR; } } if (event == J65_INTEGER || event == J65_NUMBER || event == J65_STRING || event == J65_KEY) { str = j65_get_string(p); len1 = j65_get_length(p); len2 = strlen (str); if (len1 != len2) { print_fail(); printf ("[%u] String length is %u but claimed to be %u\n", pos, len2, len1); return J65_USER_ERROR; } if (strcmp (str, ec->str) != 0) { print_fail(); printf ("[%u] For %s, got '%s' but expected '%s'\n", pos, ename, str, ec->str); return J65_USER_ERROR; } } line_no = j65_get_line_number(p); if (line_no != ec->line_no) { print_fail(); printf ("[%u] For %s, got line %lu but expected %lu\n", pos, ename, line_no, ec->line_no); return J65_USER_ERROR; } depth = j65_get_current_depth (p); if (depth != ec->depth) { print_fail (); printf ("[%u] For %s, got depth %u but expected %u\n", pos, ename, depth, ec->depth); return J65_USER_ERROR; } ctx->pos++; return 0; } static void run_test (const event_check *events, size_t len) { my_context ctx; uint32_t line_no; int8_t ret; const char *str = events->str; printf ("test %02ld: ", events->integer); ctx.magic = MAGIC; ctx.events = events; ctx.len = len; ctx.pos = 1; /* Use a small nesting depth to make it easy to test. */ j65_init (&parser, (void *) &ctx, callback, 4); ret = j65_parse(&parser, str, strlen(str)); if (ret == J65_USER_ERROR) { return; } if (ret != events->ev) { print_fail(); printf ("Got return code %d but expected %d\n", ret, events->ev); return; } if (ctx.pos != ctx.len) { print_fail(); printf ("Got %u events but expected %u\n", ctx.pos - 1, ctx.len - 1); return; } line_no = j65_get_line_number(&parser); if (line_no != events->line_no) { print_fail(); printf ("Final line number was %lu but expected %lu\n", line_no, events->line_no); return; } print_pass(); } static void depth_test (uint8_t specified, uint8_t expected) { uint8_t actual; snprintf (buf, sizeof (buf), "depth test (%u):", specified); printf ("%-18s", buf); j65_init (&parser, NULL, NULL, specified); actual = j65_get_max_depth (&parser); if (actual != expected) { print_fail (); printf ("Got %u but expected %u\n", actual, expected); } else { print_pass (); } } #define TEST(x) run_test (x, sizeof(x) / sizeof(x[0])) int main (int argc, char **argv) { int color; TEST(test00); TEST(test01); TEST(test02); TEST(test03); TEST(test04); TEST(test05); TEST(test06); TEST(test07); TEST(test08); TEST(test09); TEST(test10); TEST(test11); TEST(test12); TEST(test13); TEST(test14); TEST(test15); TEST(test16); TEST(test17); TEST(test18); TEST(test19); TEST(test20); TEST(test21); TEST(test22); TEST(test23); TEST(test24); TEST(test25); TEST(test26); TEST(test27); TEST(test28); TEST(test29); TEST(test30); TEST(test31); TEST(test32); TEST(test33); TEST(test34); TEST(test35); TEST(test36); TEST(test37); TEST(test38); TEST(test39); TEST(test40); TEST(test41); TEST(test42); TEST(test43); TEST(test44); TEST(test45); TEST(test46); TEST(test47); TEST(test48); depth_test (0, 224); depth_test (1, 1); depth_test (16, 16); depth_test (123, 123); depth_test (224, 224); depth_test (255, 224); if (failures > 0) color = 31; /* red */ else color = 32; /* green */ printf ("\033[%dm================================\033[0m\n", color); printf ("%d tests passed; %d tests failed\n", passes, failures); printf ("\033[%dm================================\033[0m\n", color); return failures; } ================================================ FILE: tools/README.md ================================================ This directory contains some scripts I used when making JSON65. You probably won't need them. ================================================ FILE: tools/asm-heading.hs ================================================ #!/usr/bin/env stack -- stack --resolver lts-12.6 --install-ghc runghc --package text {-# LANGUAGE OverloadedStrings #-} {- JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. -} import Control.Monad import qualified Data.Text as T import qualified Data.Text.IO as T import System.Environment width :: Int width = 70 main = do args <- getArgs forM_ args $ \arg -> do let title = T.pack arg semis = T.replicate width ";" T.putStrLn semis T.putStrLn $ ";;" <> T.center (width - 4) ' ' title <> ";;" T.putStrLn semis T.putStrLn "" ================================================ FILE: tools/check-endproc.pl ================================================ #!/usr/bin/perl -w # Script to check that the comment on the ".endproc" line matches # the name on the corresponding ".proc" line. use strict; my $name = undef; my $procLine = undef; my $exitCode = 0; while (<>) { chomp; if (/^\.proc\s+(\S+)/) { $name = $1; $procLine = $.; } elsif (/^.endproc/) { if (/;\s*(\S+)/) { my $endName = $1; if ($name ne $endName) { print STDERR "$ARGV: $endName on line $. but ", "$name on line $procLine\n"; $exitCode = 1; } } else { print STDERR "$ARGV: no name on line $. but ", "$name on line $procLine\n"; $exitCode = 1; } } } continue { close ARGV if eof; # Not eof()! } exit ($exitCode); ================================================ FILE: tools/convert_enums.pl ================================================ #!/usr/bin/perl -w # JSON65 - A JSON parser for the 6502 microprocessor. # # https://github.com/ppelleti/json65 # # Copyright © 2018 Patrick Pelletier # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. my $inenum = 0; sub hexify { my $arg = $_[0]; $arg =~ s/0x/\$/; $arg =~ s/(-\d+)/sprintf "\$%02x", $1 & 0xff/e; return $arg; } while (<>) { chomp; if (/^enum\s+(\w+)/) { print ";; $1\n"; print ".enum\n"; $inenum = 1; } elsif (/^\}\;/) { print ".endenum\n\n"; $inenum = 0; } elsif ($inenum) { s/^\s+(\w+\s*)(=?\s*[-\w]*),/"$1".hexify($2)/e; s%/\*(.*)\*/%;$1%; s/^\s*/ /; s/\s+$//; print $_, "\n"; } } ================================================ FILE: tools/debug.inc ================================================ ;; JSON65 - A JSON parser for the 6502 microprocessor. ;; ;; https://github.com/ppelleti/json65 ;; ;; Copyright © 2018 Patrick Pelletier ;; ;; This software is provided 'as-is', without any express or implied ;; warranty. In no event will the authors be held liable for any damages ;; arising from the use of this software. ;; ;; Permission is granted to anyone to use this software for any purpose, ;; including commercial applications, and to alter it and redistribute it ;; freely, subject to the following restrictions: ;; ;; 1. The origin of this software must not be misrepresented; you must not ;; claim that you wrote the original software. If you use this software ;; in a product, an acknowledgment in the product documentation would be ;; appreciated but is not required. ;; 2. Altered source versions must be plainly marked as such, and must not be ;; misrepresented as being the original software. ;; 3. This notice may not be removed or altered from any source distribution. .import pusha0 .import pushax .import _printf zp_bytes = 24 .data save0a: .byte 0 save0x: .byte 0 save_a: .byte 0 save_x: .byte 0 save_y: .byte 0 save_zp: .res zp_bytes .code .macro save_regs .local L php sta save_a stx save_x sty save_y ldy #zp_bytes-1 L: lda sreg,y sta save_zp,y dey bpl L .endmacro ; save_regs .macro restore_regs .local L ldy #zp_bytes-1 L: lda save_zp,y sta sreg,y dey bpl L ldy save_y ldx save_x lda save_a plp .endmacro ; restore_regs .macro print_str str .local S .data S: .asciiz str .code php sta save0a stx save0x lda #S jsr debug_str ldx save0x lda save0a plp .endmacro ; print_str .macro print_hex jsr debug_hex .endmacro ; print_hex .macro print_str_nl str print_str str jsr debug_nl .endmacro ; print_str_nl .macro print_str_hex str print_str str jsr debug_hex .endmacro ; print_str_hex .macro print_hex_nl jsr debug_hex jsr debug_nl .endmacro ; print_hex_nl .macro print_str_hex_nl str print_str str jsr debug_hex jsr debug_nl .endmacro ; print_str_hex_nl .macro print_nl jsr debug_nl .endmacro ; print_nl .macro print_long arg save_regs lda #percent_08lx jsr pushax lda arg+2 sta sreg lda arg+3 sta sreg+1 lda arg ldx arg+1 jsr pusheax ldy #6 jsr _printf restore_regs .endmacro ; print_log .data percent_08lx: .asciiz "[%08lx]" .code .macro print_word arg save_regs lda #percent_04x jsr pushax lda arg ldx arg+1 jsr pushax ldy #4 jsr _printf restore_regs .endmacro ; print_log .data percent_04x: .asciiz "[%04x]" .code .proc debug_str save_regs lda #percent_s jsr pushax lda save_a ldx save_x jsr pushax ldy #4 jsr _printf restore_regs rts .data percent_s: .asciiz "%s" .code .endproc ; debug_str .proc debug_hex save_regs lda #percent_x jsr pushax lda save_a jsr pusha0 ldy #4 jsr _printf restore_regs rts .data percent_x: .asciiz "%02X" .code .endproc ; debug_hex .proc debug_nl save_regs lda #newline jsr pushax ldy #2 jsr _printf restore_regs rts .data newline: .byte $0a, $00 .code .endproc ; debug_nl ================================================ FILE: tools/make_charprops.pl ================================================ #!/usr/bin/perl -w # JSON65 - A JSON parser for the 6502 microprocessor. # # https://github.com/ppelleti/json65 # # Copyright © 2018 Patrick Pelletier # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. my ($ws, $str, $lit, $int, $num) = qw(prop_ws prop_str prop_lit prop_int prop_num); my ($lsq, $lcur, $rsq, $rcur, $colon, $comma, $quote) = qw(sc_lsq sc_lcur sc_rsq sc_rcur sc_colon sc_comma sc_quote); my @props = (); for (my $i = 0; $i < 128; $i++) { push @props, []; } foreach my $c (0x20, 0x09, 0x0a, 0x0d) { push $props[$c], $ws; } for (my $c = 32; $c < 128; $c++) { push $props[$c], $str; } foreach my $c (split (//, "aeflnrstu")) { push $props[ord($c)], $lit; } foreach my $c (split (//, "-0123456789")) { push $props[ord($c)], $int; push $props[ord($c)], $num; } foreach my $c (split (//, ".eE+")) { push $props[ord($c)], $num; } push $props[ord("[")], $lsq; push $props[ord("{")], $lcur; push $props[ord("]")], $rsq; push $props[ord("}")], $rcur; push $props[ord(":")], $colon; push $props[ord(",")], $comma; push $props[ord("\"")], $quote; print "charprops:\n"; my $c = 0; foreach my $prop (@props) { print " .byte "; my $x = join ("|", @$prop); if ($x eq "") { $x = "0"; } printf "%-27s", $x; print "; "; if ($c > 32 and $c < 127) { print chr($c); } else { printf "%s%02x", '$', $c; } print "\n"; $c++; } ================================================ FILE: tools/make_dispatch.pl ================================================ #!/usr/bin/perl -w # JSON65 - A JSON parser for the 6502 microprocessor. # # https://github.com/ppelleti/json65 # # Copyright © 2018 Patrick Pelletier # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages # arising from the use of this software. # # Permission is granted to anyone to use this software for any purpose, # including commercial applications, and to alter it and redistribute it # freely, subject to the following restrictions: # # 1. The origin of this software must not be misrepresented; you must not # claim that you wrote the original software. If you use this software # in a product, an acknowledgment in the product documentation would be # appreciated but is not required. # 2. Altered source versions must be plainly marked as such, and must not be # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. use strict; my @schars = qw(dt_none dt_lsq dt_lcur dt_rsq dt_rcur dt_colon dt_comma dt_quote); my ($none, $lsq, $lcur, $rsq, $rcur, $colon, $comma, $quote) = @schars; my ($rdy, $rdy_ca, $key, $key_co, $ncolon, $ncomma_ca, $ncomma_co, $done) = (0, 1, 2, 3, 4, 5, 6, 7); my ($perr, $ichar) = qw(disp_parse_error disp_illegal_char); my ($xstr, $xcolon, $xcomma) = qw(disp_exp_string disp_exp_colon disp_exp_comma); my ($xobj, $xarr) = qw(disp_exp_obj_end disp_exp_array_end); my ($sobj, $eobj) = qw(disp_start_obj disp_end_obj); my ($sarr, $earr, $sstr) = qw(disp_start_array disp_end_array disp_start_string); my ($comarr, $comobj, $dcolon) = qw(disp_comma_array disp_comma_object disp_colon); my %table = (); my @default = ($perr, $perr, $perr, $perr, $perr, $perr, $perr, $perr); $default[$key] = $xstr; $default[$key_co] = $xstr; $default[$ncolon] = $xcolon; $default[$ncomma_ca] = $xcomma; $default[$ncomma_co] = $xcomma; foreach my $schar (@schars) { $table{$schar} = [@default]; } $table{$none} = [$ichar, $ichar, $ichar, $ichar, $ichar, $ichar, $ichar, $ichar]; foreach my $state ($rdy_ca, $ncomma_ca) { $table{$rcur}[$state] = $xarr; } foreach my $state ($key_co, $ncomma_co) { $table{$rsq}[$state] = $xobj; } foreach my $state ($rdy, $rdy_ca) { $table{$lsq}[$state] = $sarr; $table{$lcur}[$state] = $sobj; $table{$quote}[$state] = $sstr; } $table{$rsq}[$rdy_ca] = $earr; $table{$quote}[$key] = $sstr; $table{$quote}[$key_co] = $sstr; $table{$rcur}[$key_co] = $eobj; $table{$colon}[$ncolon] = $dcolon; $table{$comma}[$ncomma_ca] = $comarr; $table{$comma}[$ncomma_co] = $comobj; $table{$rsq}[$ncomma_ca] = $earr; $table{$rcur}[$ncomma_co] = $eobj; foreach my $schar (@schars) { printf ".define %-8s", $schar; my $sep = " "; foreach my $func (@{$table{$schar}}) { print $sep, $func, "-1"; $sep = ","; } print "\n"; } ================================================ FILE: tools/random-json.hs ================================================ #!/usr/bin/env stack -- stack --resolver lts-12.6 --install-ghc runghc --package MonadRandom {- JSON65 - A JSON parser for the 6502 microprocessor. https://github.com/ppelleti/json65 Copyright © 2018 Patrick Pelletier This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. -} import Control.Monad import Control.Monad.Random.Strict import Data.Char import Data.List import Text.Printf randomLiteral :: MonadRandom m => m String randomLiteral = do x <- getRandomR (0, 2) return $ ["null", "false", "true"] !! x randomInteger :: MonadRandom m => m String randomInteger = do x <- getRandomR (-4294967296, 4294967296) return $ show (x :: Integer) randomNumber :: MonadRandom m => m String randomNumber = do x <- getRandom return $ show (x :: Float) escapes :: [(Char, Char)] escapes = [ ('\"', '\"') , ('\\', '\\') , ('\b', 'b') , ('\f', 'f') , ('\n', 'n') , ('\r', 'r') , ('\t', 't') ] randomChar :: MonadRandom m => m String randomChar = do x <- getRandomR (1, 126) let c = chr x esc = c `lookup` escapes case esc of (Just c') -> return $ ['\\', c'] _ -> if x < 32 then return $ printf "\\u%04x" x else return [c] randomString :: MonadRandom m => m String randomString = do len <- getRandomR (0, 6) chars <- replicateM len randomChar return $ "\"" ++ concat chars ++ "\"" randomArray :: MonadRandom m => Int -> m String randomArray level = do len <- getRandomR (0, 5) values <- replicateM len (randomValue (level - 1)) return $ "[" ++ intercalate "," values ++ "]" mkPair :: String -> String -> String mkPair k v = k ++ ":" ++ v randomObject :: MonadRandom m => Int -> m String randomObject level = do len <- getRandomR (0, 5) keys <- replicateM len randomString values <- replicateM len (randomValue (level - 1)) let pairs = zipWith mkPair keys values return $ "{" ++ intercalate "," pairs ++ "}" randomValue :: MonadRandom m => Int -> m String randomValue level = do let highest = if level < 0 then 3 else 5 x <- getRandomR (0, highest) case x of 4 -> randomArray level 5 -> randomObject level _ -> [randomLiteral, randomInteger, randomNumber, randomString] !! x randomArrayOrObject :: MonadRandom m => Int -> m String randomArrayOrObject level = do x <- getRandom if x then randomArray level else randomObject level generateStuff :: Int -> IO () generateStuff level = do str <- randomArrayOrObject level when (length str < 2046) $ do putStrLn str generateStuff (level + 1) main :: IO () main = generateStuff 0