Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
798 lines (695 sloc) 16.8 KB
#~~
# JSON parsing library
# Copyright (c) 2012,2019 Randy Hollines
~~#
use Collection.Generic;
#~
Support for paring JSON strings and documents (json.obl)
~#
bundle Data.JSON {
#~
Support for JSON parsing
~#
class JSONParser {
@input : String;
@tokens : Vector<Token>;
@token_pos : Int;
@cur_token : Token;
#~
Creates a new parser
@param input JSON input string
~#
New(input : String) {
@input := input;
@tokens : Nil;
@token_pos := 0;
}
method : GetToken(pos : Int) ~ Token {
if(pos < @tokens->Size()) {
return @tokens->Get(pos)->As(Token);
};
return Token->New(Token->Type->EOS);
}
method : Match(token : Token->Type) ~ Bool {
return @cur_token->GetType() = token;
}
#~
Parses a JSON string
@return root JSON element
~#
method : public : Parse() ~ JSONElement {
@tokens := Scan();
if(@tokens->Size() > 0) {
# DumpTokens(@tokens);
NextToken();
return ParseValue();
};
return Nil;
}
method : NextToken() ~ Nil {
if(@token_pos < @tokens->Size()) {
@cur_token := @tokens->Get(@token_pos)->As(Token);
@token_pos += 1;
}
else {
@cur_token := Token->New(Token->Type->EOS);
};
}
#~
Parses a JSON value
@return JSON value element
~#
method : public : ParseValue() ~ JSONElement {
element : JSONElement;
select(@cur_token->GetType()) {
label Token->Type->OBR: {
# "array"->PrintLine();
element := ParseArray();
}
label Token->Type->OCBR: {
# "object"->PrintLine();
element := ParseObject();
}
label Token->Type->IDENT: {
element := JSONElement->New(@cur_token->GetValue());
# Console->Print("string=")->PrintLine(@cur_token->GetValue());
NextToken();
}
label Token->Type->INT_NUM:
label Token->Type->FLOAT_NUM: {
element := JSONElement->New(JSONType->NUMBER, @cur_token->GetValue());
# Console->Print("number=")->PrintLine(@cur_token->GetValue());
NextToken();
}
label Token->Type->TRUE: {
element := JSONElement->New(JSONType->TRUE);
# "true"->PrintLine();
NextToken();
}
label Token->Type->FALSE: {
element := JSONElement->New(JSONType->FALSE);
# "false"->PrintLine();
NextToken();
}
label Token->Type->NULL: {
element := JSONElement->New(JSONType->NULL);
# "null"->PrintLine();
NextToken();
}
other: {
"*** Unexpected value *** "->PrintLine();
# @cur_token->GetType()->As(Int)->PrintLine();
return Nil;
}
};
return element;
}
#~
Parses a JSON object
@return JSON object element
~#
method : public : ParseObject() ~ JSONElement {
elements := Map->New()<String, JSONElement>;
NextToken();
while(@cur_token->GetType() <> Token->Type->EOS &
@cur_token->GetType() <> Token->Type->CCBR) {
# parse name
elem_name : Token;
if(Match(Token->Type->IDENT) = false) {
"*** Expected identifier *** "->PrintLine();
# @cur_token->GetType()->As(Int)->PrintLine();
return Nil;
};
elem_name := @cur_token;
NextToken();
if(Match(Token->Type->COLON) = false) {
"*** Expected ':' *** "->PrintLine();
# @cur_token->GetType()->As(Int)->PrintLine();
return Nil;
};
NextToken();
# parse value
value := ParseValue();
if(value = Nil) {
return Nil;
};
# IO.Console->Print("insert=")->PrintLine(elem_name->GetValue());
elements->Insert(elem_name->GetValue(), value);
# update
if(Match(Token->Type->COMMA)) {
NextToken();
# "comma"->PrintLine();
};
};
if(Match(Token->Type->CCBR) = false) {
"*** Expected '}' *** "->PrintLine();
# @cur_token->GetType()->As(Int)->PrintLine();
return Nil;
};
NextToken();
return JSONElement->New(elements);
}
#~
Parses a JSON array
@return JSON array element
~#
method : public : ParseArray() ~ JSONElement {
elements := Vector->New()<JSONElement>;
NextToken();
while(@cur_token->GetType() <> Token->Type->EOS &
@cur_token->GetType() <> Token->Type->CBR) {
value := ParseValue();
if(value = Nil) {
return Nil;
};
elements->AddBack(value);
# update
if(Match(Token->Type->COMMA)) {
NextToken();
# "comma"->PrintLine();
};
};
if(Match(Token->Type->CBR) = false) {
"*** Expected ']' *** "->PrintLine();
# @cur_token->GetType()->As(Int)->PrintLine();
return Nil;
};
NextToken();
return JSONElement->New(elements);
}
# scan JSON tokens
method : native : Scan() ~ Vector<Token> {
tokens := Vector->New()<Token>;
input := @input->ToCharArray();
if(input <> Nil) {
index := 0;
# TODO: add-in line numbers
while(index < input->Size()) {
# skip whitespace
while(index < input->Size() & (input[index] = '\r' | input[index] = '\n' |
input[index] = '\t' | input[index] = ' ')) {
# update
index += 1;
};
# comment
if(index + 1 < input->Size() & input[index] = '/' & input[index + 1] = '/') {
index += 2;
while(index < input->Size() & (input[index] <> '\r' & input[index] <> '\n')) {
# update
index += 1;
};
}
# token
else {
# parse string
if(index < input->Size() & (input[index] = '"' | input[index]->IsChar())) {
len : Int; offset : Int;
if(input[index] = '"') {
index += 1;
offset := index;
end := false;
escape := false;
while(index < input->Size() & end = false) {
if(input[index] = '"' & escape = false) {
# done
end := true;
}
else if(input[index] = '\\') {
escape := true;
# update
index += 1;
}
else {
escape := false;
# update
index += 1;
};
};
len := index - offset;
# update
index += 1;
}
else {
offset := index;
while(index < input->Size() & (input[index]->IsChar() | input[index] = '_')) {
# update
index += 1;
};
len := index - offset;
};
if(len > 0) {
string := @input->SubString(offset, len);
if(string->Equals("true")) {
tokens->AddBack(Token->New(Token->Type->TRUE));
}
else if(string->Equals("false")) {
tokens->AddBack(Token->New(Token->Type->FALSE));
}
else if(string->Equals("null")) {
tokens->AddBack(Token->New(Token->Type->NULL));
}
else {
tokens->AddBack(Token->New(Token->Type->IDENT, string));
};
}
else {
tokens->AddBack(Token->New(Token->Type->IDENT, ""));
};
}
# parse number
else if(index < input->Size() & (input[index]->IsDigit() | input[index] = '-' | input[index] = '.')) {
offset := index;
minus_count := 0;
dot_count := 0;
# TODO: e digits
while(index < input->Size() &
(input[index]->IsDigit() | input[index] = '-' | input[index] = '.' | input[index] = 'e' | input[index] = 'E')) {
if(input[index] = '-') {
minus_count += 1;
}
else if(input[index] = '.') {
dot_count += 1;
};
# update
index += 1;
};
len := index - offset;
if(len > 0) {
string := @input->SubString(offset, len);
if(dot_count = 1 & minus_count < 2) {
tokens->AddBack(Token->New(Token->Type->FLOAT_NUM, string));
}
else if(dot_count = 0 & minus_count < 2) {
tokens->AddBack(Token->New(Token->Type->INT_NUM, string));
}
else {
tokens->AddBack(Token->New(Token->Type->BAD_NUM, string));
};
}
else {
tokens->AddBack(Token->New(Token->Type->BAD_NUM, ""));
};
}
# parse character
else if(index < input->Size()) {
select(input[index]) {
label '[': {
tokens->AddBack(Token->New(Token->Type->OBR));
}
label ']': {
tokens->AddBack(Token->New(Token->Type->CBR));
}
label '{': {
tokens->AddBack(Token->New(Token->Type->OCBR));
}
label '}': {
tokens->AddBack(Token->New(Token->Type->CCBR));
}
label ',': {
tokens->AddBack(Token->New(Token->Type->COMMA));
}
label ':': {
tokens->AddBack(Token->New(Token->Type->COLON));
}
other: {
tokens->AddBack(Token->New(Token->Type->OTHER));
# IO.Console->Print("other 1=")->PrintLine(input[index]->As(Int));
}
};
# update
index += 1;
};
};
};
};
return tokens;
}
method : DumpTokens(tokens : Vector<Token>) ~ Nil {
if(tokens->Size() > 0) {
each(i : tokens) {
select(tokens->Get(i)->As(Token)->GetType()) {
label Token->Type->IDENT: {
IO.Console->Print("token=IDENT, value=")->PrintLine(tokens->Get(i)->As(Token)->GetValue());
}
label Token->Type->INT_NUM: {
IO.Console->Print("token=INTEGER, value=")->PrintLine(tokens->Get(i)->As(Token)->GetValue());
}
label Token->Type->FLOAT_NUM: {
IO.Console->Print("token=FLOAT, value=")->PrintLine(tokens->Get(i)->As(Token)->GetValue());
}
label Token->Type->OBR: {
"token='['"->PrintLine();
}
label Token->Type->CBR: {
"token=']'"->PrintLine();
}
label Token->Type->OCBR: {
"token='{'"->PrintLine();
}
label Token->Type->CCBR: {
"token='}'"->PrintLine();
}
label Token->Type->PREN: {
"token='''"->PrintLine();
}
label Token->Type->COLON: {
"token=':'"->PrintLine();
}
label Token->Type->COMMA: {
"token=','"->PrintLine();
}
label Token->Type->OTHER:
label Token->Type->BAD_NUM: {
"token=OTHER"->PrintLine();
}
};
};
};
}
}
#~
JSON value element
~#
class JSONElement {
@type : JSONType;
@value : String;
@array_elems : Vector<JSONElement>;
@map_elems : Map<String, JSONElement>;
#~
Constructor
@param type JSON element type
~#
New(type : JSONType) {
if(type = JSONType->TRUE) {
@type := type;
@value := "true";
}
else if(type = JSONType->FALSE) {
@type := type;
@value := "false";
}
else if(type = JSONType->NULL) {
@type := type;
@value := "";
}
else {
@type := JSONType->OTHER;
};
}
#~
Constructor
@param type JSON element type
@param value JSON string value
~#
New(type : JSONType, value : String) {
if(type = JSONType->STRING | type = JSONType->NUMBER) {
@type := type;
@value := value;
}
else {
@type := JSONType->OTHER;
};
}
#~
Constructor
@param value JSON string value
~#
New(value : String) {
@type := JSONType->STRING;
@value := value;
}
#~
Constructor
@param value JSON integer value
~#
New(value : Int) {
@type := JSONType->NUMBER;
@value := value->ToString();
}
#~
Constructor
@param value JSON float value
~#
New(value : Float) {
@type := JSONType->NUMBER;
@value := value->ToString();
}
#~
Constructor
@param array_elems JSON array value
~#
New(array_elems : Vector<JSONElement>) {
@type := JSONType->ARRAY;
@array_elems := array_elems;
}
#~
Constructor
@param map_elems JSON object (names/values)
~#
New(map_elems : Map<String, JSONElement>) {
@type := JSONType->OBJECT;
@map_elems := map_elems;
}
#~
Gets the type
@return type
~#
method : public : GetType() ~ JSONType {
return @type;
}
#~
Gets the value
@return value
~#
method : public : GetValue() ~ String {
if(@value <> Nil) {
return @value;
};
return "";
}
#~
Gets an indexed value from an array type
@param index index
@return indexed value
~#
method : public : Get(index : Int) ~ JSONElement {
if(@array_elems <> Nil & index < @array_elems->Size()) {
return @array_elems->Get(index);
};
return Nil;
}
#~
Gets a named value from an object type
@param name element name
@return element value
~#
method : public : Get(name : String) ~ JSONElement {
if(@map_elems <> Nil) {
return @map_elems->Find(name);
};
return Nil;
}
#~
Gets the names of object attributes
@return object attribute names
~#
method : public : GetNames() ~ Vector<String> {
if(@map_elems <> Nil) {
return @map_elems->GetKeys()<String>;
};
return Nil;
}
#~
Gets the size of an array or object value
@return size of an array or object value
~#
method : public : Size() ~ Int {
if(@array_elems <> Nil) {
return @array_elems->Size();
};
if(@map_elems <> Nil) {
return @map_elems->Size();
};
return 0;
}
#~
Checks a node's type
@param elem element to check
@param type type to check
@return true of matching, false otherwise
~#
function : public : MatchType(elem : JSONElement, type : JSONType) ~ Bool {
if(elem <> Nil & elem->GetType() = type) {
return true;
};
return false;
}
#~
Checks a node's type
@param elem element to check
@param value value to check
@return true of matching, false otherwise
~#
function : public : MatchValue(elem : JSONElement, value : String) ~ Bool {
if(elem <> Nil & elem->GetType() = JSONType->STRING & elem->GetValue()->Equals(value)) {
return true;
};
return false;
}
#~
Format the element into a JSON string
@return JSON string
~#
method : public : ToString() ~ String {
output := String->New();
Format(output);
return output;
}
method : Format(output : String) ~ Nil {
select(@type) {
label JSONType->STRING: {
if(@value <> Nil) {
output->Append('"');
output->Append(@value);
output->Append('"');
};
}
label JSONType->NUMBER: {
if(@value <> Nil) {
output->Append(@value);
};
}
label JSONType->TRUE: {
output->Append("\"true\"");
}
label JSONType->FALSE: {
output->Append("\"false\"");
}
label JSONType->NULL: {
output->Append("\"null\"");
}
label JSONType->ARRAY: {
if(@array_elems <> Nil) {
output->Append('[');
each(i : @array_elems) {
@array_elems->Get(i)->As(JSONElement)->Format(output);
if(i + 1 < @array_elems->Size()) {
output->Append(',');
};
};
output->Append(']');
};
}
label JSONType->OBJECT: {
if(@map_elems <> Nil) {
output->Append('{');
keys := @map_elems->GetKeys()<String>;
each(i : keys) {
key := keys->Get(i)->As(String);
value := @map_elems->Find(key);
output->Append('"');
output->Append(key);
output->Append("\":");
value->As(JSONElement)->Format(output);
if(i + 1 < @map_elems->Size()) {
output->Append(',');
};
};
output->Append('}');
};
}
};
}
#~
Queries the object graph. Object attributes referenced by '/' while array elements are referenced by '[index]'. Example "cars/[last]/make".
@param path query path
@return matching element
~#
method : public : FindElements(path : String) ~ JSONElement {
filters := path->Split("/");
if(filters->Size() > 0) {
element := @self;
for(i := 0; i < filters->Size() & element <> Nil; i += 1;) {
element := FindElement(filters[i]->As(String), element);
};
return element;
};
return Nil;
}
method : FindElement(filter : String, node : JSONElement) ~ JSONElement {
# array
if(filter->StartsWith('[') & filter->EndsWith(']') & node->GetType() = JSONType->ARRAY) {
end := filter->Find(']');
index := filter->SubString(1, end - 1);
offset : Int;
if(index->Equals("first")) {
offset := 0;
}
else if(index->Equals("last")) {
offset := node->Size() - 1;
}
else {
offset := index->ToInt();
};
if(offset > -1 & offset < node->Size()) {
return node->Get(offset);
};
}
# object
else if(node->GetType() = JSONType->OBJECT) {
return node->Get(filter);
};
return Nil;
}
}
#~
JSON element type
~#
enum JSONType {
STRING,
NUMBER,
TRUE,
FALSE,
NULL,
ARRAY,
OBJECT,
OTHER
}
# token class
class Token {
@type : Token->Type;
@value : String;
enum Type {
IDENT,
INT_NUM,
FLOAT_NUM,
BAD_NUM,
TRUE,
FALSE,
NULL,
COMMA,
OBR,
CBR,
OCBR,
CCBR,
PREN,
COLON,
OTHER,
EOS
}
New(type : Token->Type, value : String) {
@type := type;
@value := value;
}
New(type : Token->Type) {
@type := type;
@value := Nil;
}
method : public : GetType() ~ Token->Type {
return @type;
}
method : public : GetValue() ~ String {
return @value;
}
}
}
You can’t perform that action at this time.