Copyright (c) 2019 Gabriel Ebner. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gabriel Ebner, Marc Huisinga
import Init.Data.List.Control
import Init.Data.OfScientific
import Init.Data.Hashable
import Init.Data.ToString.Macro
-- mantissa * 10^-exponent
structure JsonNumber where
deriving DecidableEq, Hashable
protected def fromNat (n : Nat) : JsonNumber := ⟨n, 0⟩
protected def fromInt (n : Int) : JsonNumber := ⟨n, 0⟩
instance : Coe Nat JsonNumber := ⟨JsonNumber.fromNat⟩
instance : Coe Int JsonNumber := ⟨JsonNumber.fromInt⟩
instance : OfNat JsonNumber n := ⟨JsonNumber.fromNat n⟩
private partial def countDigits (n : Nat) : Nat :=
let rec loop (n digits : Nat) : Nat :=
-- convert mantissa * 10^-exponent to 0.mantissa * 10^exponent
protected def normalize : JsonNumber → Int × Nat × Int
let sign : Int := if m > 0 then 1 else -1
let nDigits := countDigits mAbs
-- eliminate trailing zeros
(sign, mAbs, -(e : Int) + nDigits)
-- todo (Dany): We should have an Ordering version of this.
def lt (a b : JsonNumber) : Bool :=
let (as, am, ae) := a.normalize
let (bs, bm, be) := b.normalize
let ((am, ae), (bm, be)) :=
if as = -1 && bs = -1 then
let amDigits := countDigits am
let bmDigits := countDigits bm
if amDigits < bmDigits then
(am * 10^(bmDigits - amDigits), bm)
(am, bm * 10^(amDigits - bmDigits))
else if ae > be then false
def ltProp : LT JsonNumber :=
⟨fun a b => lt a b = true⟩
instance : LT JsonNumber :=
instance (a b : JsonNumber) : Decidable (a < b) :=
inferInstanceAs (Decidable (lt a b = true))
instance : Ord JsonNumber where
if x < y then Ordering.lt
else if x > y then Ordering.gt
protected def toString : JsonNumber → String
let sign := if m ≥ 0 then "" else "-"
-- if there are too many zeroes after the decimal, we
-- use exponents to compress the representation.
-- this is mostly done for memory usage reasons:
-- the size of the representation would otherwise
-- grow exponentially in the value of exponent.
let exp : Int := 9 + countDigits m - (e : Int)
let exp := if exp < 0 then exp else 0
let e' := 10 ^ (e - exp.natAbs)
let left := (m / e').repr
if m % e' = 0 && exp = 0 then
|>.repr.toSubstring.drop 1
|>.dropRightWhile (fun c => c = '0')
let exp := if exp = 0 then "" else "e" ++ exp.repr
s!"{sign}{left}.{right}{exp}"
-- shift a JsonNumber by a specified amount of places to the left
protected def shiftl : JsonNumber → Nat → JsonNumber
-- if s ≤ e, then 10 ^ (s - e) = 1, and hence the mantissa remains unchanged.
-- otherwise, the expression pads the mantissa with zeroes
-- to accommodate for the remaining places to shift.
| ⟨m, e⟩, s => ⟨m * (10 ^ (s - e) : Nat), e - s⟩
-- shift a JsonNumber by a specified amount of places to the right
protected def shiftr : JsonNumber → Nat → JsonNumber
| ⟨m, e⟩, s => ⟨m, e + s⟩
instance : ToString JsonNumber := ⟨JsonNumber.toString⟩
instance : Repr JsonNumber where
reprPrec | ⟨m, e⟩, _ => Std.Format.bracket "⟨" (repr m ++ "," ++ repr e) "⟩"
instance : OfScientific JsonNumber where
ofScientific mantissa exponentSign decimalExponent :=
{mantissa := mantissa, exponent := decimalExponent}
{mantissa := (mantissa * 10 ^ decimalExponent : Nat), exponent := 0}
instance : Neg JsonNumber where
neg jn := ⟨- jn.mantissa, jn.exponent⟩
instance : Inhabited JsonNumber := ⟨0⟩
def toFloat : JsonNumber → Float
| ⟨m, e⟩ => (if m >= 0 then 1.0 else -1.0) * OfScientific.ofScientific m.natAbs true e
/-- Creates a json number from a positive float, panicking if it isn't.
[todo]EdAyers: Currently goes via a string representation, when more float primitives are available
should switch to using those. -/
private def fromPositiveFloat! (x : Float) : JsonNumber :=
match Lean.Syntax.decodeScientificLitVal? (toString x) with
| none => panic! s!"Failed to parse {toString x}"
| some (m, sign, e) => OfScientific.ofScientific m sign e
/-- Attempts to convert a float to a JsonNumber, if the number isn't finite returns
the appropriate string from "NaN", "Infinity", "-Infinity". -/
def fromFloat? (x : Float): Sum String JsonNumber :=
if x.isNaN then Sum.inl "NaN"
Sum.inl <| if x > 0 then "Infinity" else "-Infinity"
Sum.inr 0 -- special case to avoid -0.0
Sum.inr <| Neg.neg <| fromPositiveFloat! <| Neg.neg <| x
Sum.inr <| fromPositiveFloat! <| x
def strLt (a b : String) := Decidable.decide (a < b)
| arr (elems : Array Json)
-- uses RBNode instead of RBMap because RBMap is a def
-- and thus currently cannot be used to define a type that
-- is recursive in one of its parameters
| obj (kvPairs : RBNode String (fun _ => Json))
private partial def beq' : Json → Json → Bool
| bool a, bool b => a == b
let _ : BEq Json := ⟨beq'⟩
let _ : BEq Json := ⟨beq'⟩
let szA := a.fold (init := 0) (fun a _ _ => a + 1)
let szB := b.fold (init := 0) (fun a _ _ => a + 1)
szA == szB && a.all fun field fa =>
match b.find compare field with
instance : BEq Json where
private partial def hash' : Json → UInt64
| bool b => mixHash 13 <| hash b
| num n => mixHash 17 <| hash n
| str s => mixHash 19 <| hash s
mixHash 23 <| elems.foldl (init := 7) fun r a => mixHash r (hash' a)
mixHash 29 <| kvPairs.fold (init := 7) fun r k v => mixHash r <| mixHash (hash k) (hash' v)
instance : Hashable Json where
-- HACK(Marc): temporary ugliness until we can use RBMap for JSON objects
def mkObj (o : List (String × Json)) : Json :=
let mut kvPairs := RBNode.leaf
kvPairs := kvPairs.insert compare k v
instance : Coe Nat Json := ⟨fun n => Json.num n⟩
instance : Coe Int Json := ⟨fun n => Json.num n⟩
instance : Coe String Json := ⟨Json.str⟩
instance : Coe Bool Json := ⟨Json.bool⟩
instance : OfNat Json n := ⟨Json.num n⟩
def isNull : Json -> Bool
def getObj? : Json → Except String (RBNode String (fun _ => Json))
| _ => throw "object expected"
def getArr? : Json → Except String (Array Json)
| _ => throw "array expected"
def getStr? : Json → Except String String
| _ => throw "String expected"
def getNat? : Json → Except String Nat
| _ => throw "Natural number expected"
def getInt? : Json → Except String Int
| _ => throw "Integer expected"
def getBool? : Json → Except String Bool
| _ => throw "Bool expected"
def getNum? : Json → Except String JsonNumber
| _ => throw "number expected"
def getObjVal? : Json → String → Except String Json
match kvs.find compare k with
| none => throw s!"property not found: {k}"
| _ , _ => throw "object expected"
def getArrVal? : Json → Nat → Except String Json
| none => throw s!"index out of bounds: {i}"
| _ , _ => throw "array expected"
def getObjValD (j : Json) (k : String) : Json :=
(j.getObjVal? k).toOption.getD null
def setObjVal! : Json → String → Json → Json
| obj kvs, k, v => obj <| kvs.insert compare k v
| _ , _, _ => panic! "Json.setObjVal!: not an object: {j}"
/-- Assuming both inputs `o₁, o₂` are json objects, will compute `{...o₁, ...o₂}`.
If `o₁` is not a json object, `o₂` will be returned.
def mergeObj : Json → Json → Json
obj <| fold (insert compare) kvs₁ kvs₂
inductive Structured where
| arr (elems : Array Json)
| obj (kvPairs : RBNode String (fun _ => Json))
instance : Coe (Array Json) Structured := ⟨Structured.arr⟩
instance : Coe (RBNode String (fun _ => Json)) Structured := ⟨Structured.obj⟩