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 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.