// JSON rendering of the AST, identical in shape to the PEG.js AST objects
// (used for differential testing against the reference implementation).

///|
pub impl ToJson for Node with fn to_json(self) {
  let obj : Map[String, Json] = {
    "type": self.type_name().to_json(),
    "location": self.location.to_json(),
  }
  match self.kind {
    Named(e, name~) => {
      obj["name"] = name.to_json()
      obj["expression"] = e.to_json()
    }
    Choice(alternatives) => obj["alternatives"] = alternatives.to_json()
    Action(e, code~) => {
      obj["expression"] = e.to_json()
      obj["code"] = code.to_json()
    }
    Sequence(elements) => obj["elements"] = elements.to_json()
    Labeled(e, label~, pick~) => {
      if pick {
        obj["pick"] = true.to_json()
      }
      obj["label"] = match label {
        Some(l) => l.to_json()
        None => Json::null()
      }
      obj["expression"] = e.to_json()
    }
    Text(e)
    | SimpleAnd(e)
    | SimpleNot(e)
    | Optional(e)
    | ZeroOrMore(e)
    | OneOrMore(e)
    | Group(e) => obj["expression"] = e.to_json()
    SemanticAnd(code~) | SemanticNot(code~) => obj["code"] = code.to_json()
    RuleRef(name~) => obj["name"] = name.to_json()
    Literal(value~, ignore_case~) => {
      obj["value"] = value.to_json()
      obj["ignoreCase"] = ignore_case.to_json()
    }
    Class(parts~, inverted~, ignore_case~) => {
      obj["parts"] = parts.to_json()
      obj["inverted"] = inverted.to_json()
      obj["ignoreCase"] = ignore_case.to_json()
    }
    Any => ()
  }
  if self.match_result is Some(m) {
    obj["match"] = m.to_json()
  }
  Json::object(obj)
}

///|
pub impl ToJson for Initializer with fn to_json(self) {
  {
    "type": "initializer",
    "location": self.location.to_json(),
    "code": self.code.to_json(),
  }
}

///|
pub impl ToJson for Rule with fn to_json(self) {
  let obj : Map[String, Json] = {
    "type": "rule",
    "location": self.location.to_json(),
    "name": self.name.to_json(),
    "expression": self.expression.to_json(),
  }
  if self.report_failures is Some(r) {
    obj["reportFailures"] = r.to_json()
  }
  if self.match_result is Some(m) {
    obj["match"] = m.to_json()
  }
  if self.bytecode is Some(bc) {
    obj["bytecode"] = bc.to_json()
  }
  Json::object(obj)
}

///|
pub impl ToJson for Comment with fn to_json(self) {
  {
    "text": self.text.to_json(),
    "multiline": self.multiline.to_json(),
    "location": self.location.to_json(),
  }
}

///|
pub impl ToJson for Grammar with fn to_json(self) {
  let obj : Map[String, Json] = {
    "type": "grammar",
    "location": self.location.to_json(),
    "initializer": match self.initializer {
      Some(i) => i.to_json()
      None => Json::null()
    },
    "comments": match self.comments {
      Some(cs) => {
        let m : Map[String, Json] = Map([])
        for c in cs {
          m[c.0.to_string()] = c.1.to_json()
        }
        Json::object(m)
      }
      None => Json::null()
    },
    "rules": self.rules.to_json(),
  }
  if self.literals is Some(xs) {
    obj["literals"] = xs.to_json()
  }
  if self.classes is Some(xs) {
    obj["classes"] = xs.to_json()
  }
  if self.expectations is Some(xs) {
    obj["expectations"] = Json::array(xs.map(@bytecode.expectation_const_json))
  }
  if self.functions is Some(xs) {
    obj["functions"] = xs.to_json()
  }
  if self.code is Some(c) {
    obj["code"] = c.to_json()
  }
  Json::object(obj)
}

///|
pub extend Node with ToJson::{to_json}

///|
pub extend Initializer with ToJson::{to_json}

///|
pub extend Rule with ToJson::{to_json}

///|
pub extend Comment with ToJson::{to_json}

///|
pub extend Grammar with ToJson::{to_json}