Overview
Atlassian Sourcetree is a free Git and Mercurial client for Windows.
Atlassian Sourcetree is a free Git and Mercurial client for Mac.
6502 JSON Parser
The JSON object parser library in 6502 assembly.
Contents
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.