So now we have an understanding of JSON and the components that make it up, but we have to create something that will read it - this will be our parser.
Our parser will step through our JSON file character by character to read it, so it'll need to:
- remember our JSON
- remember where in the JSON it's currently looking
- know if it's at the end
- read the current character
- step to the next character
So let's do it!
First, we'll create a class which will take in our JSON as a CharSequence
(we could use a String
, too - the functionality is the same, but this lets you do something weird like throw a StringBuilder
in there), and will remember where it's currently reading:
class Parser(private val input: CharSequence) {
var cursor: Int = 0
}
Now let's give it the ability to read and step through the input.
class Parser(private val input: CharSequence) {
var cursor: Int = 0
private fun step(): Char {
return input[cursor++]
}
private fun peek(): Char {
return input[cursor]
}
private fun skip() {
cursor++
}
}
peek()
will read the current character, skip()
, will move the cursor forwards by one, and step()
and read and then move on.
However, trying to read past the end will throw an exception, so let's add a method to check if we're at the end:
private fun hasNext(): Boolean {
return cursor < input.length
}
This is everything we'll need for moving along our JSON text. Next, let's add some functions to actually read it:
fun read(predicate: (Char) -> Boolean): String {
val result = StringBuilder()
while (predicate(peek())) {
result.append(step())
}
return result.toString()
}
fun read(pattern: CharSequence): String {
val result = StringBuilder()
var patternCursor = 0
while (peek() == pattern[patternCursor++]) {
result.append(skip())
}
return result.toString()
}
These two functions are vital to how the parser will do its job:
They read the JSON one character at a time, and store what they've read in a buffer, until the character they read reaches a certain condition. For the first function, that condition is given via a predicate, and in the second, the condition is that the buffer's contents match a given string. Once the condition is met, the buffer's contents are returned.
These five methods, simple as they seem, are extremely powerful, and all we need as a foundation for our parser!
Top comments (0)