Full Code of ppelleti/json65 for AI

master 878d295f1d51 cached
43 files
196.7 KB
60.3k tokens
36 symbols
1 requests
Download .txt
Showing preview only (208K chars total). Download the full file or copy to clipboard to get everything.
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 <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <cc65.h>

#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 (<F>) {
        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 <errno.h>
#include <string.h>
#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 <stdint.h>
#include <stdio.h>

#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 <stdbool.h>
#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 <stdio.h>
#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 <stdio.h>

/*
  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
        ldx #>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 <stdint.h>

/*
  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 <stdbool.h>
#include <stdlib.h>             /* 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 <stdint.h>
#include <stddef.h>             /* 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
        ldx #>str_null
        ldy #3
        jsr compare_strings
        beq got_null
        lda #<str_true
        ldx #>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
        ldx #>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 <conio.h>

#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 <string.h>
#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,"<gZP\u001c\u001f":-8960435077329510243,"szu":0.6044294}
{"&.\tV":"a","N":-17694422251758191599,"{\u001c^\u000b":"&=\u0016.K}","\u001f}~~[\t":13764838241451311043,"b\u0010)!s\u0013":4115161054381493887,"pE;\u0015B7":false,"}":"\u0003\u0002H\f@`","ocV>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,["\u0004</e:","f;5\b]",0.13011688,"-\u0016{L^r"]]
[false,"*Z"]
{"":-14448800448673676895,"@y\u001aq}\u0002":0.11866385,"K4V;\u0014":[["w",0.2579152,"M","",8313590187149240626],"$>h",["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,"\u0016<Z\u001a=":{"\nX":{},"CG":"^:s","\u00129\u0011z,":"2\u0007w\u0002D","\u001c/":14555262098322473384,"":-5946533795406649298},"\\<A":17937647869425473647,"4\u0010:X":{}},"r":0.89700264,"zal\u0001":[]},"":"\\\u000e\u0014","V":{"nn":[],"@\u0019|T":"R\u0006"},"u{<.5":["U\b\u0007s~",{"W)":false,"v\f$Wi":-17612115454251660451,"G\u0006\u0019\u0003mk":-9458453112674665176,"Y\u0001r":{"":{"Z*\u001a\u0015_i":"_6\u001c~\"}","\"4":{"":0.3258859,"\u000fo[RL":2738684748814013921,".":0.5259006,"f":0.28348464},"\u0019\u001a}":0.9390163,"g- ":["1gfy4\u000b",0.13463932,"\tv",false],"m-9\u001e":{",b\u0006":0.35202783}},"9j!":0.6527836}},"m\u000eF",15401944105642734126,0.100584686]},"":{"=":{" @\u0018[\u0013":-4798237755001598196,"qZ\b0":"o\f\u000e"},"2s":false}},[-17026305678075551850]]
{"=u_":0.10595447,"e86|?":16584676174349563655,"Y\f\ni":true,"\t":[]}
{"Y\u000f\u0007":null}
{"a:kK\u00060":"=:$&)\u0015","w\u001f\n\u0016f'":{"\n!&":"\u000eUl,o%","BD":11168338163327932912,"aUN->O":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":["<l",0.21901178],"\u001b+mK\u0001\u0002":{"\u001b\bt":"","V\u0014\u0003\u001dG\u0005":0.6024258,"":0.6851842},"U=A":-14471741596704654577},["",0.59391403]],{},"u"]],"":false},"":11271894647333079509,"4)}":12978190940517445918}
[[{},{"\u0001^[":0.6274655,"s\ru":-16285590633424424013,"\u0005_#":-2902719902988987317},4.6498835e-2,[false,"vP"],0.4098791],null,"ccJ]",{"x~M8\u000b":"Kl\nrl@","L":0.5812448,"":[12621578391773795701,-14768389065015598213],"\u0004R\u001b\u0011\u0018-":null}]
[null]
{"9\u000e":"v}NV","~\u0016t":null}
{"":5.3502917e-2,"":0.7454183,"\u001aq~":[]}
{"]":null,"D":"l0L","~*uN":[],"":9954274599782580648}
{"":0.9339974,"\u0013":4739434292161533768,"/%3yF'":4208093120411332727,"\u0012V+Im":{"~PUs\u00151":0.4505244,"o\u0015\u0006":"!C","(^":false,"":0.7402246,"c\buX":[["\u001cQ[%>","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<k1":0.5800585}
[0.86506915,0.745442,[[{"":"","\u001far":16119537991642233069},{"":[null],"Iz":false,"\u001e":"URMA[$"},0.5963315],[],"5.rW\u0017",{"P":-10068242471433579683," \u0013{f\u000e":0.392839,"Y\b\u0005Z":"","\u001f":[],"#p":0.24786073}],0.38616395,[]]
["4T1\r@",0.46275347,false]
{"hly'l":true}
{"":[false],"":18311605067220884321,"\\0":{"J#\u0011":"\b","-":0.48263913,"":[]}}
[0.9280277,"P"]
[true,{"G\\vm":true,"7<{\u0013m@":""}]
{"b":-17176440223338641234,"{~#1":"d","T&":0.3095814}
[["5w\u0012Y\n",-3465947354667783786,true],0.58169204]
{"\u0007#\u001eSf":true,"o@i":{"\u0003P\u0007r!~":{},"\u0019bx5":-5870705663361752808,"<T'\u0012\t":-8253887455420531740,"v-G\n%b":17303148683879179163,"D0":[12701047572705528383,"P^cW\u001d",{"X":"g\u0016s","":"k[\"","Cyz\n\u0004":0.2698384},{"VE":0.10767931,"\\cH,?":{"l":"B\u0019\bd ","B$\\":null},">-#^":{}}]},"^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%<o":"/e)I$","\t":[]}},0.15512782],true],"Y}GvcE":13488126456154209821,"FM}I":true,"<1fb(":{",\u0003@gV":false,"FYC-":"/\n\u001bb","#\b":true}},{"m*4\u000eH":"&\u0018$\u001a9\r","k":{"\nTWm":0.88612574,"\u0005(^\u001a\f\u001c":true,"l]e":false,"\u0006q":true}},true,"d\u0017U4",{}],{"nU[":[10911731097193658029,0.6481947],"":0.5755147,"\t":12465806549772274316,"\u001dzK#?$":{"q":-10484987948483073532}},"k%d`&",3044966341192108219],"Uk\u0010L",{"\fg":false,"o":16369525926792919602,"(\u0004V\"[\t":"-K"},"Y","\u001e[E+"]
{"5\u0002\f":"#&5t*","b{6\u001a":-15275213607943662689,"Y;":17265934196169125293,"\nQlCs9":null,"\u0018(:":true}
[{"|":0.68765,"\u001eISgI\u0003":[false,"CM=\u0006\u00039"],"\u0014x":{"4),":[0.35985446]},"F-\u0013":false,"gkH\u0001":[true,"a.'}&j"]},false,{"\u0015\"F6":[[[],[7.6780796e-2,{"nJ":{},"\u0011kgB":[0.8854914,[true,":P",["6B&M",[[0.9121036,"\f\u00158",15364012209050718127,[true,"\r5\u000e"]]]],"mtWhem",-8179481715501543552]],"D$X&R@":2255881672456517052}],{"":"'NF~\u000eG","":[0.26298237,7752763682448092000,"C",true],"Qx$\u0005(@":null,"/":"","e\u001e{\u000e":"2"},true],"\u0006KANq\u000f"],"5.\u0004\u001f":14849978413013421257},3.3628285e-2]
{"r;s":-6593165038148587118}
[-8006884604484209542]
{"F'R&wn":0.82659614}
{"suU\u001f":0.8861345,"A\u0007rK":[{},[-6776522806699827829,-10267898355349287368],"",false],"\u001fu4NH":-2216197903302261055}
{"\u001bZ`":0.66861683,"":0.48398995,"V":0.30915624,"+":-1026948343628585707,"y:\u0005X":0.2573502}
{"{~":"","JV1\t(B":",N\u0018|_"}
[true,[],4.1036606e-2]
[0.73778456,{"/_E":[false],"s":0.81533575},"!?9dP",true,false]
{"":["i"],"\u0018Lof":{},"\u0014)d33)":[1705660862319921361,[0.70245653,[{},false,-4997087011668413281,0.70262194]]],"T":0.22172987,"\u001e":{"q":null,"\ncW\u0016&f":0.66210586}}
{"X$N":{},"":{",jkv\u00058":5185843031279423760,"l#\u001bm\t\f":"","\u001biD5\u0006":-9351375925373681315,"z\u000f))\u001e@":"\u001b3\u001bH"},"` f":{"\u00112Kf\r":null,"\tg1":[[null,false],[{},{"":-10702680099945297361,"`\u001ci.[":{"$@^k":{},"":697936244851400800,"6_z":"","RF\u0001v\u0005":false,"?":null},"@\t":"\u000b\u0005","":null},"@\u0017",-17783375405486191619],9136462765999751505],"i\u0016\"":10064203520377885526},"\bC":{"GG7c!":0.51944315,"m$g+\u0001\u0007":{"!\\B%!\u001a":"J\u000b\u0018","":[[0.5990416,16979263451032275490],[[-3319032353105603835]],0.8669717],"V\u001bZ9\u00078":{},"#}\u000e\u0015\u0010,":-11662685099556867956,"\bv":12939561448974367640},"\u000b\u0015@w_I":[[{"":{},"`5\u001a\u001c*H":{"XVcrv\u0012":"$",".`\u0010":1.0253668e-2,"&":-7969014040597734301,"Ae":0.68151945},"":{"q[_f+":{"W`$g":"\r&^\u0005","TjUUpg":"9"},"vE\u00115\fW":["&_",false,12477136470711026981]},"\u000f\r?\u0011{":15775777991788307493,"T\u001f6":0.36692625},{},{},{"\u001f:\u000eoH":{"VoZ\u001bP2":{"n\t\u0005>\u0011|":false,"G":[],"":"\u000b*!d","i8\u0003d":[],"X!N>":true}},"\u0014":0.7147376,"2<Z,":0.6847712,"=O":"\\J\u0015E","`\u001b[\u0016;k":null}],-16943125239715684542]},"F&Ok:":{"I1@~V":false}}
[["+\u000eMq\u0015n","0-Z\u0011"],{},0.19048035,{}]
{"":0.17463982,"B}O\u0006h":"j\u0016\u0016;$","v":false,"{q\\":0.1645158}
[[{},-10335304945929474818],"",[]]
[{"":11011572605828908966,"D\\y":8.9524806e-2},0.12063557,null]
[[{"[ F":[],"\fZ\u001a+#\u0012":0.20774364,"@u":14140938735167775918,"x\\":[-11016694664242453733,true,0.7662049,null],"f&\u0012":10041475338386091190}],[""]]
[{"p\u0006b\u001cK":null},{"":[[".\u0016\u000ex`j",-6879987747836866833,10223230372432915459,{"4O":1112402400137721488,"iCH\r":-11294462142224825198," k^":0.20589554},[8573083530212052064,"W37/d\t",""]],0.7530455]}]
[0.66232306]
{"G":null,"C":{")UT}N":0.5103738,"Jv\u001dy\u000bQ":"5H~3","\u001d~\u0016\u0015":true,"\u001f\u0001?(\u0017s":2473312665277409295,"|#m":false}}
[[{"_)":{"o":-12316865322655154751,"":{},"4rw-f":"c","\\J~":{"_nu\u000e":[9.66655e-2,-14195996720476945681],"I]\f~Ai":4.922563e-2," y":0.92399687,"T\u0005":null}}},{"6S^eEB":null},0.67890656,""],null,true,"\u000bx|",0.40480417]
[{"":"<\u001b\u001dNr"},3.2110214e-3,false,null,0.945265]
[{"y=b":{"57\u0006":{"3I":0.36540532,"R;*":"e0-R\u001e"}},"":{"\u001b6I\"":true,")t\u001e":false},"\u000e:~huo":0.90902025,"":[0.7344798,false]},[],[null,0.37931484]]
{"U":null,"='!n)]":null,"/B":4091446843733087751,"R(fu":{"xe>2":[["(",false,{"":0.42793614}]]},"-1\u0015z\u0005\u0011":false}
["y\u0007&I$",false]
[-13516489482431253430,"cVx"]
[{"\u0007\u0006g\u001c":-8554119748749916417},"\u001ch$SJ\u0014",{"Fh\u00050L7":[]},{"B":"\u0010T","\u0015>[c u":{"":"M3X"},"\u000f\u0004tFu~":0.36826915},-4617804547813974534]
{"\u00135_\u0014|y":13469420637207143766,"f01":0.38382834,"Zb":{"2z":true,"-oWAq":-17693981350083080169,"\u0015":-16142200838510853908},"":[-11453326168044549327,true,"W3Z+"]}
[-11153910131317761898,"\u001ch",[16314294140409942272,{},0.93278205,11399627290119531131],[]]
{"\u000e/\u0007Q\"\u0012":{"&b!0\f\u001e":null,"\u0016,C":10027704590075874049,"mRQ":"(\u000fb\u0019","":true},"\u001al1Tt":0.9605522,"":"+35Y","":12330351478004686954,"":{"":0.80495906,"E":0.68375826,"\fg":0.42994535}}
{"\u0010-~":[]}
{"O\u0010\u001fW":{"\t-cpRB":-1207104230604966545,"&#":false,"K<":{},",e":-3243935909847784433,"=\t\u00039$N":false},"K\"ZA":[false]}
[8330608987917035325,18104787239307741033]
{"8!F~}L":"kvA|w\u0017","{":{}}
{"]j\u0013]":13135044173561850680}
{"[":-14572228672651219669,"":null,"\"\b\u001a\n\u0017":[0.92823654,{"-z\u0010\u001f\u0004":7090716430674012613,"\u001dH8t":"","":-12005360874135090142,"\u0015K\u0014,{>":["\u0004DUO\u0012"]},"\bo",[0.19997853,false,0.44237286]],"-\u001dd":[-10911830261830972153,{"y\u000e'EI\u0004":"-vu","=$1":{"6-)`\r":"&J","\u001a<)\u0015V":{},"m\u001d":"|M"},"je\u0006":[],"\u0005BuM\u0019\n":{"":1782142678781591223,"8`\u0005\u001c\u0017":false,"b":"@4"},"\u0016m":"H"},{"E":17391435708081682382,"VH\u001b!":[-9580877567575004549],"Q\u000f":[5371389913356269173],"Aq :":"it"},-15750828222741139551]}
["$\u0005",0.3907426]
[8679419118201241337,0.19923568,0.668362,[null,0.44885856,-6773739461166169741,true],[]]
[0.4405555,{"":true,"\u0002&6O":-5924290840962216166,"\r\u0017\u0014C":4.2134523e-3}]
[0.9339055,""]
{"\u000f_":{"|^&m":[],"e\u0004z":{"\u0006+":true,"fb":"<W","<\u00184\u0019":"v\u001aZ","\bP\u0003\u001e":null,"\u0019":"l"},"\u001f":"Nr<yY"},"":"H\u001c"}
{"":"DO"}
[17256012902499945094]
{"\tR7":{"l\u000e":[],"\t\u0013Rx\u0011h":null,"wP":[14547570528401522133,14415130777834825591,{"":[3.6435902e-2]},"S\"g"],"\r\u001e\u0017":"1Tb#\u0016","\u001c1[\"":16344739751689581814}}
{" \u0002\bi":false,"Jh":[[0.33667308,-17369336385787834625,"\u0003ar","(\u0012!",[[true]]],-1396302974865016530,6.0578644e-2],"":"d\u0003j\u000f"}
{"":-8991839176212064162,"\u000f":0.87215024,"99q\u001b\u0013,":10827064753934197247,"$v7;":null,"\u0017pb":false}
{"-TgWQ":0.34665275,"J":{"dp":-9189006637028301388,"-":[{"\u0014\u001a\u0013":null},0.5770299]},"aC\b\u0016\u0001":{"V8\u001cz":{"w5*+9":false,"DV+Trn":{"ss":true}},"_Ln\u00025`":[0.91180235],"[\u0003~\f":4416088078877825503,"3\u001a_$0h":-8749706514808019600},"":"","2n*0#'":5896380210604322024}
{"jVZf.\u001d":[4570204019026784438,null,[],["9",null,{"A\u0005ZI":13260052419541173374,"LdW^O\u0002":true,"FeIw&":[0.89395565,{"'":{},"":[true],"\f\t_":672387497269769344,"\u0010Sr":-2157951215979429253},null,[[false,0.80883986,[],"Q"]],0.9129004],"\u0017\u0019XT":0.57672817,"r":"\u001a"},"",[true,[{"*Cmd~":-4798229312441466626,"pX#'nY":false,".\u0015i$U":[true,-14938229482389367753,true]},{"aF;_":0.79151475,"\u0012wOX\u0006}":"\u001cx2\u000f","UmK":1736833851845507255,"":{},"gtA7C":"\u0017\b\u0002"},[]],"`6\"9[;",-6877653363415966808,"\n"]],[[["?\u0010Xv",17795186868318363551,false,-11569501727506071697],[null],0.7076068,";2S","yTgS"],["T\b",11406439396583882123],null]],"\u000b{\bYJ":[[],{"=@Y(D":[-2689466064178754073,-4610455999244253621],"\u001c`6od":4005483234178880618},{"LXWddO":0.34575886,"t>y(":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`",["<z\u00163",[0.6074513,{"":0.9845493,"\u0019l>\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\u001d<Y":0.63055354,"\u0010":-12060799814929459901},0.16415578]],"*"],"":2607220307768334,"":null,"`@\u000b\u001b\u000b":11682738680454146932},["v\u0012!zI",false,null,null],true,"\u00183",["!\u0011Dy<p",0.6951675]],1.2881577e-2,9.184706e-2,[null,["/#6:%"],"\u0011.jR[y","K;\u0002nfq",null]]},"p~\u0015":11565752727145742707,"#.\rIy":14150557114163512681},true],"T:D\u0015",{"FbHagB":[{"r\u001e^":0.9365841,"k9`q":{},"ia9":-6722268436164996956,"q\u0014JV5T":-1173478293511733138,"\u001cD*{>h":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<m":null,"\u001d\fT2I\u0012":{"":[],"":435778865},"/]":0.29311603,"p\u000b\b":"\u0004t","k":"xFe1"},"\u000f\u0007V":4152148563,"\u000eF%":-1627699888}]
{"a\u0013\fE":false,"\u000e+*d\u000e":605395782,"/\u0004q\u0014\"":"Q:\u0016\u001f","x$fJ":{}}
[["9"],true,0.43510032,{"`AzY":{"":false,"zv":null,"mRI\u0011":[true,[[3883676960]]],"DU\u0002":"SCC~T"}}]
[[-3290883344,0.54075867,0.7522493,null,null],"\r\u000f\u0015[",2429340856]
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":{"q":{"r":{"s":{"t":{"u":{"v":{"w":{"x":{"y":{"z":{"a":{"b":{"c":{"d":{"e":{"f":{"g":{"h":{"i":{"j":{"k":{"l":{"m":{"n":{"o":{"p":true}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}


================================================
FILE: tests/test-quote.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 "json65-quote.h"

static void do_test (const char *s) {
    fputc ('\"', stdout);
    j65_print_escaped (s, stdout);
    fputs ("\"\n", stdout);
}

int main (int argc, char **argv) {
    do_test ("Hello, World!");
    do_test ("Hello, World!\n");
    do_test ("Hello,\r\nWorld!");
    do_test ("Hello, \"World!\"");
    do_test ("\aHello, World!");
    do_test ("Backslash \\");
    do_test ("Hello,\tWorld!");
    do_test ("\001\002\003");

    return 0;
}


================================================
FILE: tests/test-string.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 <stdio.h>
#include <string.h>
#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 <stdio.h>
#include <string.h>
#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 <stdio.h>
#include <string.h>
#include <json65.h>

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
        ldx #>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
        ldx #>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
        ldx #>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
        ldx #>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
        ldx #>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
        ldx #>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
Download .txt
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
Download .txt
SYMBOL INDEX (36 symbols across 13 files)

FILE: examples/example.c
  function my_exit (line 39) | static void my_exit (int code) __attribute__ ((noreturn)) {
  function main (line 47) | int main (int argc, char **argv) {

FILE: src/json65-file.c
  function j65_parse_file (line 46) | int8_t __fastcall__ j65_parse_file (FILE *f,
  function j65_default_err_func (line 157) | void __fastcall__ j65_default_err_func (FILE *err,

FILE: src/json65-print.c
  function j65_print_tree (line 36) | int __fastcall__ j65_print_tree (j65_node *root, FILE *f) {

FILE: src/json65-string.h
  type j65_strings (line 59) | typedef struct {

FILE: src/json65-tree.c
  type j65_tree_internal (line 29) | typedef struct {
  function j65_init_tree (line 36) | void __fastcall__ j65_init_tree (j65_tree *t) {
  function j65_tree_callback (line 44) | int8_t __fastcall__ j65_tree_callback (j65_parser *p, uint8_t event) {
  function j65_free_tree (line 158) | void __fastcall__ j65_free_tree (j65_tree *t) {

FILE: src/json65-tree.h
  type j65_source_location (line 52) | typedef struct {
  type j65_node (line 58) | typedef struct j65_node j65_node;
  type j65_node (line 86) | struct j65_node {
  type j65_tree (line 117) | typedef struct {

FILE: src/json65.h
  type j65_event (line 37) | enum j65_event {
  type j65_status (line 76) | enum j65_status {
  type j65_parser (line 99) | typedef struct {

FILE: tests/test-file.c
  function errfunc (line 32) | static void errfunc (FILE *err, void *ctx, int8_t status) {
  function callback (line 36) | static int8_t callback (j65_parser *p, uint8_t event) {
  function main (line 40) | int main (int argc, char **argv) {

FILE: tests/test-print.c
  function do_test (line 35) | static int do_test (void) {
  function main (line 88) | int main (int argc, char **argv) {

FILE: tests/test-quote.c
  function do_test (line 27) | static void do_test (const char *s) {
  function main (line 33) | int main (int argc, char **argv) {

FILE: tests/test-string.c
  function print_bucket_usage (line 36) | static void print_bucket_usage (void) {
  function main (line 52) | int main (int argc, char **argv) {

FILE: tests/test-tree.c
  function do_test (line 34) | static int do_test (size_t len) {
  function main (line 102) | int main (int argc, char **argv) {

FILE: tests/test.c
  type event_check (line 35) | typedef struct {
  type my_context (line 43) | typedef struct {
  function print_pass (line 351) | static void print_pass (void) {
  function print_fail (line 356) | static void print_fail (void) {
  function callback (line 361) | static int8_t callback (j65_parser *p, uint8_t event) {
  function run_test (line 444) | static void run_test (const event_check *events, size_t len) {
  function depth_test (line 488) | static void depth_test (uint8_t specified, uint8_t expected) {
  function main (line 507) | int main (int argc, char **argv) {
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (215K chars).
[
  {
    "path": ".gitignore",
    "chars": 111,
    "preview": "*~\n*.o\n*.map\n*.tmp\ntest\ntest-string\ntest-tree\ntest-quote\ntest-print\ntestfile.system\ntestfile.po\nexample.system\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 855,
    "preview": "Copyright © 2018 Patrick Pelletier\n\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no e"
  },
  {
    "path": "README.md",
    "chars": 9357,
    "preview": "I was watching TV, and there was a commercial which proclaimed, \"It's\ntime to do what *you* want!\"  I replied to the TV,"
  },
  {
    "path": "examples/example.c",
    "chars": 4391,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "examples/input.json",
    "chars": 147,
    "preview": "{\n    \"apple\": 2,\n    \"banana\": {\n        \"color\": \"yellow\",\n        \"edible\": true,\n        \"sieverts\": 9.82e-8\n    },\n"
  },
  {
    "path": "run-tests.pl",
    "chars": 5832,
    "preview": "#!/usr/bin/perl -w\n\n# JSON65 - A JSON parser for the 6502 microprocessor.\n#\n# https://github.com/ppelleti/json65\n#\n# Cop"
  },
  {
    "path": "src/json65-file.c",
    "chars": 5196,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-file.h",
    "chars": 5573,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-print.c",
    "chars": 4007,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-print.h",
    "chars": 1378,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-quote.h",
    "chars": 1377,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-quote.s",
    "chars": 5012,
    "preview": ";; JSON65 - A JSON parser for the 6502 microprocessor.\n;;\n;; https://github.com/ppelleti/json65\n;;\n;; Copyright © 2018 P"
  },
  {
    "path": "src/json65-string.h",
    "chars": 3362,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-string.s",
    "chars": 6567,
    "preview": ";; JSON65 - A JSON parser for the 6502 microprocessor.\n;;\n;; https://github.com/ppelleti/json65\n;;\n;; Copyright © 2018 P"
  },
  {
    "path": "src/json65-tree.c",
    "chars": 5268,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65-tree.h",
    "chars": 5841,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65.h",
    "chars": 11342,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "src/json65.s",
    "chars": 47695,
    "preview": ";; JSON65 - A JSON parser for the 6502 microprocessor.\n;;\n;; https://github.com/ppelleti/json65\n;;\n;; Copyright © 2018 P"
  },
  {
    "path": "tests/create-testfile-disk-image.pl",
    "chars": 2193,
    "preview": "#!/usr/bin/perl -w\n\n# JSON65 - A JSON parser for the 6502 microprocessor.\n#\n# https://github.com/ppelleti/json65\n#\n# Cop"
  },
  {
    "path": "tests/file00.json",
    "chars": 21,
    "preview": "{ \"hello\", \"error\" }\n"
  },
  {
    "path": "tests/file01.json",
    "chars": 23,
    "preview": "{ \"mismatched\": true ]\n"
  },
  {
    "path": "tests/file02.json",
    "chars": 16,
    "preview": "{ null: \"bad\" }\n"
  },
  {
    "path": "tests/file03.json",
    "chars": 18,
    "preview": "{ \"error\": $foo }\n"
  },
  {
    "path": "tests/file04.json",
    "chars": 44,
    "preview": "[[[[[[[[[[[[[[[[[\"nesting\"]]]]]]]]]]]]]]]]]\n"
  },
  {
    "path": "tests/file05.json",
    "chars": 57,
    "preview": "{\n    \"multiple\": 1,\n    \"lines\": 2,\n    \"trouble\":: 3\n}\n"
  },
  {
    "path": "tests/file06.json",
    "chars": 40,
    "preview": "[\n    \"an\",\n    \"extra\",\n    \"comma\",\n]\n"
  },
  {
    "path": "tests/file07.json",
    "chars": 263,
    "preview": "[ \"a string which is way too long: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!0123456789abcdefghijk"
  },
  {
    "path": "tests/test-file.c",
    "chars": 1873,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "tests/test-print.c",
    "chars": 2748,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "tests/test-print.json",
    "chars": 27828,
    "preview": "[\"Hello\",\"World!\"]\n{\"foo\":\"bar\",\"nested\":[1,2,3]}\n\"Just a string\"\n5\nfalse\n[0.32572436,true,-11186217319148845949,true,0."
  },
  {
    "path": "tests/test-quote.c",
    "chars": 1450,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "tests/test-string.c",
    "chars": 2484,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "tests/test-tree.c",
    "chars": 3400,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "tests/test-tree.json",
    "chars": 373,
    "preview": "{\n    \"listen\": [\"127.0.0.1\", 7890],\n    \"verbose\": true,\n\n    \"color\": {\n        \"gamma\": 2.5,\n        \"whitepoint\": [1"
  },
  {
    "path": "tests/test.c",
    "chars": 18568,
    "preview": "/*\n  JSON65 - A JSON parser for the 6502 microprocessor.\n\n  https://github.com/ppelleti/json65\n\n  Copyright © 2018 Patri"
  },
  {
    "path": "tools/README.md",
    "chars": 94,
    "preview": "This directory contains some scripts I used when making JSON65.\nYou probably won't need them.\n"
  },
  {
    "path": "tools/asm-heading.hs",
    "chars": 1489,
    "preview": "#!/usr/bin/env stack\n-- stack --resolver lts-12.6 --install-ghc runghc --package text\n\n{-# LANGUAGE OverloadedStrings #-"
  },
  {
    "path": "tools/check-endproc.pl",
    "chars": 863,
    "preview": "#!/usr/bin/perl -w\n\n# Script to check that the comment on the \".endproc\" line matches\n# the name on the corresponding \"."
  },
  {
    "path": "tools/convert_enums.pl",
    "chars": 1527,
    "preview": "#!/usr/bin/perl -w\n\n# JSON65 - A JSON parser for the 6502 microprocessor.\n#\n# https://github.com/ppelleti/json65\n#\n# Cop"
  },
  {
    "path": "tools/debug.inc",
    "chars": 4207,
    "preview": ";; JSON65 - A JSON parser for the 6502 microprocessor.\n;;\n;; https://github.com/ppelleti/json65\n;;\n;; Copyright © 2018 P"
  },
  {
    "path": "tools/make_charprops.pl",
    "chars": 2237,
    "preview": "#!/usr/bin/perl -w\n\n# JSON65 - A JSON parser for the 6502 microprocessor.\n#\n# https://github.com/ppelleti/json65\n#\n# Cop"
  },
  {
    "path": "tools/make_dispatch.pl",
    "chars": 2939,
    "preview": "#!/usr/bin/perl -w\n\n# JSON65 - A JSON parser for the 6502 microprocessor.\n#\n# https://github.com/ppelleti/json65\n#\n# Cop"
  },
  {
    "path": "tools/random-json.hs",
    "chars": 3347,
    "preview": "#!/usr/bin/env stack\n-- stack --resolver lts-12.6 --install-ghc runghc --package MonadRandom\n\n{-\n  JSON65 - A JSON parse"
  }
]

About this extraction

This page contains the full source code of the ppelleti/json65 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (196.7 KB), approximately 60.3k tokens, and a symbol index with 36 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!