/- 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 -/ prelude import Lean.Data.Json.Basic import Lean.Data.Parsec import Lean.Data.RBMap namespace Lean.Json.Parser open Lean.Parsec @[inline] def hexChar : Parsec Nat := do let c ← anyChar if '0' ≤ c ∧ c ≤ '9' then pure $ c.val.toNat - '0'.val.toNat else if 'a' ≤ c ∧ c ≤ 'f' then pure $ c.val.toNat - 'a'.val.toNat + 10 else if 'A' ≤ c ∧ c ≤ 'F' then pure $ c.val.toNat - 'A'.val.toNat + 10 else fail "invalid hex character" def escapedChar : Parsec Char := do let c ← anyChar match c with | '\\' => return '\\' | '"' => return '"' | '/' => return '/' | 'b' => return '\x08' | 'f' => return '\x0c' | 'n' => return '\n' | 'r' => return '\x0d' | 't' => return '\t' | 'u' => let u1 ← hexChar; let u2 ← hexChar; let u3 ← hexChar; let u4 ← hexChar return Char.ofNat $ 4096*u1 + 256*u2 + 16*u3 + u4 | _ => fail "illegal \\u escape" partial def strCore (acc : String) : Parsec String := do let c ← peek! if c = '"' then -- " skip return acc else let c ← anyChar if c = '\\' then strCore (acc.push (← escapedChar)) -- as to whether c.val > 0xffff should be split up and encoded with multiple \u, -- the JSON standard is not definite: both directly printing the character -- and encoding it with multiple \u is allowed. we choose the former. else if 0x0020 ≤ c.val ∧ c.val ≤ 0x10ffff then strCore (acc.push c) else fail "unexpected character in string" def str : Parsec String := strCore "" partial def natCore (acc digits : Nat) : Parsec (Nat × Nat) := do let some c ← peek? | return (acc, digits) if '0' ≤ c ∧ c ≤ '9' then skip let acc' := 10*acc + (c.val.toNat - '0'.val.toNat) natCore acc' (digits+1) else return (acc, digits) @[inline] def lookahead (p : Char → Prop) (desc : String) [DecidablePred p] : Parsec Unit := do let c ← peek! if p c then return () else fail <| "expected " ++ desc @[inline] def natNonZero : Parsec Nat := do lookahead (fun c => '1' ≤ c ∧ c ≤ '9') "1-9" let (n, _) ← natCore 0 0 return n @[inline] def natNumDigits : Parsec (Nat × Nat) := do lookahead (fun c => '0' ≤ c ∧ c ≤ '9') "digit" natCore 0 0 @[inline] def natMaybeZero : Parsec Nat := do let (n, _) ← natNumDigits return n def num : Parsec JsonNumber := do let c ← peek! let sign ← if c = '-' then skip pure (-1 : Int) else pure 1 let c ← peek! let res ← if c = '0' then skip pure 0 else natNonZero let c? ← peek? let res : JsonNumber ← if c? = some '.' then skip let (n, d) ← natNumDigits if d > USize.size then fail "too many decimals" let mantissa' := sign * (res * (10^d : Nat) + n) let exponent' := d pure <| JsonNumber.mk mantissa' exponent' else pure <| JsonNumber.fromInt (sign * res) let c? ← peek? if c? = some 'e' ∨ c? = some 'E' then skip let c ← peek! if c = '-' then skip let n ← natMaybeZero return res.shiftr n else if c = '+' then skip let n ← natMaybeZero if n > USize.size then fail "exp too large" return res.shiftl n else return res partial def arrayCore (anyCore : Parsec Json) (acc : Array Json) : Parsec (Array Json) := do let hd ← anyCore let acc' := acc.push hd let c ← anyChar if c = ']' then ws return acc' else if c = ',' then ws arrayCore anyCore acc' else fail "unexpected character in array" partial def objectCore (anyCore : Parsec Json) : Parsec (RBNode String (fun _ => Json)) := do lookahead (fun c => c = '"') "\""; skip; -- " let k ← strCore ""; ws lookahead (fun c => c = ':') ":"; skip; ws let v ← anyCore let c ← anyChar if c = '}' then ws return RBNode.singleton k v else if c = ',' then ws let kvs ← objectCore anyCore return kvs.insert compare k v else fail "unexpected character in object" partial def anyCore : Parsec Json := do let c ← peek! if c = '[' then skip; ws let c ← peek! if c = ']' then skip; ws return Json.arr (Array.mkEmpty 0) else let a ← arrayCore anyCore (Array.mkEmpty 4) return Json.arr a else if c = '{' then skip; ws let c ← peek! if c = '}' then skip; ws return Json.obj (RBNode.leaf) else let kvs ← objectCore anyCore return Json.obj kvs else if c = '\"' then skip let s ← strCore "" ws return Json.str s else if c = 'f' then skipString "false"; ws return Json.bool false else if c = 't' then skipString "true"; ws return Json.bool true else if c = 'n' then skipString "null"; ws return Json.null else if c = '-' ∨ ('0' ≤ c ∧ c ≤ '9') then let n ← num ws return Json.num n else fail "unexpected input" def any : Parsec Json := do ws let res ← anyCore eof return res end Json.Parser namespace Json def parse (s : String) : Except String Lean.Json := match Json.Parser.any s.mkIterator with | Parsec.ParseResult.success _ res => Except.ok res | Parsec.ParseResult.error it err => Except.error s!"offset {repr it.i.byteIdx}: {err}" end Json end Lean