|
#define ggon_decode |
|
// real/string ggon_decode(string text) |
|
// Decodes a GGON (Gang Garrison Object Notation) text |
|
// Returns either a string or a ds_map handle |
|
|
|
var text; |
|
text = argument0; |
|
|
|
var tokens; |
|
tokens = ggon_tokenise(text); |
|
|
|
var result; |
|
result = ggon_parse_tokens(tokens); |
|
|
|
ds_queue_destroy(tokens); |
|
|
|
return result; |
|
|
|
#define ggon_encode |
|
// string ggon_encode(real/string value) |
|
// Encodes a ds_map or string to a GGON (Gang Garrison Object Notation) text |
|
// Encoding is recursive, real values in the maptreated as ds_map handles |
|
// Returns the encoded text |
|
|
|
var value; |
|
value = argument0; |
|
|
|
var out; |
|
|
|
// string value |
|
if (is_string(value)) |
|
{ |
|
// Check if alphanumeric |
|
var alphanumeric, i, char; |
|
// We don't set alphanumeric to true ahead of time because of empty strings |
|
alphanumeric = false; |
|
for (i = 1; i <= string_length(value); i += 1) |
|
{ |
|
char = string_char_at(value, i); |
|
if (('a' <= char and char <= 'z') or ('A' <= char and char <= 'Z') or ('0' <= char and char <= '9') or char == '_' or char == '.' or char == '+' or char == '-') |
|
{ |
|
alphanumeric = true; |
|
} |
|
else |
|
{ |
|
alphanumeric = false; |
|
break; |
|
} |
|
} |
|
|
|
// As no quoting is necessary, just output verbatim |
|
if (alphanumeric) |
|
{ |
|
out = value; |
|
return out; |
|
} |
|
|
|
out = "'"; |
|
for (i = 1; i <= string_length(value); i += 1) |
|
{ |
|
char = string_char_at(value, i); |
|
// ' and \ are escaped as \' and '' |
|
if (char == "'" or char == "\") |
|
out += '\' + char; |
|
// newlines, carriage returns, tabs and null bytes are escaped specially |
|
else if (char == chr(10)) |
|
out += '\n'; |
|
else if (char == chr(13)) |
|
out += '\r'; |
|
else if (char == chr(9)) |
|
out += '\t'; |
|
else if (char == chr(0)) |
|
out += '\0'; |
|
// Otherwise we can just output verbatim |
|
else |
|
out += char; |
|
} |
|
out += "'"; |
|
|
|
return out; |
|
// not string, therefore real, therefore ds_map representing map value |
|
} else { |
|
out = "{"; |
|
|
|
var key, first; |
|
first = true; |
|
for (key = ds_map_find_first(value); is_string(key); key = ds_map_find_next(value, key)) |
|
{ |
|
if (!first) |
|
out += ','; |
|
else |
|
first = false; |
|
out += ggon_encode(key) + ':' + ggon_encode(ds_map_find_value(value, key)); |
|
} |
|
|
|
out += "}"; |
|
|
|
return out; |
|
} |
|
|
|
#define ggon_tokenise |
|
// real ggon_tokenise(string text) |
|
// Tokenises a GGON (Gang Garrison Object Notation) text |
|
// Returns a ds_queue of tokens |
|
// For each token, list has type ("punctuation" or "string") and value |
|
// Thus you must iterate over the list two elements at a time |
|
|
|
var text; |
|
text = argument0; |
|
|
|
var tokens; |
|
tokens = ds_queue_create(); |
|
|
|
while (string_length(text) > 0) |
|
{ |
|
var char; |
|
char = string_char_at(text, 1); |
|
|
|
// basic punctuation: '{', '}', ':' and ',' |
|
if (char == '{' or char == '}' or char == ':' or char == ',') |
|
{ |
|
ds_queue_enqueue(tokens, 'punctuation'); |
|
ds_queue_enqueue(tokens, char); |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
continue; |
|
} |
|
|
|
// skip whitespace (space, tab, new line or carriage return) |
|
if (char == ' ' or char == chr(9) or char == chr(10) or char == chr(13)) |
|
{ |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
continue; |
|
} |
|
|
|
// "identifiers" (bare word strings, really) of format [a-zA-Z0-9_]+ |
|
if (('a' <= char and char <= 'z') or ('A' <= char and char <= 'Z') or ('0' <= char and char <= '9') or char == '_' or char == '.' or char == '+' or char == '-') |
|
{ |
|
var identifier; |
|
identifier = ''; |
|
while (('a' <= char and char <= 'z') or ('A' <= char and char <= 'Z') or ('0' <= char and char <= '9') or char == '_' or char == '.' or char == '+' or char == '-') |
|
{ |
|
if (string_length(text) == 0) |
|
show_error('Error when tokenising GGON: unexpected end of text while parsing string', true); |
|
identifier += char; |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
char = string_char_at(text, 1); |
|
} |
|
ds_queue_enqueue(tokens, 'string'); |
|
ds_queue_enqueue(tokens, identifier); |
|
continue; |
|
} |
|
|
|
// string |
|
if (char == "'") |
|
{ |
|
var str; |
|
str = ''; |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
char = string_char_at(text, 1); |
|
while (char != "'") |
|
{ |
|
if (string_length(text) == 0) |
|
show_error('Error when tokenising GGON: unexpected end of text while parsing string', true); |
|
// escaping |
|
if (char == '\') |
|
{ |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
char = string_char_at(text, 1); |
|
if (char == "'" or char == '\') |
|
str += char; |
|
// new line escape |
|
else if (char == 'n') |
|
str += chr(10); |
|
// carriage return escape |
|
else if (char == 'r') |
|
str += chr(13); |
|
// tab escape |
|
else if (char == 't') |
|
str += chr(9); |
|
// null byte escape |
|
else if (char == '0') |
|
str += chr(0); |
|
else |
|
show_error('Error when tokenising GGON: unknown escape sequence "\' + char + '"', true); |
|
} |
|
else |
|
{ |
|
str += char; |
|
} |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
char = string_char_at(text, 1); |
|
} |
|
if (char != "'") |
|
show_error('Error when tokenising GGON: unexpected character "' + char + '" while parsing string, expected "' + "'" + '"', true); |
|
text = string_copy(text, 2, string_length(text) - 1); |
|
char = string_char_at(text, 1); |
|
|
|
ds_queue_enqueue(tokens, 'string'); |
|
ds_queue_enqueue(tokens, str); |
|
continue; |
|
} |
|
|
|
show_error('Error when tokenising GGON: unexpected character "' + char + '"', true); |
|
} |
|
|
|
return tokens; |
|
|
|
#define ggon_parse_tokens |
|
// real/string ggon_decode(real tokens) |
|
// Decodes a tokenised GGON (Gang Garrison Object Notation) text ds_queue |
|
// Returns either a string or a ds_map handle |
|
|
|
var tokens; |
|
tokens = argument0; |
|
|
|
var tokenType, tokenValue; |
|
while (!ds_queue_empty(tokens)) |
|
{ |
|
tokenType = ds_queue_dequeue(tokens); |
|
tokenValue = ds_queue_dequeue(tokens); |
|
|
|
if (tokenType == 'string') |
|
return tokenValue; |
|
|
|
if (tokenType == 'punctuation') |
|
{ |
|
// GGON has only two primitives - it could only be string or opening { |
|
if (tokenValue != '{') |
|
show_error('Error when parsing GGON: unexpected token "' + tokenValue + '"', true); |
|
|
|
var map; |
|
map = ds_map_create(); |
|
|
|
tokenType = ds_queue_head(tokens); |
|
if (tokenType == 'punctuation') |
|
{ |
|
tokenType = ds_queue_dequeue(tokens); |
|
tokenValue = ds_queue_dequeue(tokens); |
|
|
|
// { can only be followed by } or a key |
|
if (tokenValue != '}') |
|
show_error('Error when parsing GGON: unexpected token "' + tokenValue + '" after opening {', true); |
|
|
|
// It's {} so we can just return our empty map |
|
return map; |
|
} |
|
else if (tokenType == 'string') |
|
{ |
|
// Parse each key of our map |
|
while (!ds_queue_empty(tokens)) |
|
{ |
|
tokenType = ds_queue_dequeue(tokens); |
|
tokenValue = ds_queue_dequeue(tokens); |
|
|
|
var key; |
|
key = tokenValue; |
|
|
|
tokenType = ds_queue_dequeue(tokens); |
|
tokenValue = ds_queue_dequeue(tokens); |
|
|
|
// Following token must be a : as we have a key |
|
if (tokenType != 'punctuation') |
|
show_error('Error when parsing GGON: unexpected ' + tokenType + ' after key', true); |
|
if (tokenValue != ':') |
|
show_error('Error when parsing GGON: unexpected token "' + tokenValue + '" after key', true); |
|
|
|
// Now we recurse to parse our value! |
|
var value; |
|
value = ggon_parse_tokens(tokens); |
|
|
|
ds_map_add(map, key, value); |
|
|
|
tokenType = ds_queue_dequeue(tokens); |
|
tokenValue = ds_queue_dequeue(tokens); |
|
|
|
// After key, colon and value, next token must be , or } |
|
if (tokenType != 'punctuation') |
|
show_error('Error when parsing GGON: unexpected ' + tokenType + ' after value', true); |
|
if (tokenValue == ',') |
|
continue; |
|
else if (tokenValue == '}') |
|
return map; |
|
else |
|
show_error('Error when parsing GGON: unexpected token "' + tokenValue + '" after value', true); |
|
} |
|
} |
|
else |
|
show_error('Error when parsing GGON: unknown token type "' + tokenType + '"', true); |
|
} |
|
|
|
show_error('Error when parsing GGON: unknown token type "' + tokenType + '"', true); |
|
} |
|
|
|
#define ggon_list_to_map |
|
// real ggon_list_to_map(real list) |
|
// Takes a ds_list |
|
// Returns a ds_map which has a "length" key and a string key for each index |
|
// This is a function to make dealing with GGON easier, as GGON lacks lists |
|
|
|
var list; |
|
list = argument0; |
|
|
|
var map; |
|
map = ds_map_create(); |
|
|
|
ds_map_add(map, "length", string(ds_list_size(list))); |
|
|
|
var i; |
|
for (i = 0; i < ds_list_size(list); i += 1) |
|
{ |
|
ds_map_add(map, string(i), ds_list_find_value(list, i)); |
|
} |
|
|
|
return map; |
|
|
|
#define ggon_map_to_list |
|
// real ggon_map_to_list(real map) |
|
// Takes a ds_map which has a "length" key and string indexes up to length |
|
// Returns a ds_list which has the values of those indexes in order |
|
// This is a function to make dealing with GGON easier, as GGON lacks lists |
|
// This is designed to work with the map ggon_list_to_map would produce |
|
|
|
var map; |
|
map = argument0; |
|
|
|
var list, length; |
|
list = ds_list_create(); |
|
length = real(ds_map_find_value(map, 'length')); |
|
|
|
var i; |
|
for (i = 0; i < length; i += 1) |
|
ds_list_add(list, ds_map_find_value(map, string(i))); |
|
|
|
return list; |
|
|
|
#define ggon_destroy_map |
|
// void ggon_destroy_map(real map) |
|
// Destroys a ds_map recursively (real values assumed to be ds_maps) |
|
|
|
var map; |
|
map = argument0; |
|
|
|
var key, value; |
|
for (key = ds_map_find_first(map); is_string(key); key = ds_map_find_next(map, key)) |
|
{ |
|
value = ds_map_find_value(map, key); |
|
if (is_real(value)) |
|
ggon_destroy_map(value); |
|
} |
|
|
|
ds_map_destroy(map); |
|
|
"Only single quotes are allowed because double quotes are dumb."
I'm sure double quotes think you are dumb too.