use crate::optimizer::optimize_key;
use crate::parser::parse;
use crate::types::{DxArray, DxTable, DxValue};
pub fn toon_to_dx(toon_str: &str) -> Result<String, String> {
let mut output = String::new();
let lines: Vec<&str> = toon_str.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
if line.is_empty() {
i += 1;
continue;
}
if let Some(space_pos) = line.find(' ') {
let key = &line[..space_pos];
let value = line[space_pos + 1..].trim_matches('"');
if i + 1 < lines.len() && lines[i + 1].starts_with(" ") {
let key_opt = optimize_key(key);
let mut nested_props = Vec::new();
let mut j = i + 1;
while j < lines.len() && lines[j].starts_with(" ") {
let nested_line = lines[j].trim();
if let Some(nested_space) = nested_line.find(' ') {
let nested_key = &nested_line[..nested_space];
let nested_val = nested_line[nested_space + 1..].trim_matches('"');
nested_props.push((optimize_key(nested_key), nested_val.to_string()));
}
j += 1;
}
if nested_props.len() <= 4 {
output.push_str(&key_opt);
output.push('.');
for (idx, (k, v)) in nested_props.iter().enumerate() {
if idx > 0 {
output.push('^');
}
output.push_str(k);
output.push(':');
output.push_str(v);
}
output.push('\n');
} else {
for (k, v) in nested_props {
output.push_str(&key_opt);
output.push('.');
output.push_str(&k);
output.push(':');
output.push_str(&v);
output.push('\n');
}
}
i = j;
continue;
} else {
let key_opt = optimize_key(key);
output.push_str(&key_opt);
output.push(':');
output.push_str(value);
output.push('\n');
}
}
i += 1;
}
Ok(output)
}
pub fn dx_to_toon(dx_str: &str) -> Result<String, String> {
let value = parse(dx_str.as_bytes()).map_err(|e| format!("DX parse error: {}", e))?;
let mut output = String::new();
dx_value_to_toon(&value, &mut output, 0)?;
Ok(output)
}
fn dx_value_to_toon(value: &DxValue, output: &mut String, indent: usize) -> Result<(), String> {
let indent_str = " ".repeat(indent);
match value {
DxValue::Object(obj) => {
for (key, val) in obj.iter() {
match val {
DxValue::Object(nested_obj) => {
output.push_str(&indent_str);
output.push_str(key);
output.push('\n');
dx_value_to_toon(&DxValue::Object(nested_obj.clone()), output, indent + 1)?;
}
DxValue::Array(arr) => {
dx_array_to_toon(key, arr, output, indent)?;
}
DxValue::Table(table) => {
dx_table_to_toon(key, table, output, indent)?;
}
_ => {
output.push_str(&indent_str);
output.push_str(key);
output.push(' ');
dx_simple_value_to_toon(val, output)?;
output.push('\n');
}
}
}
}
_ => {
output.push_str(&indent_str);
dx_simple_value_to_toon(value, output)?;
output.push('\n');
}
}
Ok(())
}
fn dx_simple_value_to_toon(value: &DxValue, output: &mut String) -> Result<(), String> {
match value {
DxValue::Null => output.push_str("null"),
DxValue::Bool(true) => output.push_str("true"),
DxValue::Bool(false) => output.push_str("false"),
DxValue::Int(i) => output.push_str(&i.to_string()),
DxValue::Float(f) => output.push_str(&f.to_string()),
DxValue::String(s) => {
output.push('"');
for c in s.chars() {
match c {
'"' => output.push_str("\\\""),
'\\' => output.push_str("\\\\"),
'\n' => output.push_str("\\n"),
'\r' => output.push_str("\\r"),
'\t' => output.push_str("\\t"),
_ => output.push(c),
}
}
output.push('"');
}
DxValue::Array(arr) => {
let items: Vec<String> = arr
.values
.iter()
.map(|v| {
let mut s = String::new();
dx_simple_value_to_toon(v, &mut s).ok();
s
})
.collect();
output.push_str(&items.join(", "));
}
DxValue::Object(_) => output.push_str("{}"),
DxValue::Table(_) => output.push_str("[[]]"),
DxValue::Ref(id) => {
output.push('@');
output.push_str(&id.to_string());
}
}
Ok(())
}
fn dx_array_to_toon(
key: &str,
arr: &DxArray,
output: &mut String,
indent: usize,
) -> Result<(), String> {
let indent_str = " ".repeat(indent);
let is_object_array = arr.values.iter().all(|v| matches!(v, DxValue::Object(_)));
if is_object_array && !arr.values.is_empty() {
if let Some(DxValue::Object(first_obj)) = arr.values.first() {
let fields: Vec<&str> = first_obj.iter().map(|(k, _)| k.as_str()).collect();
let field_list = fields.join(",");
output.push_str(&indent_str);
output.push_str(key);
output.push('[');
output.push_str(&arr.values.len().to_string());
output.push_str("]{");
output.push_str(&field_list);
output.push_str("}:\n");
for val in &arr.values {
if let DxValue::Object(obj) = val {
output.push_str(&indent_str);
output.push_str(" ");
let values: Vec<String> = fields
.iter()
.map(|f| {
let mut s = String::new();
if let Some(v) = obj.get(f) {
dx_simple_value_to_toon(v, &mut s).ok();
}
s
})
.collect();
output.push_str(&values.join(","));
output.push('\n');
}
}
}
} else {
output.push_str(&indent_str);
output.push_str(key);
output.push('[');
output.push_str(&arr.values.len().to_string());
output.push_str("]: ");
let items: Vec<String> = arr
.values
.iter()
.map(|v| {
let mut s = String::new();
dx_simple_value_to_toon(v, &mut s).ok();
s
})
.collect();
output.push_str(&items.join(", "));
output.push('\n');
}
Ok(())
}
fn dx_table_to_toon(
key: &str,
table: &DxTable,
output: &mut String,
indent: usize,
) -> Result<(), String> {
let indent_str = " ".repeat(indent);
let fields: Vec<&str> = table
.schema
.columns
.iter()
.map(|c| c.name.as_str())
.collect();
let field_list = fields.join(",");
output.push_str(&indent_str);
output.push_str(key);
output.push('[');
output.push_str(&table.rows.len().to_string());
output.push_str("]{");
output.push_str(&field_list);
output.push_str("}:\n");
for row in &table.rows {
output.push_str(&indent_str);
output.push_str(" ");
let values: Vec<String> = row
.iter()
.map(|v| {
let mut s = String::new();
dx_simple_value_to_toon(v, &mut s).ok();
s
})
.collect();
output.push_str(&values.join(","));
output.push('\n');
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_toon_to_dx() {
let toon = r#"
name "test"
version "1.0.0"
"#;
let dx = toon_to_dx(toon).unwrap();
assert!(dx.contains("n:test"));
}
#[test]
fn test_dx_to_toon_simple() {
let dx = "name:test\nversion:100";
let result = dx_to_toon(dx);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
let toon = result.unwrap();
assert!(toon.contains("name"), "Missing 'name' key");
assert!(toon.contains("test"), "Missing 'test' value");
assert!(toon.contains("version"), "Missing 'version' key");
assert!(toon.contains("100"), "Missing '100' value");
}
#[test]
fn test_dx_to_toon_numbers() {
let dx = "count:42\nprice:19.99";
let toon = dx_to_toon(dx).unwrap();
assert!(toon.contains("count 42"));
assert!(toon.contains("price 19.99"));
}
#[test]
fn test_dx_to_toon_booleans() {
let dx = "active:+\ndisabled:-";
let toon = dx_to_toon(dx).unwrap();
assert!(toon.contains("active true"));
assert!(toon.contains("disabled false"));
}
#[test]
fn test_dx_to_toon_array() {
let dx = "tags>alpha|beta|gamma";
let toon = dx_to_toon(dx).unwrap();
assert!(toon.contains("tags[3]"));
assert!(toon.contains("alpha"));
assert!(toon.contains("beta"));
assert!(toon.contains("gamma"));
}
#[test]
fn test_dx_to_toon_table() {
let dx = "users=id%i name%s\n1 Alice\n2 Bob";
let toon = dx_to_toon(dx).unwrap();
assert!(toon.contains("users[2]{id,name}"));
assert!(toon.contains("1,\"Alice\""));
assert!(toon.contains("2,\"Bob\""));
}
}