///|
pub(all) suberror ParseError {
InvalidJson(position~ : Int, reason~ : String)
DepthLimit(Int)
} derive(Debug)
///|
pub extend ParseError with @debug.Debug::{to_repr}
///|
priv struct JsonParser {
source : String
mut offset : Int
limit : Int
}
///|
/// Parse JSON while retaining every numeric literal in Json::Number's repr.
/// Duplicate object keys use the last value. Offsets are UTF-16 code units.
pub fn parse(
source : StringView,
max_nesting_depth? : Int = 128,
) -> Json raise ParseError {
if max_nesting_depth < 1 {
raise DepthLimit(max_nesting_depth)
}
let parser = {
source: source.to_owned(),
offset: 0,
limit: max_nesting_depth,
}
let value = parser.value(0)
parser.whitespace()
if parser.offset != parser.source.length() {
parser.invalid("unexpected trailing input")
}
value
}
///|
fn JsonParser::invalid(
self : JsonParser,
reason : String,
) -> Unit raise ParseError {
raise InvalidJson(position=self.offset, reason~)
}
///|
fn JsonParser::peek(self : JsonParser) -> Char? {
self.source.get_char(self.offset)
}
///|
fn JsonParser::whitespace(self : JsonParser) -> Unit {
while self.offset < self.source.length() {
match self.peek() {
Some(' ' | '\t' | '\r' | '\n') => self.offset += 1
_ => break
}
}
}
///|
fn JsonParser::consume(self : JsonParser, expected : Char) -> Bool {
if self.peek() == Some(expected) {
self.offset += 1
true
} else {
false
}
}
///|
fn JsonParser::expect(
self : JsonParser,
expected : Char,
) -> Unit raise ParseError {
self.whitespace()
if !self.consume(expected) {
self.invalid("expected '" + expected.to_string() + "'")
}
}
///|
fn JsonParser::string(self : JsonParser) -> String raise ParseError {
let start = self.offset
if !self.consume('"') {
self.invalid("expected a string")
}
let mut escaped = false
while self.offset < self.source.length() {
// Scan code units: slicing at a quote remains safe across surrogate pairs.
let unit = self.source[self.offset]
self.offset += 1
if escaped {
escaped = false
continue
}
if unit == 92 {
escaped = true
continue
}
if unit == 34 {
let token = self.source[start:self.offset]
let decoded = @json.parse(token) catch {
_ => {
self.invalid("invalid string escape")
Json::null()
}
}
match decoded {
String(text) => return text
_ => self.invalid("expected a string token")
}
}
if unit < 32 {
self.invalid("unescaped control character")
}
}
self.invalid("unterminated string")
""
}
///|
fn JsonParser::digits(self : JsonParser) -> Int {
let start = self.offset
while self.peek() is Some(c) && c >= '0' && c <= '9' {
self.offset += 1
}
self.offset - start
}
///|
fn JsonParser::number(self : JsonParser) -> Json raise ParseError {
let start = self.offset
ignore(self.consume('-'))
if self.consume('0') {
if self.peek() is Some(c) && c >= '0' && c <= '9' {
self.invalid("leading zero in number")
}
} else if self.digits() == 0 {
self.invalid("number requires integer digits")
}
if self.consume('.') && self.digits() == 0 {
self.invalid("number requires fractional digits")
}
if self.consume('e') || self.consume('E') {
if !self.consume('+') {
ignore(self.consume('-'))
}
if self.digits() == 0 {
self.invalid("number requires exponent digits")
}
}
let token = self.source[start:self.offset].to_owned()
if ExactNumber::from_literal(token) is None {
self.invalid("number exceeds supported decimal representation")
}
let binary = match literal_double(token) {
Some(number) => number
None => {
self.invalid("invalid number")
0.0
}
}
Json::number(binary, repr=token)
}
///|
fn JsonParser::literal(
self : JsonParser,
spelling : String,
value : Json,
) -> Json raise ParseError {
if !self.source[self.offset:].has_prefix(spelling) {
self.invalid("invalid literal")
}
self.offset += spelling.length()
value
}
///|
fn JsonParser::value(self : JsonParser, depth : Int) -> Json raise ParseError {
if depth > self.limit {
raise DepthLimit(self.limit)
}
self.whitespace()
match self.peek() {
Some('"') => Json::string(self.string())
Some('t') => self.literal("true", Json::boolean(true))
Some('f') => self.literal("false", Json::boolean(false))
Some('n') => self.literal("null", Json::null())
Some('-' | '0'..='9') => self.number()
Some('[') => {
self.offset += 1
let values : Array[Json] = []
self.whitespace()
if self.consume(']') {
return Json::array(values)
}
while true {
values.push(self.value(depth + 1))
self.whitespace()
if self.consume(']') {
break
}
self.expect(',')
}
Json::array(values)
}
Some('{') => {
self.offset += 1
let fields : Map[String, Json] = Map([])
self.whitespace()
if self.consume('}') {
return Json::object(fields)
}
while true {
self.whitespace()
let key = self.string()
self.expect(':')
fields.set(key, self.value(depth + 1))
self.whitespace()
if self.consume('}') {
break
}
self.expect(',')
}
Json::object(fields)
}
_ => {
self.invalid("expected a JSON value")
Json::null()
}
}
}