// Originally from /jsonion.gml in JSOnion // JSOnion v1.0.0d is licensed under the MIT licence. You may freely adapt and use this library in commercial and non-commercial projects. #define __jso_gmt_tuple { /** tuple(>element_0<, >element_1<, ..., >element_n<): Return an n-tuple @author: GameGeisha @version: 1.2 (GMTuple) */ //Position, address table and data var pos, addr_table, data; pos = 6*argument_count+4; addr_table = ""; data = ""; //Build the tuple element-by-element var i, ca, isstr, datastr; for (i=0; ith element of @author: GameGeisha @version: 1.2 (GMTuple) */ //Capture arguments var t, n, size; t = argument0; n = argument1; size = __jso_gmt_size(t); //Search for the bounding positions for the th element in the address table var start, afterend, isstr; isstr = ord(string_char_at(t, 4+6*n))-$30; start = real(string_copy(t, 5+6*n, 5)); if (n < size-1) { afterend = real(string_copy(t, 11+6*n, 5)); } else { afterend = string_length(t)+1; } //Return the th element with the correct type if (isstr) { return string_copy(t, start, afterend-start); } else { return real(string_copy(t, start, afterend-start)); } } #define __jso_gmt_size { /** size(tuple_source): Return the size of @author: GameGeisha @version: 1.2 (GMTuple) */ return real(string_copy(argument0, 1, 3)); } #define __jso_gmt_numtostr { /** __gmt_numtostr(num): Return string representation of . Decimal numbers expressed as scientific notation with double precision (i.e. 15 digits) @author: GameGeisha @version: 1.2 (edited) */ if (frac(argument0) == 0) { return string(argument0); } var mantissa, exponent; exponent = floor(log10(abs(argument0))); mantissa = string_format(argument0/power(10,exponent), 15, 14); var i, ca; i = string_length(mantissa); do { ca = string_char_at(mantissa, i); i -= 1; } until (ca != "0") if (ca != ".") { mantissa = string_copy(mantissa, 1, i+1); } else { mantissa = string_copy(mantissa, 1, i); } if (exponent != 0) { return mantissa + "e" + string(exponent); } else { return mantissa; } } #define __jso_gmt_test_all { /** test_all(): Runs all test suites @author: GameGeisha @version: 1.2 (GMTuple) */ //Automated testing sequence __jso_gmt_test_elem(); __jso_gmt_test_size(); __jso_gmt_test_numtostr(); } #define __jso_gmt_test_numtostr { /** _test_numtostr(): Runs number-to-string tests @author: GameGeisha @version: 1.2 (GMTuple) */ var tolerance; tolerance = 1/10000000000; if (real(__jso_gmt_numtostr(9)) != 9) { show_message("Scientific notation conversion failed for 1-digit integer! Result: " + __jso_gmt_numtostr(9)); } if (real(__jso_gmt_numtostr(500)) != 500) { show_message("Scientific notation conversion failed for 3-digit integer! Result: " + __jso_gmt_numtostr(500)); } if (abs(real(__jso_gmt_numtostr(pi))-pi) > tolerance) { show_message("Scientific notation conversion failed for pi! Result: " + __jso_gmt_numtostr(pi)); } if (abs(real(__jso_gmt_numtostr(104729.903455))-104729.903455) > tolerance) { show_message("Scientific notation conversion failed for large decimal number! Result: " + __jso_gmt_numtostr(104729.903455)); } if (abs(real(__jso_gmt_numtostr(-pi))+pi) > tolerance) { show_message("Scientific notation conversion failed for -pi! Result: " + __jso_gmt_numtostr(-pi)); } if (abs(real(__jso_gmt_numtostr(1/pi))-1/pi) > tolerance) { show_message("Scientific notation conversion failed for 1/pi! Result: " + __jso_gmt_numtostr(1/pi)); } } #define __jso_gmt_test_elem { /** _test_elem(): Runs tuple element retrieval tests @author: GameGeisha @version: 1.2 (GMTuple) */ if (__jso_gmt_elem(__jso_gmt_tuple("Qblock", "kll"), 0) != "Qblock") { show_message("Element retrieval failed for simple string. #Should be:Qblock#Actual:" + __jso_gmt_elem(__jso_gmt_tuple("Qblock"), 0)); } if (__jso_gmt_elem(__jso_gmt_tuple(9, "Q", -7), 0) != 9) { show_message("Element retrieval failed for simple number. #Should be 9#Actual:" + string(__jso_gmt_elem(__jso_gmt_tuple(9, "Q", 7), 0))); } if (__jso_gmt_elem(__jso_gmt_tuple("Qblock", "", "Negg"), 1) != "") { show_message("Element retrieval failed for empty string#Should be empty string#Actual:"+string(__jso_gmt_elem(__jso_gmt_tuple("Qblock", "", "Negg"), 0))); } if (__jso_gmt_elem(__jso_gmt_elem(__jso_gmt_tuple("Not this", __jso_gmt_tuple(0, 1, 2, 3), "Waahoo"), 1), 3) != 3) { show_message("Element retrieval failed in nested tuple. #Should be 3#Actual:" + string(__jso_gmt_elem(__jso_gmt_elem(__jso_gmt_tuple("Not this", __jso_gmt_tuple(0, 1, 2, 3), "Waahoo"), 1), 3))); } } #define __jso_gmt_test_size { /** _test_size(): Runs tuple size tests @author: GameGeisha @version: 1.2 (GMTuple) */ if (__jso_gmt_size(__jso_gmt_tuple("Waahoo", "Negg", 0)) != 3) { show_message("Bad size for 3-tuple"); } if (__jso_gmt_size(__jso_gmt_tuple()) != 0) { show_message("Bad size for null tuple"); } if (__jso_gmt_size(__jso_gmt_tuple(7)) != 1) { show_message("Bad size for 1-tuple"); } if (__jso_gmt_size(__jso_gmt_tuple(1,2,3,4,5,6,7,8,9,10)) != 10) { show_message("Bad size for 10-tuple"); } } #define jso_new_map { /** jso_new_map(): Create a new map. JSOnion version: 1.0.0d */ return ds_map_create(); } #define jso_new_list { /** jso_new_list(): Create a new list. JSOnion version: 1.0.0d */ return ds_list_create(); } #define jso_map_add_real { /** jso_map_add_real(map, key, val): Add the key-value pair : to , where is a real value. JSOnion version: 1.0.0d */ ds_map_add(argument0, argument1, argument2); } #define jso_map_add_string { /** jso_map_add_string(map, key, str): Add the key-value pair : to , where is a string value. JSOnion version: 1.0.0d */ ds_map_add(argument0, argument1, "s" + argument2); } #define jso_map_add_sublist { /** jso_map_add_sublist(map, key, sublist): Add the key-value pair : to , where is a list. JSOnion version: 1.0.0d */ ds_map_add(argument0, argument1, "l" + string(argument2)); } #define jso_map_add_submap { /** jso_map_add_submap(map, key, submap): Add the key-value pair : to , where is a map. JSOnion version: 1.0.0d */ ds_map_add(argument0, argument1, "m" + string(argument2)); } #define jso_map_add_integer { /** jso_map_add_integer(map, key, int): Add the key-value pair : to , where is a integer value. JSOnion version: 1.0.0d */ ds_map_add(argument0, argument1, floor(argument2)); } #define jso_map_add_boolean { /** jso_map_add_boolean(map, key, bool): Add the key-value pair : to , where is a boolean value. JSOnion version: 1.0.0d */ if (argument2) { ds_map_add(argument0, argument1, "btrue"); } else { ds_map_add(argument0, argument1, "bfalse"); } } #define jso_list_add_real { /** jso_list_add_real(list, val): Append the real value to . JSOnion version: 1.0.0d */ ds_list_add(argument0, argument1); } #define jso_list_add_string { /** jso_list_add_string(list, str): Append the string value to . JSOnion version: 1.0.0d */ ds_list_add(argument0, "s" + argument1); } #define jso_list_add_sublist { /** jso_list_add_sublist(list, sublist): Append the list to . JSOnion version: 1.0.0d */ ds_list_add(argument0, "l" + string(argument1)); } #define jso_list_add_submap { /** jso_list_add_submap(list, submap): Append the map to . JSOnion version: 1.0.0d */ ds_list_add(argument0, "m" + string(argument1)); } #define jso_list_add_integer { /** jso_list_add_integer(list, int): Append the integer to . JSOnion version: 1.0.0d */ ds_list_add(argument0, floor(argument1)); } #define jso_list_add_boolean { /** jso_list_add_boolean(list, bool): Append the boolean value to . JSOnion version: 1.0.0d */ if (argument1) { ds_list_add(argument0, "btrue"); } else { ds_list_add(argument0, "bfalse"); } } #define jso_map_get { /** jso_map_get(map, key): Retrieve the value stored in with the key value , with the correct type. JSOnion version: 1.0.0d */ //Grab the value var v; v = ds_map_find_value(argument0, argument1); //String; could be string, map or list if (is_string(v)) { switch (string_char_at(v, 1)) { case "s": return string_delete(v, 1, 1); break; case "l": case "m": return real(string_delete(v, 1, 1)); break; case "b": if (v == "btrue") { return true; } else if (v == "bfalse") { return false; } else { show_error("Invalid boolean value.", true); } break; default: show_error("Invalid map contents.", true); break; } } //Real; return real value as-is else { return v; } } #define jso_map_get_type { /** jso_map_get_type(map, key): Return the type of value to which the key value is mapped to in . JSOnion version: 1.0.0d */ //Grab the value var v; v = ds_map_find_value(argument0, argument1); //String; could be string, map or list if (is_string(v)) { switch (string_char_at(v, 1)) { case "s": return jso_type_string; break; case "l": return jso_type_list; break; case "m": return jso_type_map; break; case "b": return jso_type_boolean; break; default: show_error("Invalid map content type.", true); break; } } //Real else { return jso_type_real; } } #define jso_list_get { /** jso_list_get(list, index): Retrieve the value stored in at position , with the correct type. JSOnion version: 1.0.0d */ //Grab the value var v; v = ds_list_find_value(argument0, argument1); //String; could be string, map or list if (is_string(v)) { switch (string_char_at(v, 1)) { case "s": return string_delete(v, 1, 1); break; case "l": case "m": return real(string_delete(v, 1, 1)); break; case "b": if (v == "btrue") { return true; } else if (v == "bfalse") { return false; } else { show_error("Invalid boolean value.", true); } break; default: show_error("Invalid list contents.", true); break; } } //Real; return real value as-is else { return v; } } #define jso_list_get_type { /** jso_list_get_type(list, index): Retrieve the type of value found at position of . JSOnion version: 1.0.0d */ //Grab the value var v; v = ds_list_find_value(argument0, argument1); //String; could be string, map or list if (is_string(v)) { switch (string_char_at(v, 1)) { case "s": return jso_type_string; break; case "l": return jso_type_list; break; case "m": return jso_type_map; break; case "b": return jso_type_boolean; break; default: show_error("Invalid list content type.", true); break; } } //Real else { return jso_type_real; } } #define jso_cleanup_map { /** jso_cleanup_map(map): Recursively free up . JSOnion version: 1.0.0d */ //Loop through all keys var i, l, k; l = ds_map_size(argument0); k = ds_map_find_first(argument0); for (i=0; i. JSOnion version: 1.0.0d */ //Loop through all elements var i, l, v; l = ds_list_size(argument0); for (i=0; i): Return a JSON-encoded version of real value . This uses scientific notation with up to 15 significant digits for decimal values. For integers, it just uses string(). This is adapted from the same algorithm used in GMTuple. JSOnion version: 1.0.0d */ return __jso_gmt_numtostr(argument0); } #define jso_encode_string { /** jso_encode_string(str): Return a JSON-encoded version of string . JSOnion version: 1.0.0d */ //Iteratively reconstruct the string var i, l, s, c; s = ""; l = string_length(argument0); for (i=1; i<=l; i+=1) { //Replace escape characters c = string_char_at(argument0, i); switch (ord(c)) { case 34: case 92: case 47: //Double quotes, backslashes and slashes s += "\" + c; break; case 8: //Backspace s += "\b"; break; case 12: //Form feed s += "\f"; break; case 10: //New line s += "\n"; break; case 13: //Carriage return s += "\r"; break; case 9: //Horizontal tab s += "\t"; break; default: //Not an escape character s += c; break; } } //Add quotes return '"' + s + '"'; } #define jso_encode_list { /** jso_encode_list(list): Return a JSON-encoded version of list . JSOnion version: 1.0.0d */ //Iteratively encode each element var i, l, s; s = ""; l = ds_list_size(argument0); for (i=0; i 0) { s += ","; } //Select correct encoding for each element, then recursively encode each switch (jso_list_get_type(argument0, i)) { case jso_type_real: s += jso_encode_real(jso_list_get(argument0, i)); break; case jso_type_string: s += jso_encode_string(jso_list_get(argument0, i)); break; case jso_type_map: s += jso_encode_map(jso_list_get(argument0, i)); break; case jso_type_list: s += jso_encode_list(jso_list_get(argument0, i)); break; case jso_type_boolean: s += jso_encode_boolean(jso_list_get(argument0, i)); break; } } //Done, add square brackets return "[" + s + "]"; } #define jso_encode_map { /** jso_encode_map(map): Return a JSON-encoded version of map . JSOnion version: 1.0.0d */ //Go through every key-value pair var i, l, k, s; s = ""; l = ds_map_size(argument0); k = ds_map_find_first(argument0); for (i=0; i 0) { s += ","; } //Find the key and encode it if (is_real(k)) { s += jso_encode_real(k); } else { s += jso_encode_string(k); } //Add the : separator s += ":"; //Select correct encoding for each value, then recursively encode each switch (jso_map_get_type(argument0, k)) { case jso_type_real: s += jso_encode_real(jso_map_get(argument0, k)); break; case jso_type_string: s += jso_encode_string(jso_map_get(argument0, k)); break; case jso_type_map: s += jso_encode_map(jso_map_get(argument0, k)); break; case jso_type_list: s += jso_encode_list(jso_map_get(argument0, k)); break; case jso_type_boolean: s += jso_encode_boolean(jso_map_get(argument0, k)); break; } //Get next key k = ds_map_find_next(argument0, k); } //Done, add braces return "{" + s + "}"; } #define jso_encode_integer { /** jso_encode_integer(int): Return a JSON-encoded version of the integer value . JSOnion version: 1.0.0d */ return string(floor(argument0)); } #define jso_encode_boolean { /** jso_encode_boolean(bool): Return a JSON-encoded version of the boolean value . JSOnion version: 1.0.0d */ if (argument0) { return "true"; } else { return "false"; } } #define jso_decode_map { /** jso_decode_map(json): Return a JSOnion-compatible map representing the JSON string . JSOnion version: 1.0.0d */ return __jso_gmt_elem(_jso_decode_map(argument0, 1), 0); } #define jso_decode_list { /** jso_decode_list(json): Return a JSOnion-compatible list representing the JSON string . JSOnion version: 1.0.0d */ return __jso_gmt_elem(_jso_decode_list(argument0, 1), 0); } #define jso_decode_string { /** jso_decode_string(json): Return a string representing the JSON string . JSOnion version: 1.0.0d */ return __jso_gmt_elem(_jso_decode_string(argument0, 1), 0); } #define jso_decode_boolean { /** jso_decode_boolean(json): Return a boolean value representing the JSON string . JSOnion version: 1.0.0d */ return __jso_gmt_elem(_jso_decode_boolean(argument0, 1), 0); } #define jso_decode_real { /** jso_decode_real(json): Return a real value representing the JSON string . JSOnion version: 1.0.0d */ return __jso_gmt_elem(_jso_decode_real(argument0, 1), 0); } #define jso_decode_integer { /** jso_decode_integer(json): Return an integer value representing the JSON string . JSOnion version: 1.0.0d */ return __jso_gmt_elem(_jso_decode_integer(argument0, 1), 0); } #define _jso_decode_map { /** _jso_decode_map(json, startindex): Extract a map from JSON string starting at position . Return a 2-tuple of the extracted map handle and the position after the ending }. JSOnion version: 1.0.0d */ var i, len, map; i = argument1; len = string_length(argument0); map = jso_new_map(); //Seek to first { var c; c = string_char_at(argument0, i); if (c != "{") { do { i += 1; c = string_char_at(argument0, i); if (!_jso_is_whitespace_char(c)) && (c != "{") { show_error("Cannot parse map at position " + string(i), true); } } until (c == "{") } i += 1; //Read until end of JSON or ending } var found_end, state, found, current_key; found_end = false; state = 0; for (i=i; i<=len && !found_end; i+=1) { c = string_char_at(argument0, i); switch (state) { //0: Looking for a key or closing } case 0: switch (c) { case "}": found_end = true; break; case '"': found = _jso_decode_string(argument0, i); current_key = __jso_gmt_elem(found, 0); i = __jso_gmt_elem(found, 1)-1; state = 1; break; case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "+": case "-": found = _jso_decode_real(argument0, i); current_key = __jso_gmt_elem(found, 0); i = __jso_gmt_elem(found, 1)-1; state = 1; break; default: if (!_jso_is_whitespace_char(c)) { show_error("Unexpected character at position " + string(i) + ".", true); } break; } break; //1: Looking for the : separator case 1: switch (c) { case ":": state = 2; break; default: if (!_jso_is_whitespace_char(c)) { show_error("Unexpected character at position " + string(i) + ".", true); } break; } break; //2: Looking for a value case 2: switch (c) { case "[": found = _jso_decode_list(argument0, i); jso_map_add_sublist(map, current_key, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 3; break; case "{": found = _jso_decode_map(argument0, i); jso_map_add_submap(map, current_key, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 3; break; case '"': found = _jso_decode_string(argument0, i); jso_map_add_string(map, current_key, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 3; break; case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "+": case "-": found = _jso_decode_real(argument0, i); jso_map_add_real(map, current_key, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 3; break; case "t": case "f": found = _jso_decode_boolean(argument0, i); jso_map_add_boolean(map, current_key, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 3; break; default: if (!_jso_is_whitespace_char(c)) { show_error("Unexpected character at position " + string(i) + ".", true); } break; } break; //3: Done looking for an entry, want comma or } case 3: switch (c) { case "}": found_end = true; break; case ",": state = 0; break; default: if (!_jso_is_whitespace_char(c)) { show_error("Unexpected character at position " + string(i) + ".", true); } break; } break; } } //Return extracted map with ending position if the ending } is found if (found_end) { return __jso_gmt_tuple(map, i); } //Ended too early, throw error else { show_error("Unexpected end of map in JSON string.", true); } } #define _jso_decode_list { /** _jso_decode_list(json, startindex): Extract a list from JSON string starting at position . Return a 2-tuple of the extracted list handle and the position after the ending ]. JSOnion version: 1.0.0d */ var i, len, list; i = argument1; len = string_length(argument0); list = jso_new_list(); //Seek to first [ var c; c = string_char_at(argument0, i); if (c != "[") { do { i += 1; c = string_char_at(argument0, i); if (!_jso_is_whitespace_char(c)) && (c != "[") { show_error("Cannot parse list at position " + string(i), true); } } until (c == "[") } i += 1; //Read until end of JSON or ending ] var found_end, state, found; found_end = false; state = 0; for (i=i; i<=len && !found_end; i+=1) { c = string_char_at(argument0, i); switch (state) { //0: Looking for an item case 0: switch (c) { case "]": found_end = true; break; case "[": found = _jso_decode_list(argument0, i); jso_list_add_sublist(list, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 1; break; case "{": found = _jso_decode_map(argument0, i); jso_list_add_submap(list, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 1; break; case '"': found = _jso_decode_string(argument0, i); jso_list_add_string(list, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 1; break; case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "+": case "-": found = _jso_decode_real(argument0, i); jso_list_add_real(list, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 1; break; case "t": case "f": found = _jso_decode_boolean(argument0, i); jso_list_add_boolean(list, __jso_gmt_elem(found, 0)); i = __jso_gmt_elem(found, 1)-1; state = 1; break; default: if (!_jso_is_whitespace_char(c)) { show_error("Unexpected character at position " + string(i) + ".", true); } break; } break; //1: Done looking for an item, want comma or ] case 1: switch (c) { case "]": found_end = true; break; case ",": state = 0; break; default: if (!_jso_is_whitespace_char(c)) { show_error("Unexpected character at position " + string(i) + ".", true); } break; } break; } } //Return extracted list with ending position if the ending ] is found if (found_end) { return __jso_gmt_tuple(list, i); } //Ended too early, throw error else { show_error("Unexpected end of list in JSON string.", true); } } #define _jso_decode_string { /** _jso_decode_string(json, startindex): Extract a string from JSON string starting at position . Return a 2-tuple of the extracted string and the position after the ending double quote. JSOnion version: 1.0.0d */ var i, len, str; i = argument1; len = string_length(argument0); str = ""; //Seek to first double quote var c; c = string_char_at(argument0, i); if (c != '"') { do { i += 1; c = string_char_at(argument0, i); } until (c == '"') } i += 1; //Read until end of JSON or ending double quote var found_end, escape_mode; found_end = false; escape_mode = false; for (i=i; i<=len && !found_end; i+=1) { c = string_char_at(argument0, i); //Escape mode if (escape_mode) { switch (c) { case '"': case "\": case "/": str += c; escape_mode = false; break; case "b": str += chr(8); escape_mode = false; break; case "f": str += chr(12); escape_mode = false; break; case "n": str += chr(10); escape_mode = false; break; case "r": str += chr(13); escape_mode = false; break; case "t": str += chr(9); escape_mode = false; break; case "u": var u; if (len-i < 5) { show_error("Invalid escape character at position " + string(i) + ".", true); } else { str += chr(_jso_hex_to_decimal(string_copy(argument0, i+1, 4))); escape_mode = false; i += 4; } break; default: show_error("Invalid escape character at position " + string(i) + ".", true); break; } } //Regular mode else { switch (c) { case '"': found_end = true; break; case "\": escape_mode = true; break; default: str += c; break; } } } //Return extracted string with ending position if the ending double quote is found if (found_end) { return __jso_gmt_tuple(str, i); } //Ended too early, throw error else { show_error("Unexpected end of string in JSON string.", true); } } #define _jso_decode_boolean { /** _jso_decode_boolean(json, startindex): Extract a boolean from JSON string starting at position . Return a 2-tuple of the extracted boolean and the position after the last e. JSOnion version: 1.0.0d */ var i, len, str; i = argument1; len = string_length(argument0); //Seek to first t or f that can be found var c; c = string_char_at(argument0, i); if (c != "t") && (c != "f") { do { i += 1; c = string_char_at(argument0, i); if (!_jso_is_whitespace_char(c)) && (c != "t") && (c != "f") { show_error("Cannot parse boolean value at position " + string(i), true); } } until (c == "t") || (c == "f") } //Look for true if t is found if (c == "t") && (string_copy(argument0, i, 4) == "true") { return __jso_gmt_tuple(true, i+4); } //Look for false if f is found else if (c == "f") && (string_copy(argument0, i, 5) == "false") { return __jso_gmt_tuple(false, i+5); } //Error: unexpected ending else { show_error("Unexpected end of boolean in JSON string.", true); } } #define _jso_decode_real { /** _jso_decode_real(json, startindex): Extract a real value from JSON string starting at position . Return a 2-tuple of the extracted real value and the position after the real value. JSOnion version: 1.0.0d */ var i, len, str; i = argument1; len = string_length(argument0); str = ""; //Seek to first character: +, -, or 0-9 var c; c = string_char_at(argument0, i); if (string_pos(c, "0123456789+-") == 0) { do { i += 1; c = string_char_at(argument0, i); if (!_jso_is_whitespace_char(c)) && (string_pos(c, "0123456789+-") == 0) { show_error("Cannot parse real value at position " + string(i), true); } } until (string_pos(c, "0123456789+-") > 0) } //Determine starting state var state; switch (c) { case "+": case "-": state = 0; break; default: state = 1; break; } str += c; i += 1; //Loop until no more digits found var done; done = false; for (i=i; i<=len && !done; i+=1) { c = string_char_at(argument0, i); switch (state) { //0: Found a sign, looking for a starting number case 0: switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; state = 1; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); break; } break; //1: Found a starting digit, looking for decimal dot, e, E, or more digits case 1: if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { done = true; i -= 1; } else { switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; break; case ".": str += c; state = 2; break; case "e": case "E": str += c; state = 3; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a dot, e, E or a digit.", true); break; } } break; //2: Found a decimal dot, looking for more digits case 2: switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; state = -2; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); break; } break; //-2: Found a decimal dot and a digit after it, looking for more digits, e, or E case -2: if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { done = true; i -= 1; } else { switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; break; case "e": case "E": str += c; state = 3; break; default: show_error("Unexpected character at position " + string(i) + ", expecting an e, E or a digit.", true); break; } } break; //3: Found an e/E, looking for +, - or more digits case 3: switch (c) { case "+": case "-": str += c; state = 4; break; case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; state = 5; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a +, - or a digit.", true); break; } break; //4: Found an e/E exponent sign, looking for more digits case 4: switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; state = 5; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); break; } break; //5: Looking for final digits of the exponent case 5: if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { done = true; i -= 1; } else { switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; state = 5; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); break; } } break; } } //Am I still expecting more characters? if (done) || (state == 1) || (state == -2) || (state == 5) { return __jso_gmt_tuple(real(str), i); } //Error: unexpected ending else { show_error("Unexpected end of real in JSON string.", true); } } #define _jso_decode_integer { /** _jso_decode_real(json, startindex): Extract a real value from JSON string starting at position . Return a 2-tuple of the extracted integer value (with leading i) and the position after the real value. JSOnion version: 1.0.0d */ var i, len, str; i = argument1; len = string_length(argument0); str = ""; //Seek to first character: +, -, or 0-9 var c; c = string_char_at(argument0, i); if (string_pos(c, "0123456789+-") == 0) { do { i += 1; c = string_char_at(argument0, i); if (!_jso_is_whitespace_char(c)) && (string_pos(c, "0123456789+-") == 0) { show_error("Cannot parse integer value at position " + string(i), true); } } until (string_pos(c, "0123456789+-") > 0) } //Determine starting state var state; switch (c) { case "+": case "-": state = 0; break; default: state = 1; break; } str += c; i += 1; //Loop until no more digits found var done; done = false; for (i=i; i<=len && !done; i+=1) { c = string_char_at(argument0, i); switch (state) { //0: Found a sign, looking for a starting number case 0: switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; state = 1; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); break; } break; //1: Found a starting digit, looking for decimal dot, e, E, or more digits case 1: if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { done = true; i -= 1; } else { switch (c) { case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": str += c; break; default: show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); break; } } break; } } //Am I still expecting more characters? if (done) || (state == 1) { return __jso_gmt_tuple(floor(real(str)), i); } //Error: unexpected ending else { show_error("Unexpected end of integer in JSON string.", true); } } #define jso_compare_maps { /** jso_compare_maps(map1, map2): Return whether the contents of JSOnion-compatible maps and are the same. JSOnion version: 1.0.0d */ //If they aren't the same size, they can't be the same var size; size = ds_map_size(argument0); if (size != ds_map_size(argument1)) { return false; } //Compare contents pairwise var i, k, type, a, b; k = ds_map_find_first(argument0); for (i=0; i and are the same. JSOnion version: 1.0.0d */ //If they aren't the same size, they can't be the same var size; size = ds_list_size(argument0); if (size != ds_list_size(argument1)) { return false; } //Compare contents pairwise var i, type, a, b; for (i=0; i, return whether a value exists there. JSOnion version: 1.0.0d */ //Catch empty calls if (argument_count < 2) { show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); } //Build list of keys/indices var i, key_list; key_list = ds_list_create(); for (i=1; i, return whether a value exists there. JSOnion version: 1.0.0d */ //Catch empty calls if (argument_count < 2) { show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); } //Build list of keys/indices var i, key_list; key_list = ds_list_create(); for (i=1; i, return the value that exists there. JSOnion version: 1.0.0d */ //Catch empty calls if (argument_count < 2) { show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); } //Build list of keys/indices var i, key_list; key_list = ds_list_create(); for (i=1; i, return the type of value that exists there. JSOnion version: 1.0.0d */ //Catch empty calls if (argument_count < 2) { show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); } //Build list of keys/indices var i, key_list; key_list = ds_list_create(); for (i=1; i, return the value that exists there. JSOnion version: 1.0.0d */ //Catch empty calls if (argument_count < 2) { show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); } //Build list of keys/indices var i, key_list; key_list = ds_list_create(); for (i=1; i, return the type of value that exists there. JSOnion version: 1.0.0d */ //Catch empty calls if (argument_count < 2) { show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); } //Build list of keys/indices var i, key_list; key_list = ds_list_create(); for (i=1; i= ds_list_size(data)) { switch (task_type) { case 0: return false; break; default: show_error("Index overflow for nested lists in " + type_string + " lookup.", true); break; } } type = jso_list_get_type(data, k); data = jso_list_get(data, k); break; //Trying to go through a leaf; don't attempt to look further default: switch (task_type) { case 0: return false; break; default: show_error("Recursive overflow in " + type_string + " lookup.", true); break; } break; } } //Can find something, return the value requested by the task switch (task_type) { case 0: return true; break; case 1: return data; break; case 2: return type; break; } } #define _jso_is_whitespace_char { /** _jso_is_whitespace_char(char): Return whether is a whitespace character. Definition of whitespace is given by Unicode 6.0, Chapter 4.6. JSOnion version: 1.0.0d */ switch (ord(argument0)) { case $0009: case $000A: case $000B: case $000C: case $000D: case $0020: case $0085: case $00A0: case $1680: case $180E: case $2000: case $2001: case $2002: case $2003: case $2004: case $2005: case $2006: case $2007: case $2008: case $2009: case $200A: case $2028: case $2029: case $202F: case $205F: case $3000: return true; } return false; } #define _jso_hex_to_decimal { /** _jso_hex_to_decimal(hex_string): Return the decimal value of the hex number represented by JSOnion version: 1.0.0d */ var hex_string, hex_digits; hex_string = string_lower(argument0); hex_digits = "0123456789abcdef"; //Convert digit-by-digit var i, len, digit_value, num; len = string_length(hex_string); num = 0; for (i=1; i<=len; i+=1) { digit_value = string_pos(string_char_at(hex_string, i), hex_digits)-1; if (digit_value >= 0) { num *= 16; num += digit_value; } //Unknown character else { show_error("Invalid hex number: " + argument0, true); } } return num; }