///|
priv struct Parser {
source : String
options : Options
mut pos : Int
edits : Array[Edit]
}
///|
fn Parser::peek(self : Parser) -> Int {
if self.pos >= self.source.length() {
-1
} else {
self.source[self.pos].to_int()
}
}
///|
fn Parser::skip_space(self : Parser) -> Unit raise RepairError {
while true {
while self.peek() == 32 ||
self.peek() == 9 ||
self.peek() == 10 ||
self.peek() == 13 {
self.pos += 1
}
if !self.options.relaxed ||
self.peek() != 47 ||
self.pos + 1 >= self.source.length() {
return
}
let start = self.pos
let next = self.source[self.pos + 1].to_int()
if next == 47 {
self.pos += 2
while self.peek() != -1 && self.peek() != 10 && self.peek() != 13 {
self.pos += 1
}
} else if next == 42 {
self.pos += 2
while true {
if self.peek() == -1 {
raise Rejected("UNCLOSED_COMMENT", start)
}
if self.peek() == 42 &&
self.pos + 1 < self.source.length() &&
self.source[self.pos + 1].to_int() == 47 {
self.pos += 2
break
}
self.pos += 1
}
} else {
return
}
self.edit(start, self.pos, " ", "REMOVE_COMMENT")
}
}
///|
fn Parser::string_token(self : Parser) -> String raise RepairError {
let start = self.pos
self.pos += 1
while self.peek() != -1 {
let c = self.peek()
self.pos += 1
if c == 92 {
if self.peek() == -1 {
raise Rejected("UNCLOSED_STRING", start)
}
self.pos += 1
} else if c == 34 {
let raw = self.source[start:self.pos].to_owned()
try {
match @json.parse(raw) {
String(s) => return s
_ => raise Rejected("INVALID_STRING", start)
}
} catch {
_ => raise Rejected("INVALID_STRING", start)
}
}
}
raise Rejected("UNCLOSED_STRING", start)
}
///|
fn Parser::value(self : Parser, depth : Int) -> Unit raise RepairError {
self.skip_space()
match self.peek() {
123 => self.object(depth + 1)
91 => self.array(depth + 1)
34 => ignore(self.string_token())
39 if self.options.relaxed => ignore(self.single_string())
-1 => raise Rejected("MISSING_VALUE", self.pos)
_ => {
let start = self.pos
while self.peek() != -1 &&
self.peek() != 44 &&
self.peek() != 93 &&
self.peek() != 125 &&
self.peek() != 32 &&
self.peek() != 9 &&
self.peek() != 10 &&
self.peek() != 13 &&
self.peek() != 47 {
self.pos += 1
}
let token = self.source[start:self.pos].to_owned()
if token.length() == 0 || !@json.valid(token) {
raise Rejected("INVALID_VALUE", start)
}
}
}
}
///|
fn Parser::object(self : Parser, depth : Int) -> Unit raise RepairError {
if depth > self.options.max_depth {
raise Rejected("DEPTH_LIMIT", self.pos)
}
self.pos += 1
self.skip_space()
if self.peek() == 125 {
self.pos += 1
return
}
let keys : Map[String, Bool] = Map([])
while true {
self.skip_space()
let key_pos = self.pos
let key = self.key()
if keys.contains(key) {
raise Rejected("DUPLICATE_KEY", key_pos)
}
keys[key] = true
self.skip_space()
if self.peek() != 58 {
raise Rejected("EXPECTED_COLON", self.pos)
}
self.pos += 1
self.value(depth)
self.skip_space()
match self.peek() {
125 => {
self.pos += 1
return
}
-1 if self.options.relaxed && self.options.close_containers => {
self.edit(self.pos, self.pos, "}", "CLOSE_CONTAINER")
return
}
44 => {
let comma = self.pos
self.pos += 1
self.skip_space()
if self.peek() == 125 && self.options.relaxed {
self.edit(comma, comma + 1, "", "TRAILING_COMMA")
self.pos += 1
return
}
}
_ => raise Rejected("EXPECTED_OBJECT_END", self.pos)
}
}
}
///|
fn Parser::array(self : Parser, depth : Int) -> Unit raise RepairError {
if depth > self.options.max_depth {
raise Rejected("DEPTH_LIMIT", self.pos)
}
self.pos += 1
self.skip_space()
if self.peek() == 93 {
self.pos += 1
return
}
while true {
self.value(depth)
self.skip_space()
match self.peek() {
93 => {
self.pos += 1
return
}
-1 if self.options.relaxed && self.options.close_containers => {
self.edit(self.pos, self.pos, "]", "CLOSE_CONTAINER")
return
}
44 => {
let comma = self.pos
self.pos += 1
self.skip_space()
if self.peek() == 93 && self.options.relaxed {
self.edit(comma, comma + 1, "", "TRAILING_COMMA")
self.pos += 1
return
}
}
_ => raise Rejected("EXPECTED_ARRAY_END", self.pos)
}
}
}
///|
/// Valid input is preserved exactly, including number spelling and whitespace.
pub fn repair(
input : String,
options? : Options = Options::conservative(),
) -> RepairResult raise RepairError {
if options.max_input < 0 ||
options.max_depth < 1 ||
options.max_depth > 256 ||
options.max_edits < 0 {
raise Rejected("INVALID_OPTIONS", 0)
}
if input.length() > options.max_input {
raise Rejected("INPUT_LIMIT", 0)
}
let p = Parser::{ source: input, options, pos: 0, edits: [], }
p.value(0)
p.skip_space()
if p.peek() != -1 {
raise Rejected("EXTRA_CONTENT", p.pos)
}
{ output: apply_edits(input, p.edits), edits: p.edits, }
}