diff --git a/code/__DEFINES/json.dm b/code/__DEFINES/json.dm new file mode 100644 index 00000000000..3f004ea4271 --- /dev/null +++ b/code/__DEFINES/json.dm @@ -0,0 +1 @@ +#define writeJson(value) list2text(_jsonHelper.WriteValue(list(), (value))) \ No newline at end of file diff --git a/code/modules/json/json.dm b/code/modules/json/json.dm index df6a6a4661c..cd411dba09e 100644 --- a/code/modules/json/json.dm +++ b/code/modules/json/json.dm @@ -1,12 +1,85 @@ -proc/json2list(json) - var/static/json_reader/_jsonr = new() - return _jsonr.ReadObject(_jsonr.ScanJson(json)) +/var/datum/jsonHelper/_jsonHelper = new // Solely as a namespace for procs. -proc/list2json(list/L) - var/static/json_writer/_jsonw = new() - return _jsonw.write(L) +// ************************************ WRITER ************************************ +/datum/jsonHelper/proc/WriteValue(list/json, value) + . = json + if(isnum(value)) + json += value // Consider num2text(value, 20) for maximum accuracy. + else if(isnull(value)) + json += "null" + else if(istext(value)) + WriteString(json, value) + else if(istype(value, /list)) + WriteList(json, value) + else + throw EXCEPTION("Datums cannot be converted to JSON.") -proc/list2json_usecache(list/L) - var/static/json_writer/_jsonw = new() - _jsonw.use_cache = 1 - return _jsonw.write(L) \ No newline at end of file +/datum/jsonHelper/proc/WriteString(list/json, str) + . = json + var/quotePos = findtextEx(str, "\"") + var/bsPos = findtextEx(str, "\\") + if (quotePos == 0 && bsPos == 0) + json.Add("\"", str, "\"") + else + json += "\"" + var/lastStop = 1 + while(quotePos != 0 || bsPos != 0) + var/escPos + if(quotePos < bsPos && quotePos != 0 || bsPos == 0) + escPos = quotePos + else + escPos = bsPos + json.Add(copytext(str, lastStop, escPos), "\\") + lastStop = escPos + if(escPos == quotePos) + quotePos = findtextEx(str, "\"", escPos + 1) + else if(escPos == bsPos) + bsPos = findtextEx(str, "\\", escPos + 1) + json.Add(copytext(str, lastStop), "\"") + +/datum/jsonHelper/proc/WriteList(list/json, list/listVal) + . = json + #define Either 0 + #define CannotBeArray 1 + #define CannotBeObject 2 + #define BadList (CannotBeArray | CannotBeObject) + var/listType = Either + for(var/key in listVal) + if(istext(key)) + if(!isnull(listVal[key])) + listType |= CannotBeArray + else + if(!isnum(key) && !isnull(listVal[key])) + listType = BadList + else + listType |= CannotBeObject + + if(listType == BadList) + throw EXCEPTION("The given list cannot be converted to JSON.") + + if(listType == CannotBeArray) + json += "{" + var/addComma + for(var/key in listVal) + if(addComma) + json += "," + else + addComma = TRUE + WriteString(json, key) + json += ":" + WriteValue(json, listVal[key]) + json += "}" + else + json += "\[" + var/addComma + for(var/key in listVal) + if(addComma) + json += "," + else + addComma = TRUE + WriteValue(json, key) + json += "]" + #undef Either + #undef CannotBeFlat + #undef CannotBeAssoc + #undef BadList \ No newline at end of file diff --git a/code/modules/json/json_reader.dm b/code/modules/json/json_reader.dm deleted file mode 100644 index 39289201de1..00000000000 --- a/code/modules/json/json_reader.dm +++ /dev/null @@ -1,187 +0,0 @@ -json_reader/var/list/string = list("'", "\"") -json_reader/var/list/symbols = list("{", "}", "\[", "]", ":", "\"", "'", ",") -json_reader/var/list/sequences = list("b" = 8, "t" = 9, "n" = 10, "f" = 12, "r" = 13) -json_reader/var/list/tokens -json_reader/var/json -json_reader/var/i = 1 - - -json_reader/proc/ScanJson(json) - src.json = json - . = new/list() - src.i = 1 - while(src.i <= lentext(json)) - var/char = get_char() - if(is_whitespace(char)) - i++ - continue - if(string.Find(char)) - . += read_string(char) - else if(symbols.Find(char)) - . += new/json_token/symbol(char) - else if(is_digit(char)) - . += read_number() - else - . += read_word() - i++ - . += new/json_token/eof() - -json_reader/proc/read_word() - var/val = "" - while(i <= lentext(json)) - var/char = get_char() - if(is_whitespace(char) || symbols.Find(char)) - i-- // let scanner handle this character - return new/json_token/word(val) - val += char - i++ - -json_reader/proc/read_string(delim) - var/escape = FALSE - var/val = "" - while(++i <= lentext(json)) - var/char = get_char() - if(escape) - switch(char) - if("\\", "'", "\"", "/", "u") - val += char - else - // TODO: support octal, hex, unicode sequences - ASSERT(sequences.Find(char)) - val += ascii2text(sequences[char]) - else - if(char == delim) - return new/json_token/text(val) - else if(char == "\\") - escape = TRUE - else - val += char - CRASH("Unterminated string.") - -json_reader/proc/read_number() - var/val = "" - var/char = get_char() - while(is_digit(char) || char == "." || lowertext(char) == "e") - val += char - i++ - char = get_char() - i-- // allow scanner to read the first non-number character - return new/json_token/number(text2num(val)) - -json_reader/proc/check_char() - ASSERT(args.Find(get_char())) - -json_reader/proc/get_char() - return copytext(json, i, i+1) - -json_reader/proc/is_whitespace(char) - return char == " " || char == "\t" || char == "\n" || text2ascii(char) == 13 - -json_reader/proc/is_digit(char) - var/c = text2ascii(char) - return 48 <= c && c <= 57 || char == "+" || char == "-" - - -json_reader/proc/ReadObject(list/tokens) - src.tokens = tokens - . = new/list() - i = 1 - read_token("{", /json_token/symbol) - while(i <= tokens.len) - var/json_token/K = get_token() - check_type(/json_token/word, /json_token/text) - next_token() - read_token(":", /json_token/symbol) - - .[K.value] = read_value() - - var/json_token/S = get_token() - check_type(/json_token/symbol) - switch(S.value) - if(",") - next_token() - continue - if("}") - next_token() - return - else - die() - -json_reader/proc/get_token() - return tokens[i] - -json_reader/proc/next_token() - return tokens[++i] - -json_reader/proc/read_token(val, type) - var/json_token/T = get_token() - if(!(T.value == val && istype(T, type))) - CRASH("Expected '[val]', found '[T.value]'.") - next_token() - return T - -json_reader/proc/check_type(...) - var/json_token/T = get_token() - for(var/type in args) - if(istype(T, type)) - return - CRASH("Bad token type: [T.type].") - -json_reader/proc/check_value(...) - var/json_token/T = get_token() - ASSERT(args.Find(T.value)) - -json_reader/proc/read_key() - var/char = get_char() - if(char == "\"" || char == "'") - return read_string(char) - -json_reader/proc/read_value() - var/json_token/T = get_token() - switch(T.type) - if(/json_token/text, /json_token/number) - next_token() - return T.value - if(/json_token/word) - next_token() - switch(T.value) - if("true") - return TRUE - if("false") - return FALSE - if("null") - return null - if(/json_token/symbol) - switch(T.value) - if("\[") - return read_array() - if("{") - return ReadObject(tokens.Copy(i)) - die() - -json_reader/proc/read_array() - read_token("\[", /json_token/symbol) - . = new/list() - var/list/L = . - while(i <= tokens.len) - // Avoid using Add() or += in case a list is returned. - L.len++ - L[L.len] = read_value() - var/json_token/T = get_token() - check_type(/json_token/symbol) - switch(T.value) - if(",") - next_token() - continue - if("]") - next_token() - return - else - die() - next_token() - CRASH("Unterminated array.") - - -json_reader/proc/die(json_token/T) - if(!T) T = get_token() - CRASH("Unexpected token: [T.value].") \ No newline at end of file diff --git a/code/modules/json/json_token.dm b/code/modules/json/json_token.dm deleted file mode 100644 index 7344e91b52c..00000000000 --- a/code/modules/json/json_token.dm +++ /dev/null @@ -1,9 +0,0 @@ -json_token/var/value -json_token/New(v) - src.value = v - -json_token/text -json_token/number -json_token/word -json_token/symbol -json_token/eof \ No newline at end of file diff --git a/code/modules/json/json_writer.dm b/code/modules/json/json_writer.dm deleted file mode 100644 index 65c38f3a0a4..00000000000 --- a/code/modules/json/json_writer.dm +++ /dev/null @@ -1,67 +0,0 @@ -#define LIST_FLAT 0 -#define LIST_NESTED 1 -#define LIST_OBJECT 2 // Object in this case means json object, ortherwise known as an associative list or array. - -json_writer/var/use_cache = 0 - -json_writer/proc/write(val) - if(isnum(val)) - . = num2text(val) - else if(isnull(val)) - . = "null" - else if(istype(val, /list)) - switch (listtype(val)) - if (LIST_FLAT) - . = write_array(val) - if (LIST_NESTED) - . = write_array(val) - if (LIST_OBJECT) - . = write_object(val) - else - . = write_string("[val]") - -json_writer/proc/write_object(list/L) - if(use_cache && L["__json_cache"]) - return L["__json_cache"] - . = list() - for(var/k in L) - . += "\"[k]\":[write(L[k])]" - - . = "{[list2text(., ",")]}" - -json_writer/proc/write_flat_array(list/L) - . = "\[[list2text(L, ",")]]" - -json_writer/proc/write_array(list/L) - . = list() - for(var/item in L) - . += write(item) - . = "\[[list2text(., ",")]]" - -json_writer/proc/write_string(txt) - var/static/list/json_escape = list("\\" = "\\\\", "\"" = "\\\"", "\n" = "\\n") - for(var/targ in json_escape) - var/start = 1 - while(start <= lentext(txt)) - var/i = findtext(txt, targ, start) - if(!i) - break - var/lrep = length(json_escape[targ]) - txt = "[copytext(txt, 1, i)][json_escape[targ]][copytext(txt, i + length(targ))]" - start = i + lrep - - return {""[txt]""} - -json_writer/proc/listtype(list/L) - . = LIST_FLAT - for(var/key in L) - if (. == LIST_FLAT && istype(key, /list)) - . = LIST_NESTED - if (!isnum(key) && !istype(key, /list)) - . = LIST_OBJECT - break // If it's an object, we can just stop now. - - -#undef LIST_FLAT -#undef LIST_NESTED -#undef LIST_OBJECT diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm index 36add2f9bb4..964b23e1e19 100644 --- a/code/modules/nano/nanoui.dm +++ b/code/modules/nano/nanoui.dm @@ -213,7 +213,7 @@ // Generate JSON. var/list/send_data = get_send_data(initial_data) - var/initial_data_json = replacetext(list2json_usecache(send_data), "'", "\\'") + var/initial_data_json = replacetext(writeJson(send_data), "'", "\\'") // Generate the HTML document. return {" @@ -314,7 +314,7 @@ var/list/send_data = get_send_data(data) // Get the data to send. // Send the new data to the recieveUpdate() Javascript function. - user << output(list2params(list(list2json_usecache(send_data))), "[window_id].browser:receiveUpdate") + user << output(list2params(list(writeJson(send_data))), "[window_id].browser:receiveUpdate") /** * private diff --git a/tgstation.dme b/tgstation.dme index 8c72aa916b8..12c4e9e6010 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -31,6 +31,7 @@ #include "code\__DEFINES\genetics.dm" #include "code\__DEFINES\hud.dm" #include "code\__DEFINES\is_helpers.dm" +#include "code\__DEFINES\json.dm" #include "code\__DEFINES\machines.dm" #include "code\__DEFINES\math.dm" #include "code\__DEFINES\misc.dm" @@ -1104,9 +1105,6 @@ #include "code\modules\hydroponics\seed_extractor.dm" #include "code\modules\hydroponics\seeds.dm" #include "code\modules\json\json.dm" -#include "code\modules\json\json_reader.dm" -#include "code\modules\json\json_token.dm" -#include "code\modules\json\json_writer.dm" #include "code\modules\library\lib_items.dm" #include "code\modules\library\lib_machines.dm" #include "code\modules\library\lib_readme.dm"