module JsonScanning {
import { ../Json; }
// Json Token
pub union JsonToken {
JsonLeftBraceToken{}
JsonRightBraceToken{}
JsonLeftBracketToken{}
JsonRightBracketToken{}
JsonColonToken{}
JsonCommaToken{}
JsonNullToken{}
JsonBoolToken{val : bool}
JsonStringToken{val : string}
JsonNumberToken{val : double}
JsonEofToken{}
}
// Json Scanner
pub mut struct JsonScanner {
source : string;
offset: uint = 0;
}
peek(this : JsonScanner) : int => {
if (this.offset < this.source#) {
return this.source[offset];
}
return -1;
}
advance(this : JsonScanner) {
this.offset += 1;
}
pub scan(this : JsonScanner) : JsonToken => {
let ch = this.peek();
if (ch < 0) {
return JsonEof;
}
return match (ch::char) {
'"' => this.scanString();
'-' | '0'..'9' => this.scanNumber();
'a'..'z' => this.scanName();
':' => this.scanChar(JsonColonToken);
',' => this.scanChar(JsonCommaToken);
'[' => this.scanChar(JsonLeftBracketToken);
']' => this.scanChar(JsonRightBracketToken);
'{' => this.scanChar(JsonLeftBraceToken);
'}' => this.scanChar(JsonRightBraceToken);
@x => throw JsonUnexpectedChar(x);
}
}
scanChar(this : JsonScanner, token : JsonToken) : JsonToken => {
let start = this.offset;
this.advance();
return token;
}
scanName(this : JsonScanner) : JsonToken => {
let start = this.offset;
loop {
let ch = this.peek();
if (ch >= 'a' && ch <= 'z') {
this.advance();
} else {
break;
}
}
let length = this.offset - start;
return cond {
length == 4 &&
this.source[start] == 't' &&
this.source[start + 1] == 'r' &&
this.source[start + 2] == 'u' &&
this.source[start + 3] == 'e' => JsonBoolToken(true);
length == 4 &&
this.source[start] == 'n' &&
this.source[start + 1] == 'u' &&
this.source[start + 2] == 'l' &&
this.source[start + 3] == 'l' => JsonNullToken;
length == 5 &&
this.source[start] == 'f' &&
this.source[start + 1] == 'a' &&
this.source[start + 2] == 'l' &&
this.source[start + 3] == 's' &&
this.source[start + 4] == 'e' => JsonBoolToken(false);
_ => throw JsonUnexpectedName(this.source.slice(start, this.offset));
}
}
scanNumber(this : JsonScanner) : JsonNumberToken => {
let start = this.offset;
if (this.peek() == '-') {
this.advance();
}
loop {
let ch = this.peek();
if (ch >= '0' && ch <= '9') {
this.advance();
} else {
break;
}
}
// TODO: fractions and exponents, e.g. 1.2 and 1.2e+3
let digits = this.source.slice(start, this.offset);
let value = parse{double}(digits);
return JsonNullToken(value);
}
scanString(this : JsonScanner) : JsonStringToken => {
this.advance();
let start = this.offset;
loop {
let ch = this.peek();
if (ch < 0) {
throw JsonUnexpectedEof();
}
if (ch != '"') {
this.advance();
} else {
break;
}
}
// TODO: escape characters, e.g. \
let value = this.source.slice(start, this.offset);
this.advance();
return JsonStringToken(value);
}
}