///|
/// copy_json copies bytes from a source reader until a valid JSON value is completed.
/// It returns the Json value, the number of bytes copied, and any error that occurred during the process.
pub fn copy_json(src : &ByteReader) -> (Json, Int64, IOError?) {
let ctx = ParseContext::make(src)
let val = ctx.parse_value() catch {
ParseError(e) => return (Json::null(), 0, Some(IOError(e.to_string())))
}
(val, ctx.offset.to_int64(), None)
}
// The following code is based upon: https://github.com/moonbitlang/core/blob/main/json/parse.mbt
// which has the following license:
// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
priv suberror ParseError {
ParseError(String)
} derive(Show)
///|
fn ParseContext::parse_value(ctx : ParseContext) -> Json raise ParseError {
let tok = ctx.lex_value(allow_rbracket=false)
ctx.parse_value2(tok)
}
///|
fn ParseContext::parse_value2(
ctx : ParseContext,
tok : Token,
) -> Json raise ParseError {
match tok {
Null => Json::null()
True => true.to_json()
False => false.to_json()
Number(n) => n.to_json()
String(s) => s.to_json()
LBrace => ctx.parse_object()
LBracket => ctx.parse_array()
RBracket | RBrace | Comma =>
raise ParseError(
"invalid JSON: unexpected '\{tok}' at offset \{ctx.offset}",
)
}
}
///|
fn ParseContext::parse_object(ctx : ParseContext) -> Json raise ParseError {
let map = Map::new()
loop ctx.lex_property_name() {
RBrace => map.to_json()
String(name) => {
ctx.lex_after_property_name()
map[name] = ctx.parse_value()
match ctx.lex_after_object_value() {
Comma => continue ctx.lex_property_name2()
RBrace => map.to_json()
tok =>
raise ParseError(
"invalid JSON: expected ',' or '}' at offset \{ctx.offset}, got '\{tok}'",
)
}
}
tok =>
raise ParseError(
"invalid JSON: expected '}' or property name at offset \{ctx.offset}, got '\{tok}'",
)
}
}
///|
fn ParseContext::parse_array(ctx : ParseContext) -> Json raise ParseError {
let vec = []
loop ctx.lex_value(allow_rbracket=true) {
RBracket => vec.to_json()
tok => {
vec.push(ctx.parse_value2(tok))
let tok2 = ctx.lex_after_array_value()
match tok2 {
Comma => continue ctx.lex_value(allow_rbracket=false)
RBracket => vec.to_json()
_ => raise ParseError("unexpected '\{tok2}' at offset \{ctx.offset}")
}
}
}
}
///|
priv struct ParseContext {
r : &ByteReader
buf : Buffer
mut offset : Int
}
///|
const DEFAULT_BUFFER_SIZE = 1024
///|
fn ParseContext::make(r : &ByteReader) -> ParseContext {
{ r, buf: Buffer::new(size_hint=DEFAULT_BUFFER_SIZE), offset: 0 }
}
///|
priv struct CharClass(Array[(Char, Char)])
///|
fn CharClass::of(array : Array[(Char, Char)]) -> CharClass {
CharClass(array)
}
///|
fn CharClass::contains(self : CharClass, c : Char) -> Bool {
for left = 0, right = self.0.length(); left < right; {
let middle = (left + right) / 2
let (min, max) = self.0[middle]
if c < min {
continue left, middle
} else if c > max {
continue middle + 1, right
} else {
break true
}
} nobreak {
false
}
}
///|
priv enum Token {
Null
True
False
Number(Double)
String(String)
LBrace
RBrace
LBracket
RBracket
Comma
// Colon
} derive(Show)
///|
let non_ascii_whitespace : CharClass = CharClass::of([
('\u00A0', '\u00A0'),
('\u1680', '\u1680'),
('\u2000', '\u200A'),
('\u2028', '\u2029'),
('\u202F', '\u202F'),
('\u205F', '\u205F'),
('\u3000', '\u3000'),
('\uFEFF', '\uFEFF'),
])
///|
fn ParseContext::lex_value(
ctx : ParseContext,
allow_rbracket~ : Bool,
) -> Token raise ParseError {
for ;; {
match ctx.read_char() {
Some('\t' | ' ' | '\n' | '\r') => continue
Some('{') => return LBrace
Some('[') => return LBracket
Some(']') =>
if allow_rbracket {
return RBracket
} else {
ctx.invalid_char(']', shift=-1)
}
Some('n') => {
ctx.expect_ascii_char(b'u')
ctx.expect_ascii_char(b'l')
ctx.expect_ascii_char(b'l')
return Null
}
Some('t') => {
ctx.expect_ascii_char(b'r')
ctx.expect_ascii_char(b'u')
ctx.expect_ascii_char(b'e')
return True
}
Some('f') => {
ctx.expect_ascii_char(b'a')
ctx.expect_ascii_char(b'l')
ctx.expect_ascii_char(b's')
ctx.expect_ascii_char(b'e')
return False
}
Some('-') =>
match ctx.read_char() {
Some('0') => {
let n = ctx.lex_zero(start=ctx.offset - 2)
return Number(n)
}
Some(c2) => {
if c2 is ('1'..='9') {
let n = ctx.lex_decimal_integer(start=ctx.offset - 2)
return Number(n)
}
ctx.invalid_char(c2, shift=-1)
}
None => raise ParseError("invalid eof")
}
Some('0') => {
let n = ctx.lex_zero(start=ctx.offset - 1)
return Number(n)
}
Some('1'..='9') => {
let n = ctx.lex_decimal_integer(start=ctx.offset - 1)
return Number(n)
}
Some('"') => {
let s = ctx.lex_string()
return String(s)
}
Some(c) => {
if c > '\u{7f}' && non_ascii_whitespace.contains(c) {
continue
}
ctx.invalid_char(c, shift=-1)
}
None => raise ParseError("invalid eof") // InvalidEof
}
}
}
///|
fn[T] ParseContext::invalid_char(
ctx : ParseContext,
c : Char,
shift? : Int = 0,
) -> T raise ParseError {
ctx.offset += shift
raise ParseError("invalid JSON: unexpected '\{c}' at offset \{ctx.offset}")
}
///|
fn ParseContext::read_new_byte(self : ParseContext) -> Char? {
let (b, err) = self.r.read_byte()
guard err is None else { return None }
self.offset += 1
let (_, err) = self.buf.write_byte(b)
guard err is None else { return None }
let current = Int::unsafe_to_char(b.to_int())
Some(current)
}
///|
fn ParseContext::read_char(ctx : ParseContext) -> Char? {
if ctx.offset < ctx.buf.length() {
let b = ctx.buf[ctx.offset]
ctx.offset += 1
return Some(Int::unsafe_to_char(b.to_int()))
}
ParseContext::read_new_byte(ctx)
}
///|
/// low surrogate
// const SURROGATE_LOW_CHAR = 0xD800
///|
/// high surrogate
// const SURROGATE_HIGH_CHAR = 0xDFFF
///|
/// `ctx.expect_char(c)` check the current context is c,
/// if it is, consume the character and return `()`,
/// otherwise raise an error, when it is an error, the position is unspecified.
fn ParseContext::expect_char(
ctx : ParseContext,
c : Char,
) -> Unit raise ParseError {
// guard ctx.offset < ctx.end_offset else { raise ParseError("invalid eof") }
// let c1 = ctx.input.unsafe_charcode_at(ctx.offset)
// ctx.offset += 1
guard ParseContext::read_char(ctx) is Some(c1) else {
raise ParseError("invalid eof")
}
if c != c1 {
raise ParseError(
"invalid JSON: unexpected '\{c1}' at offset \{ctx.offset}, wanted '\{c}'",
)
}
// let c0 = c.to_int()
// if c0 < 0xFFFF {
// // c0 < SURROGATE_LOW_CHAR || c0 is (0xE000..=0XFFFF)
// // c0 is a valid char so only need check if c0<0xFFFF is BMP code point
// if c0 != c1 {
// ctx.invalid_char!(c0, shift=-1)
// }
// } else {
// // c0 is not bmp code point
// // c1 has to be surrogate pair otherwise it is invalid
// guard c1 is (SURROGATE_LOW_CHAR..=SURROGATE_HIGH_CHAR) &&
// ctx.offset < ctx.end_offset else {
// ctx.invalid_char!(c1, shift=-1)
// }
// let c2 = ctx.input.unsafe_charcode_at(ctx.offset)
// let c3 = (c1 << 10) + c2 - 0x35fdc00
// if c3 != c0 {
// ctx.invalid_char!(c3, shift=-1)
// } else {
// ctx.offset += 1 // consume and move forward
// }
// }
}
///|
/// `ctx.expect_ascii_char(c)` check the current context is c,
///
fn ParseContext::expect_ascii_char(
ctx : ParseContext,
c : Byte,
) -> Unit raise ParseError {
// guard ctx.offset < ctx.end_offset else { raise ParseError("invalid eof") }
// let c1 = ctx.input.unsafe_charcode_at(ctx.offset)
// ctx.offset += 1
// if c.to_int() != c1 {
// ctx.invalid_char!(c, shift=-1)
// }
ParseContext::expect_char(ctx, Int::unsafe_to_char(c.to_int()))
}
///|
test "expect_char" {
let buf = Buffer::new(size_hint=3)
buf.write_string("abc") |> ignore()
let ctx = ParseContext::make(buf)
ctx.expect_char('a')
ctx.expect_char('b')
ctx.expect_char('c')
inspect(
try? ctx.expect_char('d').to_string(),
content=(
#|Err(ParseError("invalid eof"))
),
)
}
///|
fn ParseContext::lex_skip_whitespace(ctx : ParseContext) -> Unit {
for ;; {
match ctx.read_char() {
Some('\t' | ' ' | '\n' | '\r') => continue
Some(c) => {
if c > '\u{7f}' && non_ascii_whitespace.contains(c) {
continue
}
ctx.offset -= 1
break
}
None => break
}
}
}
///|
fn ParseContext::lex_after_array_value(
ctx : ParseContext,
) -> Token raise ParseError {
ctx.lex_skip_whitespace()
match ctx.read_char() {
Some(']') => RBracket
Some(',') => Comma
Some(c) => ctx.invalid_char(c, shift=-1)
None => raise ParseError("invalid eof")
}
}
///|
fn ParseContext::lex_after_property_name(
ctx : ParseContext,
) -> Unit raise ParseError {
ctx.lex_skip_whitespace()
match ctx.read_char() {
Some(':') => ()
Some(c) => ctx.invalid_char(c, shift=-1)
None => raise ParseError("invalid eof")
}
}
///|
fn ParseContext::lex_after_object_value(
ctx : ParseContext,
) -> Token raise ParseError {
ctx.lex_skip_whitespace()
match ctx.read_char() {
Some('}') => RBrace
Some(',') => Comma
Some(c) => ctx.invalid_char(c, shift=-1)
None => raise ParseError("invalid eof")
}
}
///|
/// In the context of `{`, try to lex token `}` or a property name,
/// otherwise raise an error.
fn ParseContext::lex_property_name(
ctx : ParseContext,
) -> Token raise ParseError {
ctx.lex_skip_whitespace()
match ctx.read_char() {
Some('}') => RBrace
Some('"') => {
let s = ctx.lex_string()
String(s)
}
Some(c) => ctx.invalid_char(c, shift=-1)
None => raise ParseError("invalid eof")
}
}
///|
/// In the context of `{ ...,` try to lex a property name,
/// otherwise raise an error.
/// since it is in comma context, `}` is not allowed.
fn ParseContext::lex_property_name2(
ctx : ParseContext,
) -> Token raise ParseError {
ctx.lex_skip_whitespace()
match ctx.read_char() {
Some('"') => {
let s = ctx.lex_string()
String(s)
}
Some(c) => ctx.invalid_char(c, shift=-1)
None => raise ParseError("invalid eof")
}
}
///|
fn ParseContext::lex_decimal_integer(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
for ;; {
match ctx.read_char() {
Some('.') => return ctx.lex_decimal_point(start~)
Some('e' | 'E') => return ctx.lex_decimal_exponent(start~)
Some(c) => {
if c >= '0' && c <= '9' {
continue
}
ctx.offset -= 1
return ctx.lex_number_end(start, ctx.offset)
}
None => return ctx.lex_number_end(start, ctx.offset)
}
}
}
///|
fn ParseContext::lex_decimal_point(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
match ctx.read_char() {
Some(c) =>
if c >= '0' && c <= '9' {
ctx.lex_decimal_fraction(start~)
} else {
ctx.invalid_char(c, shift=-1)
}
None => raise ParseError("invalid eof")
}
}
///|
fn ParseContext::lex_decimal_fraction(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
for ;; {
match ctx.read_char() {
Some('e' | 'E') => return ctx.lex_decimal_exponent(start~)
Some(c) => {
if c >= '0' && c <= '9' {
continue
}
ctx.offset -= 1
return ctx.lex_number_end(start, ctx.offset)
}
None => return ctx.lex_number_end(start, ctx.offset)
}
}
}
///|
fn ParseContext::lex_decimal_exponent(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
match ctx.read_char() {
Some('+') | Some('-') => return ctx.lex_decimal_exponent_sign(start~)
Some(c) => {
if c >= '0' && c <= '9' {
return ctx.lex_decimal_exponent_integer(start~)
}
ctx.invalid_char(c, shift=-1)
}
None => raise ParseError("invalid eof")
}
}
///|
fn ParseContext::lex_decimal_exponent_sign(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
match ctx.read_char() {
Some(c) => {
if c >= '0' && c <= '9' {
return ctx.lex_decimal_exponent_integer(start~)
}
ctx.invalid_char(c, shift=-1)
}
None => raise ParseError("invalid eof")
}
}
///|
fn ParseContext::lex_decimal_exponent_integer(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
for ;; {
match ctx.read_char() {
Some(c) => {
if c >= '0' && c <= '9' {
continue
}
ctx.offset -= 1
return ctx.lex_number_end(start, ctx.offset)
}
None => return ctx.lex_number_end(start, ctx.offset)
}
}
}
///|
fn ParseContext::lex_zero(
ctx : ParseContext,
start~ : Int,
) -> Double raise ParseError {
match ctx.read_char() {
Some('.') => ctx.lex_decimal_point(start~)
Some('e' | 'E') => ctx.lex_decimal_exponent(start~)
Some(c) => {
if c >= '0' && c <= '9' {
ctx.invalid_char(c, shift=-1)
}
ctx.offset -= 1
return ctx.lex_number_end(start, ctx.offset)
}
None => return ctx.lex_number_end(start, ctx.offset)
}
}
///|
fn ParseContext::lex_number_end(
ctx : ParseContext,
start : Int,
end : Int,
) -> Double raise ParseError {
let s = ctx.buf.substring(start~, end~)
@strconv.parse_double(s) catch {
_ => raise ParseError("invalid number '\{s}' at offsets \{start}:\{end}") // InvalidNumber(offset_to_position(ctx.input, start), s)
}
}
///|
fn ParseContext::lex_string(ctx : ParseContext) -> String raise ParseError {
let buf = StringBuilder::new()
let mut start = ctx.offset
fn flush(end : Int) {
if start > 0 && end > start {
// buf.write_substring(ctx.input, start, end - start)
buf.write_string(ctx.buf.substring(start~, end~))
}
}
for ;; {
match ctx.read_char() {
Some('"') => {
flush(ctx.offset - 1)
break
}
Some('\n' | '\r') as c => ctx.invalid_char(c.unwrap(), shift=-1)
Some('\\') => {
flush(ctx.offset - 1)
match ctx.read_char() {
Some('b') => buf.write_char('\b')
Some('f') => buf.write_char('\u{0C}')
Some('n') => buf.write_char('\n')
Some('r') => buf.write_char('\r')
Some('t') => buf.write_char('\t')
Some('"') => buf.write_char('"')
Some('\\') => buf.write_char('\\')
Some('/') => buf.write_char('/')
Some('u') => {
let c = ctx.lex_hex_digits(4)
buf.write_char(Int::unsafe_to_char(c))
}
Some(c) => ctx.invalid_char(c, shift=-1)
None => raise ParseError("invalid eof")
}
start = ctx.offset
}
Some(ch) =>
if ch.to_int() < 32 {
ctx.invalid_char(ch, shift=-1)
} else {
continue
}
None => raise ParseError("invalid eof")
}
}
buf.to_string()
}
///|
fn ParseContext::lex_hex_digits(
ctx : ParseContext,
n : Int,
) -> Int raise ParseError {
let mut r = 0
for _ in 0..
if c >= 'A' {
let d = (c.to_int() & (32).lnot()) - 'A'.to_int() + 10
if d > 15 {
ctx.invalid_char(c, shift=-1)
}
r = (r << 4) | d
} else if c >= '0' {
let d = c.to_int() - '0'.to_int()
if d > 9 {
ctx.invalid_char(c, shift=-1)
}
r = (r << 4) | d
} else {
ctx.invalid_char(c, shift=-1)
}
None => raise ParseError("invalid eof")
}
}
r
}