fun isSpace(c: String) return c == " " or c == "\t" or c == "\n"; fun isDigit(c: String) { u = unicode(c); d = u - unicode("0"); return d >= 0 and d <= 9; } fun skipSpaces(context: Map) { # Skip zero or more whitespace characters. while context.cur < count(context.str) and isSpace(context.str[context.cur]): context.cur = context.cur + 1; } fun parseNull(context: Map) { assert(context.cur < count(context.str) and context.str[context.cur] == 'n'); if context.cur+3 > count(context.str) or context.str[context.cur+1] != 'u' or context.str[context.cur+2] != 'l' or context.str[context.cur+3] != 'l': return none, "Unexpected token starting with \"n\""; context.cur = context.cur + count("null"); return none; } fun parseTrue(context: Map) { assert(context.cur < count(context.str) and context.str[context.cur] == 't'); if context.cur+3 > count(context.str) or context.str[context.cur+1] != 'r' or context.str[context.cur+2] != 'u' or context.str[context.cur+3] != 'e': return none, "Unexpected token starting with \"t\""; context.cur = context.cur + count("true"); return true; } fun parseFalse(context: Map) { assert(context.cur < count(context.str) and context.str[context.cur] == 'f'); if context.cur+3 > count(context.str) or context.str[context.cur+1] != 'a' or context.str[context.cur+2] != 'l' or context.str[context.cur+3] != 's' or context.str[context.cur+4] != 'e': return none, "Unexpected token starting with \"f\""; context.cur = context.cur + count("false"); return false; } fun dummy() {} Func = type(dummy); fun skip(context: Map, test_callback: Func) { k = context.cur; while k < count(context.str) and test_callback(context.str[k]): k = k + 1; return k; } fun parseInteger(context: Map) { assert(context.cur < count(context.str) and isDigit(context.str[context.cur])); buffer = 0; do { c = context.str[context.cur]; d = unicode(c) - unicode('0'); buffer = buffer * 10 + d; context.cur = context.cur + 1; } while context.cur < count(context.str) and isDigit(context.str[context.cur]); return buffer; } fun parseFloating(context: Map) { assert(context.cur < count(context.str) and isDigit(context.str[context.cur])); # It's ensured by the caller (parseIntegerOrFloating) # that now the strings contains two sequences of # digits with a dot in the middle. buffer = 0.0; # Scan the integer part. do { c = context.str[context.cur]; d = unicode(c) - unicode('0'); buffer = buffer * 10 + d; context.cur = context.cur + 1; } while context.str[context.cur] != "."; assert(isDigit(context.str[context.cur+1])); # Scan the decimal part. q = 1; do { c = context.str[context.cur]; d = unicode(c) - unicode('0'); q = q / 10; buffer = buffer + q * d; context.cur = context.cur + 1; } while context.cur < count(context.str) and isDigit(context); return buffer; } fun parseIntegerOrFloating(context: Map) { assert(context.cur < count(context.str) and isDigit(context.str[context.cur])); # Is the first digit sequence followed by a dot # and then one or more digits? k = skip(context, isDigit); if k+1 < count(context.str) and context.str[k] == "." and isDigit(context.str[k+1]): # It's a floating point value, then! return parseFloating(context); else # It's good old integer! return parseInteger(context); } fun parseString(context: Map) { assert(context.cur < count(context.str) and context.str[context.cur] == '"'); context.cur = context.cur + 1; buffer = ""; while context.cur < count(context.str) and context.str[context.cur] != '"': { # TODO: Support special characters such as \n, \t ecc buffer = strcat(buffer, context.str[context.cur]); context.cur = context.cur + 1; } if context.cur == count(context.str): return none, "Source string ended inside a string literal"; context.cur = context.cur + 1; return buffer; } fun parseArray(context: Map) { assert(context.cur < count(context.str) and context.str[context.cur] == '['); context.cur = context.cur + 1; skipSpaces(context); if context.cur == count(context.str): return none, "Source string ended inside an array"; if context.str[context.cur] == ']': return []; list = []; while true: { item, err = parseValue(context); if err != none: return none, err; list[count(list)] = item; skipSpaces(context); if context.cur == count(context.str): return none, "Source string ended inside an array"; if context.str[context.cur] == ']': break; if context.str[context.cur] != ',': return none, "Unexpected token inside an array, where either ',' or ']' were expected"; context.cur = context.cur + 1; skipSpaces(context); } assert(context.cur < count(context.str) and context.str[context.cur] == ']'); context.cur = context.cur + 1; return list; } fun parseValue(context: Map) { if context.cur == count(context.str): return none, "Source ended where a value was expected"; c = context.str[context.cur]; if c == "{": val, err = parseObject(context); else if c == "[": val, err = parseArray(context); else if c == '"': val, err = parseString(context); else if isDigit(c): val, err = parseIntegerOrFloating(context); else if c == 'n': val, err = parseNull(context); else if c == 't': val, err = parseTrue(context); else if c == 'f': val, err = parseFalse(context); else { val = none; err = strcat("Unexepected character \"", c, "\" where a value was expected"); } return val, err; } fun parse(str: String) { context = {str: str, cur: 0}; skipSpaces(context); val, err = parseValue(context); return val, err; } fun compareAny(A, B) { fun compareLists(A: List, B: List) { n = count(A); if n != count(B): return flase; i = 0; while i < n: { if not compareAny(A[i], B[i]): return false; i = i + 1; } return true; } T = type(A); if T != type(B): return false; if T == List: return compareLists(A, B); if T == Map: error("Maps aren't supported yet!"); return A == B; }