304 lines
6.5 KiB
Plaintext
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;
|
|
} |