Scanner = {src: String, i: int}; fun newScanner(src: String) { scan = {src: src, i: 0}; assert(istypeof(Scanner, scan)); return scan; } fun isDigit(char: ?String) { if char == none: return false; return string.ord(char) >= string.ord('0') and string.ord(char) <= string.ord('9'); } fun isSpace(c: String) return c == ' ' or c == '\t' or c == '\n'; fun min(x, y) { if x < y: return x; return y; } fun hint(scan: Scanner, n: int = 1) { k = scan.i + n; if k < count(scan.src): return scan.src[k]; return none; } fun current(scan: Scanner) return hint(scan, 0); fun consume(scan: Scanner, n: int = 1) { assert(n > 0); limit = count(scan.src); scan.i = min(scan.i + n, limit); } fun consumeSpaces(scan: Scanner) while isSpace(current(scan)): consume(scan); fun parseString(scan: Scanner) { assert(current(scan) == '"'); consume(scan); # Consume the opening " temp = ""; char = current(scan); while char != '"' and char != none: { temp = string.cat(temp, char); consume(scan); char = current(scan); } if char == none: { val = none; err = "Source ended inside a string literal"; } else { assert(char == '"'); consume(scan); val = temp; err = none; } return val, err; } fun parseObject(scan: Scanner) { assert(current(scan) == '{'); consume(scan); obj = {}; consumeSpaces(scan); if current(scan) != '}': do { key, err = parseValue(scan); if err != none: return none, err; if type(key) != String: return none, "Object key isn't a string"; consumeSpaces(scan); if current(scan) != ':': return none, "Missing ':' separator after key"; consume(scan); val, err = parseValue(scan); if err != none: return none, err; if obj[key] != none: return none, "Duplicate key in object"; obj[key] = val; consumeSpaces(scan); if current(scan) == '}': done = true; else { if current(scan) != ',': return none, "Invalid character where either ',' or '}' were expected"; consume(scan); # Consume the "," done = false; } } while not done; assert(current(scan) == '}'); consume(scan); return obj; } fun parseArray(scan: Scanner) { assert(current(scan) == '['); consume(scan); # Consume the [ array = []; consumeSpaces(scan); if current(scan) != ']': do { val, err = parseValue(scan); if err != none: return none, err; array[count(array)] = val; consumeSpaces(scan); if current(scan) == ']': done = true; else { if current(scan) != ',': return none, "Invalid character where either ',' or ']' were expected"; consume(scan); # Consume the "," done = false; } } while not done; assert(current(scan) == ']'); consume(scan); return array; } fun parseFalse(scan: Scanner) { assert(current(scan) == 'f'); consume(scan); if current(scan) != 'a': return none, "Invalid character after 'f'"; consume(scan); if current(scan) != 'l': return none, "Invalid character after 'fa'"; consume(scan); if current(scan) != 's': return none, "Invalid character after 'fal'"; consume(scan); if current(scan) != 'e': return none, "Invalid character after 'fals'"; consume(scan); return false; } fun parseTrue(scan: Scanner) { assert(current(scan) == 't'); consume(scan); if current(scan) != 'r': return none, "Invalid character after 't'"; consume(scan); if current(scan) != 'u': return none, "Invalid character after 'tr'"; consume(scan); if current(scan) != 'e': return none, "Invalid character after 'tru'"; consume(scan); return true; } fun parseNull(scan: Scanner) { assert(current(scan) == 'n'); consume(scan); if current(scan) != 'u': return none, "Invalid character after 'n'"; consume(scan); if current(scan) != 'l': return none, "Invalid character after 'nu'"; consume(scan); if current(scan) != 'l': return none, "Invalid character after 'nul'"; consume(scan); return none; } fun integerFromDigit(char: String) { assert(count(char) == 1); return string.ord(char) - string.ord('0'); } fun parseNumber(scan: Scanner) { char = current(scan); sign = 1; if char == '+' or char == '-': { sign = {'+': 1, '-': -1}[char]; consume(scan); char = current(scan); if not isDigit(char): return none, "Invalid character after +/-"; } temp = 0.0; do { assert(isDigit(char)); n = integerFromDigit(char); temp = temp * 10 + n; consume(scan); char = current(scan); } while isDigit(char); if char == '.' and isDigit(hint(scan, 1)): { consume(scan); # Consume the dot char = current(scan); q = 1.0; do { q = q / 10; n = integerFromDigit(char); temp = temp + q * n; consume(scan); char = current(scan); } while isDigit(char); } return sign * temp; } fun parseValue(scan: Scanner) { table = { '"': parseString, '{': parseObject, '[': parseArray, 'f': parseFalse, 't': parseTrue, 'n': parseNull, '+': parseNumber, '-': parseNumber, '0': parseNumber, '1': parseNumber, '2': parseNumber, '3': parseNumber, '4': parseNumber, '5': parseNumber, '6': parseNumber, '7': parseNumber, '8': parseNumber, '9': parseNumber }; consumeSpaces(scan); char = current(scan); if char == none: { val = none; err = "Source ended where a value was expected"; } else { callback = table[char]; if callback == none: { val = none; err = "Invalid character where a value was expected"; } else (val, err) = callback(scan); } return (val, err); } fun parse(src: String) { scan = newScanner(src); val, err = parseValue(scan); return val, err; }