// Parses a JSON file into a tree and prints it back, two spaces per level. // // nikaia run -- tests/document.in/data.json // { // "name": "a\nb", // … // } // longest string: 3 characters // // Any JSON file will do; a one-line one is enough: // `{"name": "a\nb", "tags": [1, 2.5, true, null], "empty": {}}`. // // Shows: an `enum` as a recursive tree, a recursive `grammar`, strings kept as // views of the input and decoded only when asked, and a `match` the compiler // checks for missing cases. // // A package: `nikaia run` in this directory runs it, and `nikaia test` checks // what it prints against `tests/` (ADR-269 D13). use std::fs use std::cli // A JSON value is one of six things, which is what an enum says. `Array` and // `Object` hold more `Json`, so the type is a tree of any depth. // // `Text` holds a view into the input, so `Json` and `Member` are tied to the // text they were parsed from. Neither declaration has to say so: the compiler // works it out, through the cycle. enum Json { Null, Bool(bool), Number(f64), Text(String), Array(Vec[Json]), Object(Vec[Member]), } pub struct Member { key: String, value: Json, } grammar Json_ { // A lowercase rule skips whitespace between its parts, so nothing below // mentions blanks, and `[1, 2]` and a document spread over forty lines // parse the same. `value` reaches `value` again through `[` and `{`. entry rule value -> Json = "null" -> { Json::Null } | "true" -> { Json::Bool(true) } | "false" -> { Json::Bool(false) } | n:NUMBER -> { Json::Number(n) } | s:TEXT -> { Json::Text(s) } | "[" "]" -> { Json::Array(Vec()) } | "[" items:values "]" -> { Json::Array(items) } | "{" "}" -> { Json::Object(Vec()) } | "{" members:members "}" -> { Json::Object(members) } // A comma-separated list: a head and a repetition, collected in the // action. `values = values "," value` would never terminate. rule values -> Vec[Json] = head:value tail:value_tail* -> { let mut all = Vec() all.push(head) for v in tail.drain() { all.push(v) } all } rule value_tail -> Json = "," v:value -> { v } rule members -> Vec[Member] = head:member tail:member_tail* -> { let mut all = Vec() all.push(head) for m in tail.drain() { all.push(m) } all } rule member_tail -> Member = "," m:member -> { m } rule member -> Member = k:TEXT ":" v:value -> { Member { key: k, value: v } } // An uppercase rule skips nothing, so the sign, the digits and the fraction // are one run with nothing between them. `dec[f64](…)` converts the text // the pattern matched. rule NUMBER -> f64 = n:dec[f64](text("-"? digit+ ("." digit+)? )) -> { n } // The body between the quotes, raw: escapes are kept as written. The body // `a\nb` is four characters in the file and three once decoded, so a JSON // string cannot simply be a slice of the input; the raw body can. // // An escape is a backslash and whatever follows it; anything else is a // character that is neither a quote nor a backslash. `text(…)` hands back // the input the repetition covered, as a view: one slice, no copy. rule TEXT -> String = "\"" s:text((ESC | PLAIN)*) "\"" -> { s } rule ESC = "\\" any -> { } rule PLAIN = not("\"") not("\\") any -> { } } // The document, printed back with two spaces per level. // // Six arms and no `else`: a variant added to `Json` makes this `match` a // compile error until it is handled. // // Strings are printed back raw, as they were in the file, so printing never // decodes and never has to re-escape. fn show(value: Json, indent: i64) { let pad = " ".repeat(indent) let inner = " ".repeat(indent + 1) match value { Json::Null => { print("null") } Json::Bool(b) => { if b { print("true") } else { print("false") } } Json::Number(n) => { print(f"{n}") } Json::Text(raw) => { print(f"\"{raw}\"") } Json::Array(items) => { if items.len() == 0 { print("[]") } else { print("[\n") let mut first = true for item in items.drain() { if !first { print(",\n") } first = false print(f"{inner}") show(item, indent + 1) } print(f"\n{pad}]") } } // A brace in a plain string is just a brace: `print("{}")` prints `{}`. // Only an f-string reads `{…}`, and there `}}` stands for one `}`. Json::Object(fields) => { if fields.len() == 0 { print("{}") } else { print("{\n") let mut first = true for field in fields.drain() { if !first { print(",\n") } first = false print(f"{inner}\"{field.key}\": ") show(field.value, indent + 1) } print(f"\n{pad}}}") } } } } // What a JSON escape means. This is the only place a string is decoded. // // `\u` is not handled: `\u0041` comes out as `u0041`, not `A`. fn unescape(raw: String) -> String { let mut out = String() let mut escaped = false for c in raw.chars() { if escaped { match c { 'n' => { out.push('\n') } 't' => { out.push('\t') } 'r' => { out.push('\r') } 'b' => { out.push_str("\u{0008}") } 'f' => { out.push_str("\u{000C}") } // `\"`, `\\` and `\/` stand for themselves. else => { out.push(c) } } escaped = false } else { if c == '\\' { escaped = true } else { out.push(c) } } } return out } // The longest decoded string in the document, in characters. It takes the // tree by `ref` because `main` still prints it afterwards; `show` takes it by // value, being the last to look. fn longest(value: ref Json) -> i64 { match value { Json::Text(raw) => return unescape(raw).chars().count() as i64 Json::Array(items) => { let mut best = 0 for item in items { let n = longest(item) if n > best { best = n } } return best } Json::Object(fields) => { let mut best = 0 for field in fields { let n = longest(field.value) if n > best { best = n } } return best } else => return 0 } } fn main() throws { let path = cli::args().nth(1) ?? "data.json" let text = fs::read_to_string(ref path, fs::Root::Anywhere) catch { eprintln(f"cannot read {path}: {error}") return } let document = Json_::value(text) catch { eprintln(f"{path}: {error}") return } let widest = longest(document) show(document, 0) println("") println(f"longest string: {widest} characters") }