Files
Noja/examples/json/json.noja
T
2022-12-06 23:58:13 +01:00

304 lines
6.5 KiB
Plaintext

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;
}