///|
fn format_javascript_path(segments : Array[PathSegment]) -> String {
let output = StringBuilder()
for index, segment in segments {
match segment {
Key(key) if is_identifier(key) => {
if index > 0 {
output.write_char('.')
}
output.write_string(key)
}
Key(key) => {
output.write_char('[')
output.write_string(Json::string(key).stringify())
output.write_char(']')
}
Index(array_index) => {
output.write_char('[')
output.write_object(array_index)
output.write_char(']')
}
}
}
output.to_string()
}
///|
fn is_identifier(key : String) -> Bool {
let chars = key.iter().to_array()
guard chars.length() > 0 else { return false }
guard chars[0].is_ascii_alphabetic() || chars[0] == '_' || chars[0] == '$' else {
return false
}
chars[1:].all(char => {
char.is_ascii_alphabetic() ||
char.is_ascii_digit() ||
char == '_' ||
char == '$'
})
}
///|
fn parse_javascript_path(
path : String,
) -> Array[PathSegment] raise FlattenJsonError {
if path == "" {
return []
}
let chars = path.iter().to_array()
let segments : Array[PathSegment] = []
let mut index = 0
let mut expects_segment = true
let mut after_dot = false
while index < chars.length() {
match chars[index] {
'.' if !expects_segment => {
expects_segment = true
after_dot = true
index = index + 1
}
'[' if !after_dot => {
let (segment, next_index) = parse_bracket_segment(chars, index, path)
segments.push(segment)
expects_segment = false
index = next_index
}
_ if expects_segment => {
let start = index
while index < chars.length() &&
chars[index] != '.' &&
chars[index] != '[' {
index = index + 1
}
let key = chars_to_string(chars[start:index])
guard is_identifier(key) else { raise InvalidPath(path) }
segments.push(Key(key))
expects_segment = false
after_dot = false
}
_ => raise InvalidPath(path)
}
}
guard !expects_segment else { raise InvalidPath(path) }
segments
}
///|
fn parse_bracket_segment(
chars : Array[Char],
start : Int,
path : String,
) -> (PathSegment, Int) raise FlattenJsonError {
let mut index = start + 1
guard index < chars.length() else { raise InvalidPath(path) }
if chars[index] == '"' {
let literal = StringBuilder()
literal.write_char('"')
index = index + 1
let mut escaped = false
while index < chars.length() {
let char = chars[index]
literal.write_char(char)
index = index + 1
if escaped {
escaped = false
} else if char == '\\' {
escaped = true
} else if char == '"' {
break
}
}
guard index < chars.length() && chars[index] == ']' else {
raise InvalidPath(path)
}
let parsed = try @core_json.parse(literal.to_string()) catch {
_ => raise InvalidPath(path)
} noraise {
value => value
}
let key = match parsed {
Json::String(key) => key
_ => raise InvalidPath(path)
}
return (Key(key), index + 1)
}
let number = StringBuilder()
while index < chars.length() && chars[index] != ']' {
number.write_char(chars[index])
index = index + 1
}
guard index < chars.length() && chars[index] == ']' else {
raise InvalidPath(path)
}
let token = number.to_string()
guard is_array_index_token(token) else { raise InvalidPath(path) }
let value = try @string.parse_int(token) catch {
_ => raise InvalidPath(path)
} noraise {
value => value
}
guard value >= 0 else { raise InvalidPath(path) }
(Index(value), index + 1)
}
///|
fn chars_to_string(chars : ArrayView[Char]) -> String {
let output = StringBuilder()
for char in chars {
output.write_char(char)
}
output.to_string()
}