1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112


use std::vec::IntoIter;

use pest::{Parser, error::Error};


#[grammar = "grammar.pest"]
#[derive(Parser)]
struct IONParser;

#[derive(Debug)]
pub(crate) enum IONValue<'a> {
    Object(Vec<(&'a str, IONValue<'a>)>),
    Array(Vec<IONValue<'a>>),
    String(&'a str),
    Number(f64),
    Boolean(bool),
    Null,
}

impl IONValue<'_> {
    fn to_json(&self) -> String {
        match self {
            IONValue::Object(o) => {
                let mut buf = "{".to_string();
                for (key, val) in o {
                    buf.push_str(&format!("\"{}\"", key));
                    buf.push(':');
                    buf.push_str(&val.to_json());
                    buf.push(',');
                }
                buf.remove(buf.len()-1);
                buf.push('}');
                buf
            }
            IONValue::Array(a) => {
                let mut buf = "[".to_string();
                for val in a {
                    buf.push_str(&val.to_json());
                    buf.push(',');
                }
                buf.remove(buf.len()-1);
                buf.push(']');
                buf
            }
            IONValue::String(a) => {
                let mut a = a.to_string();
                if a.as_bytes()[0] as char == '"' {
                    a
                } else if a.as_bytes()[0] as char == '\'' {
                    a.remove(0);
                    a.remove(a.len() - 1);
                    format!("\"{}\"", a)
                } else {
                    format!("\"{}\"", a)
                }
            },
            IONValue::Number(a) => a.to_string(),
            IONValue::Boolean(a) => a.to_string(),
            IONValue::Null => "null".to_string(),
        }
    }
}

pub fn ion_to_json(file: &str) -> Result<String, anyhow::Error> {
    let p = match IONParser::parse(Rule::ion, file)?.next() {
        Some(s) => s,
        None => return Err(anyhow::anyhow!("not a valid file"))
    };
    let p = parse_value(p);
    let json = p.to_json();
    if json != "{" {
        Ok(json)
    } else {
        Ok("".to_string())
    }
}

use pest::iterators::Pair;

use crate::error;
fn parse_value(pair: Pair<Rule>) -> IONValue {
    match pair.as_rule() {
        Rule::ion |
        Rule::object => {

            IONValue::Object(
                pair.into_inner().map(|s| {
                    let mut inner = s.into_inner();
                    let name = inner.next().unwrap().as_span().as_str();
                    let value = inner.next().unwrap();
                    let value = parse_value(value);
                    (name, value)
                }).collect()
            )
        },
        Rule::array => IONValue::Array(pair.into_inner().map(parse_value).collect()),
        Rule::version |
        Rule::string => IONValue::String(pair.as_str()),
        Rule::number => IONValue::Number(pair.as_str().parse().unwrap()),
        Rule::boolean => IONValue::Boolean(pair.as_str().parse().unwrap()),
        Rule::null => IONValue::Null,
        | Rule::key_val
        | Rule::value
        | Rule::qchar
        | Rule::sqchar
        | Rule::COMMENT
        | Rule::WHITESPACE => unreachable!(),
    }
}