// JSONLogic coercion follows json-logic-js 2.0.5 for JSON values.
///|
fn js_string(value : Json) -> String {
match value {
Null => "null"
True => "true"
False => "false"
String(s) => s
Number(n, ..) => if n == 0.0 { "0" } else { n.to_string() }
Array(xs) =>
xs.map(x => if x == Json::null() { "" } else { js_string(x) }).join(",")
Object(_) => "[object Object]"
}
}
///|
fn parse_number(text : String) -> Double {
let s = js_trim(text)
if s.is_empty() {
return 0.0
}
if s == "Infinity" || s == "+Infinity" {
return @double.infinity
}
if s == "-Infinity" {
return @double.neg_infinity
}
// Number accepts unsigned 0x/0b/0o strings; arithmetic + and * use parseFloat instead.
if s.has_prefix("0x") ||
s.has_prefix("0X") ||
s.has_prefix("0b") ||
s.has_prefix("0B") ||
s.has_prefix("0o") ||
s.has_prefix("0O") {
let base = if s[1] == 120 || s[1] == 88 {
16
} else if s[1] == 98 || s[1] == 66 {
2
} else {
8
}
if s.length() == 2 {
return @double.not_a_number
}
let mut result = 0.0
for i = 2; i < s.length(); i = i + 1 {
let c = s[i].to_int()
let digit = if c >= 48 && c <= 57 {
c - 48
} else if c >= 65 && c <= 70 {
c - 55
} else if c >= 97 && c <= 102 {
c - 87
} else {
-1
}
if digit < 0 || digit >= base {
return @double.not_a_number
}
result = result * base.to_double() + digit.to_double()
}
return result
}
// Restrict decimal syntax before delegating conversion to the standard library.
let end = float_prefix_end(s)
if end != s.length() || end == 0 {
return @double.not_a_number
}
@string.parse_double(s) catch {
_ => @double.not_a_number
}
}
///|
fn numeric(value : Json) -> Double {
match value {
Null | False => 0.0
True => 1.0
Number(n, ..) => n
_ => parse_number(js_string(value))
}
}
///|
fn digit_at(s : String, i : Int) -> Bool {
i < s.length() && s[i] >= 48 && s[i] <= 57
}
///|
fn float_prefix_end(s : String) -> Int {
let mut i = 0
if i < s.length() && (s[i] == 43 || s[i] == 45) {
i += 1
}
let begin = i
while digit_at(s, i) {
i += 1
}
let mut digits = i - begin
if i < s.length() && s[i] == 46 {
i += 1
let begin = i
while digit_at(s, i) {
i += 1
}
digits += i - begin
}
if digits == 0 {
return 0
}
let before_exp = i
if i < s.length() && (s[i] == 69 || s[i] == 101) {
i += 1
if i < s.length() && (s[i] == 43 || s[i] == 45) {
i += 1
}
let begin = i
while digit_at(s, i) {
i += 1
}
if i == begin {
return before_exp
}
}
i
}
///|
fn parse_float(value : Json) -> Double {
let s = js_trim(js_string(value))
if s.has_prefix("Infinity") || s.has_prefix("+Infinity") {
return @double.infinity
}
if s.has_prefix("-Infinity") {
return @double.neg_infinity
}
let end = float_prefix_end(s)
if end == 0 {
return @double.not_a_number
}
@string.parse_double(s[:end]) catch {
_ => @double.not_a_number
}
}
///|
fn strict_equal(a : Json, b : Json) -> Bool {
match (a, b) {
(Array(x), Array(y)) => physical_equal(x, y)
(Object(x), Object(y)) => physical_equal(x, y)
(Number(x, ..), Number(y, ..)) => x == y
_ => a == b
}
}
///|
fn loose_equal(a : Json, b : Json) -> Bool {
match (a, b) {
(Array(_), Array(_)) | (Object(_), Object(_)) => strict_equal(a, b)
(Array(_), Object(_)) | (Object(_), Array(_)) => false
(True, _) | (False, _) => loose_equal(numeric(a).to_json(), b)
(_, True) | (_, False) => loose_equal(a, numeric(b).to_json())
(Number(x, ..), String(_)) => x == numeric(b)
(String(_), Number(y, ..)) => numeric(a) == y
(Number(_, ..), Array(_))
| (Number(_, ..), Object(_))
| (String(_), Array(_))
| (String(_), Object(_)) => loose_equal(a, js_string(b).to_json())
(Array(_), Number(_, ..))
| (Object(_), Number(_, ..))
| (Array(_), String(_))
| (Object(_), String(_)) => loose_equal(js_string(a).to_json(), b)
_ => strict_equal(a, b)
}
}
///|
fn relational(op : String, a : Json, b : Json) -> Bool {
let a = match a {
Array(_) | Object(_) => js_string(a).to_json()
_ => a
}
let b = match b {
Array(_) | Object(_) => js_string(b).to_json()
_ => b
}
match (a, b) {
(String(x), String(y)) => {
let c = x.lexical_compare(y)
match op {
">" => c > 0
">=" => c >= 0
"<" => c < 0
_ => c <= 0
}
}
_ => {
let x = numeric(a)
let y = numeric(b)
match op {
">" => x > y
">=" => x >= y
"<" => x < y
_ => x <= y
}
}
}
}
// ECMAScript WhiteSpace and LineTerminator, excluding Unicode NEL (U+0085).
///|
fn js_space(c : UInt16) -> Bool {
(c >= 9 && c <= 13) ||
c == 32 ||
c == 160 ||
c == 5760 ||
(c >= 8192 && c <= 8202) ||
c == 8232 ||
c == 8233 ||
c == 8239 ||
c == 8287 ||
c == 12288 ||
c == 65279
}
///|
fn js_trim(s : String) -> String {
let mut start = 0
let mut end = s.length()
while start < end && js_space(s[start]) {
start += 1
}
while end > start && js_space(s[end - 1]) {
end -= 1
}
s[start:end].to_owned()
}