HTTPS SSH

6502 JSON Parser

The JSON object parser library in 6502 assembly.

Contents

  1. About.

  2. Installation.

  3. Usage.

  4. Index of functions.

  5. License.

1. About.

6502 JSON Parser is a library created to parse valid JSON objects, written in 6502 assembly for Commodore 64, or
any other 6502/6510 8-bit machine. It was written in 64tass cross-assembler (v1.53.1515) and is fully unit tested with
c64unit test framework.

Where can it be useful? One of its use cases, and the reason why this library has been created, is that it provides
an elegant solution to request data from APIs. In API world, especially REST API world, JSON format
is one of the most common, lightweight and human friendly format available, and it will probably stay around
for another decade. Because of that, I believe that supporting Commodore 64 and other 8-bit machines with
the versatile 6502 JSON Parser will bring new ideas, and actively help to develop modern software for
our beloved, small, but still in some way powerful machines.

You could easily use this JSON parser with http64 library written by Doc Bacardi which allows to make requests
over HTTP. You can find source code here: http://csdb.dk/release/?id=14611.
http64 library is used in HTTP-Load, and Singular Browser, and works perfectly with RR-Net ethernet
cartridge, or other devices.

You can also use JSON parser to organize your data, or to keep your configuration on the disk or cartridge -
or anything else you wish for!

Features:

  • any given length of JSON can be parsed, it also applies to value length
  • static or stream parsing mode: JSON object can be downloaded at once before parsing, or can be parsed from open stream
    which can be useful for heavy responses
  • with just one line of code, it's an easy way to store array of strings, or integers in a table,
    without memory constraints
  • escape characters are supported for keys, and values (e.g. quotes escaped by backslash character)
  • option to convert ASCII characters to PETSCII, so they can be displayed on screen straight away
  • possibility to store not only values, but also keys (including length of these values and keys)
  • no zero-page addresses occupied (though it's configurable to do so), so you don't have to care about reserving one,
    and avoiding variable collisions
  • keys length is limited but configurable (up to 256 bytes)
  • nesting level of JSON elements up to 256 levels

2. Installation.

To add 6502 JSON Parser to your project, just copy install/6502-json-parser-dependency.bat (Windows), or
install/6502-json-parser-dependency.sh (Linux, Mac) script file from this repository to your main project folder.
Execution of the script file creates vendor/6502-json-parser folder and clones this repository using Git version
control system. It's recommended to list vendor folder in .gitignore file if you're using Git.

Then, in your code, just include the library, e.g.:

.include "../vendor/6502-json-parser/src/json-parser.asm"

3. Usage.

The best way to learn how to use a library is through examples. You'll find 6502 JSON Parser case-specific examples
in unit tests in tests/test-cases folder.

However, a few words of explanation will show how flexible the 6502 JSON Parser is, and that it had been designed
with simplicity in mind.

Static JSON object mode

First of all, you have to specify the memory location of a valid, full JSON object, e.g.:

setJsonObject myJsonObject

or, e.g. with fixed memory location:

setJsonObject $8000

This way the library will know where to start parsing. We don't need to specify end memory location of JSON object,
as the parser assumes that a JSON object is valid, in other words, that the number of opening and closing brackets
is equal.

Streamed JSON object mode

To parse JSON object from opened stream, you have to use setStreamedJsonObject method instead of setJsonObject, and
write your own implementation for retrieving data, and for reading new data from stream. It can only be done by overriding two
methods from the library: JsonParser.getByte which loads the current byte, and JsonParser.walkForward which buffers and reads
data from stream.

Note: If Y register is used in these methods, you have to save it, as JSON Parser uses it as well.
See tests/test-suite-stream-object.asm for reference.

Parsing

The main idea behind the parser is level nesting. Assuming that a JSON object has a few levels,
you have to specify what the parser should expect on each level, and perform appropriate action for particular case.

Let's discuss simple JSON object like this one:

{  
   "first_key":{  
      "colour":6
   },
   "second_key":[  
      "red",
      "green"
   ]
}

On the first level of this object, you can expect two nested objects, with keys first_key and second_key:

-
    expectJsonKey "first_key", firstKeyIterator
    expectJsonKey "second_key", secondKeyIterator

    isJsonObjectCompleted
    bne -
    rts

In this example, expectJsonKey function is going to look for occurrences of first_key, or second_key, and then
it will jump to corresponding label firstKeyIterator, or secondKeyIterator where further actions
need to be performed.

The order of keys appearance doesn't matter here as isJsonObjectCompleted is going to iterate all elements found.
Also, some keys can be just ignored if we don't want to get data from these elements.

Then, the "iterators" will perform further actions on the next level.
firstKeyIterator is going to parse an object, where colour key is expected:

firstKeyIterator

    expectJsonKey "colour", colourIterator

    isJsonObjectCompleted
    bne firstKeyIterator
    rts

And secondKeyIterator contains an array of strings, so we can get all values, and save them in memory with
offset of 16 bytes (as an example) for each data value, which means that the next value will be added on the next
16 bytes:

secondKeyIterator

    storeJsonValue coloursTableLocation, 16

    isJsonArrayCompleted
    bne secondKeyIterator
    rts

Note that we've used isJsonArrayCompleted here instead of isJsonObjectCompleted, as parsing in this case
is executed on array, not on an object.

coloursTableLocation is a pointer (LSB, set in any memory location including zero-page) to table location
where data will be set, e.g.:

coloursTable
    .fill 16 * 10, 0 ; we're expecting maximum of 10 strings, 16 bytes each

coloursTableLocation
    .word coloursTable ; we can point to table location statically if we're going to parse JSON only once

In the example above, coloursTableLocation can be set statically to point to the table, but you must be aware that
if you're going to parse JSON again, this pointer will continue incrementing. If you want to put data in the same memory
location as before, you need to reset the pointer to this table, so it makes more sense just to set it on each run with
setJsonOutputMemoryLocation method, e.g.:

setJsonOutputMemoryLocation coloursTableLocation, coloursTable ; set pointers just before the start to parse JSON

...

coloursTableLocation
    .word 0 ; in this case it doesn't matter what the value is, as setJsonOutputMemoryLocation will set it automatically

In this case, table will be overwritten with new data, so YOU have to take care to clear this memory location
beforehand. Otherwise you can have data corruption.

We've almost finished. The last part which needs to be done is colourIterator where we're expecting a single integer:

colourIterator

    storeJsonValue colourIntegerLocation
    rts

Value will be stored under location set by colourIntegerLocation pointers (omitted here, but done the same way as for
coloursTable).

And that's it. See index of functions to learn more.

4. Index of functions.

expectAnyJsonKey <iteratorFunction>

expectJsonKey "<key>", <iteratorFunction>

isJsonArrayCompleted

isJsonObjectCompleted

setAsciiMode

setJsonObject <jsonObjectMemoryLocation>

setJsonOutputMemoryLocation <memoryLocationPointer>, <memoryLocation>

setPetsciiMode

setStreamedJsonObject

storeJsonKey <memoryLocationPointers>, [<memoryOffset>]

storeJsonKeyLength <memoryLocationPointers>, [<memoryOffset>]

storeJsonValue <memoryLocationPointers>, [<memoryOffset>]

storeJsonValueLength <memoryLocationPointers>, [<memoryOffset>]

5. License.

Copyright © 2017, Bartosz Żołyński, Commocore.

Released under the GNU General Public License, version 3.