// A JSON parser that keeps the exact text of every number (in
// `Json::Number(_, repr=Some(text))`), so that readers can check that a
// number is an integer and read 64-bit integers without loss.
///|
priv struct JsonParser {
s : StringView
mut i : Int
mut depth : Int
}
///|
let max_nesting_depth : Int = 1024
///|
fn[T] JsonParser::fail(self : JsonParser, msg : String) -> T raise JsonError {
// compute the line and column of the current position
let mut line = 1
let mut col = 1
for k in 0..= self.s.length() {
"unexpected end of input"
} else {
msg
}
raise JsonError("invalid JSON: \{msg} at line \{line}, column \{col}")
}
///|
fn JsonParser::peek(self : JsonParser) -> Int {
if self.i < self.s.length() {
self.s[self.i].to_int()
} else {
-1
}
}
///|
fn JsonParser::skip_blanks(self : JsonParser) -> Unit {
while self.peek() is (' ' | '\t' | '\n' | '\r') {
self.i += 1
}
}
///|
fn JsonParser::expect_word(
self : JsonParser,
word : String,
value : Json,
) -> Json raise JsonError {
for c in word {
if self.peek() != c.to_int() {
self.fail("unexpected character")
}
self.i += 1
}
value
}
///|
fn JsonParser::value(self : JsonParser) -> Json raise JsonError {
self.skip_blanks()
match self.peek() {
'{' => self.object()
'[' => self.array()
'"' => Json::string(self.string())
't' => self.expect_word("true", Json::boolean(true))
'f' => self.expect_word("false", Json::boolean(false))
'n' => self.expect_word("null", Json::null())
'-' | '0'..='9' => self.number()
-1 => self.fail("unexpected end of input")
_ => self.fail("unexpected character")
}
}
///|
fn JsonParser::enter(self : JsonParser) -> Unit raise JsonError {
self.depth += 1
if self.depth > max_nesting_depth {
self.fail("too deeply nested")
}
}
///|
fn JsonParser::object(self : JsonParser) -> Json raise JsonError {
self.enter()
self.i += 1 // {
let m : Map[String, Json] = Map([])
self.skip_blanks()
if self.peek() == '}' {
self.i += 1
} else {
for ;; {
self.skip_blanks()
if self.peek() != '"' {
self.fail("expected a string")
}
let key = self.string()
self.skip_blanks()
if self.peek() != ':' {
self.fail("expected ':'")
}
self.i += 1
m[key] = self.value()
self.skip_blanks()
match self.peek() {
',' => self.i += 1
'}' => {
self.i += 1
break
}
_ => self.fail("expected ',' or '}'")
}
}
}
self.depth -= 1
Json::object(m)
}
///|
fn JsonParser::array(self : JsonParser) -> Json raise JsonError {
self.enter()
self.i += 1 // [
let l = []
self.skip_blanks()
if self.peek() == ']' {
self.i += 1
} else {
for ;; {
l.push(self.value())
self.skip_blanks()
match self.peek() {
',' => self.i += 1
']' => {
self.i += 1
break
}
_ => self.fail("expected ',' or ']'")
}
}
}
self.depth -= 1
Json::array(l)
}
///|
fn hex_value(c : Int) -> Int {
match c {
'0'..='9' => c - '0'
'a'..='f' => c - 'a' + 10
'A'..='F' => c - 'A' + 10
_ => -1
}
}
///|
fn JsonParser::string(self : JsonParser) -> String raise JsonError {
self.i += 1 // opening quote
let buf = StringBuilder()
for ;; {
let c = self.peek()
match c {
-1 => self.fail("unterminated string")
'"' => {
self.i += 1
break
}
'\\' => {
self.i += 1
let e = self.peek()
self.i += 1
match e {
'"' => buf.write_char('"')
'\\' => buf.write_char('\\')
'/' => buf.write_char('/')
'b' => buf.write_char('\b')
'f' => buf.write_char('\u{0C}')
'n' => buf.write_char('\n')
'r' => buf.write_char('\r')
't' => buf.write_char('\t')
'u' => {
let mut code = 0
for _ in 0..<4 {
let h = hex_value(self.peek())
if h < 0 {
self.fail("invalid \\u escape")
}
code = code * 16 + h
self.i += 1
}
// UTF-16 code units are copied as they are, which handles
// surrogate pairs
buf.write_char(code.unsafe_to_char())
}
_ => self.fail("invalid escape sequence")
}
}
c if c < 0x20 => self.fail("control character in string")
_ => {
buf.write_char(c.unsafe_to_char())
self.i += 1
}
}
}
buf.to_string()
}
///|
fn JsonParser::digits(self : JsonParser) -> Int {
let start = self.i
while self.peek() is ('0'..='9') {
self.i += 1
}
self.i - start
}
///|
fn JsonParser::number(self : JsonParser) -> Json raise JsonError {
let start = self.i
if self.peek() == '-' {
self.i += 1
}
match self.peek() {
'0' => self.i += 1
'1'..='9' => ignore(self.digits())
_ => self.fail("invalid number")
}
if self.peek() == '.' {
self.i += 1
if self.digits() == 0 {
self.fail("invalid number")
}
}
if self.peek() is ('e' | 'E') {
self.i += 1
if self.peek() is ('+' | '-') {
self.i += 1
}
if self.digits() == 0 {
self.fail("invalid number")
}
}
let text = self.s[start:self.i].to_owned()
let value = @string.parse_double(text) catch {
_ => self.fail("invalid number")
}
Json::number(value, repr=text)
}
///|
/// Parse a JSON string. Unlike `@json.parse`, the exact text of every
/// number is kept in `Json::Number(_, repr=Some(text))`, which lets the
/// readers of integers reject non-integers such as `1.5` or `1.0` and read
/// 64-bit integers without loss of precision.
pub fn parse(s : StringView) -> Json raise JsonError {
let p : JsonParser = { s, i: 0, depth: 0, }
let v = p.value()
p.skip_blanks()
if p.i < s.length() {
p.fail("unexpected data after the JSON value")
}
v
}