use indexmap::IndexMap;
use super::value::{EvalError, EvalResult, Value};
pub fn parse_json(input: &str) -> EvalResult<Value> {
#[cfg(feature = "data-loading")]
{
use serde_json::Value as JsonValue;
let json: JsonValue = serde_json::from_str(input)
.map_err(|e| EvalError::other(format!("JSON parse error: {}", e)))?;
json_to_value(json)
}
#[cfg(not(feature = "data-loading"))]
{
parse_json_simple(input)
}
}
pub fn parse_yaml(input: &str) -> EvalResult<Value> {
#[cfg(feature = "data-loading")]
{
use serde_yaml::Value as YamlValue;
let yaml: YamlValue = serde_yaml::from_str(input)
.map_err(|e| EvalError::other(format!("YAML parse error: {}", e)))?;
yaml_to_value(yaml)
}
#[cfg(not(feature = "data-loading"))]
{
let _ = input; Err(EvalError::other(
"YAML parsing requires 'data-loading' feature".to_string(),
))
}
}
pub fn parse_toml(input: &str) -> EvalResult<Value> {
#[cfg(feature = "data-loading")]
{
let toml_value: toml::Value = input
.parse()
.map_err(|e| EvalError::other(format!("TOML parse error: {}", e)))?;
toml_to_value(toml_value)
}
#[cfg(not(feature = "data-loading"))]
{
let _ = input; Err(EvalError::other(
"TOML parsing requires 'data-loading' feature".to_string(),
))
}
}
pub fn parse_csv(input: &str, has_header: bool) -> EvalResult<Value> {
#[cfg(feature = "data-loading")]
{
let mut reader = csv::ReaderBuilder::new()
.has_headers(has_header)
.from_reader(input.as_bytes());
if has_header {
let headers: Vec<String> = reader
.headers()
.map_err(|e| EvalError::other(format!("CSV header error: {}", e)))?
.iter()
.map(|s| s.to_string())
.collect();
let mut rows = Vec::new();
for result in reader.records() {
let record =
result.map_err(|e| EvalError::other(format!("CSV record error: {}", e)))?;
let mut row = IndexMap::new();
for (i, value) in record.iter().enumerate() {
let key = headers.get(i).cloned().unwrap_or_else(|| i.to_string());
row.insert(key, Value::Str(value.to_string()));
}
rows.push(Value::Dict(row));
}
Ok(Value::Array(rows))
} else {
let mut rows = Vec::new();
for result in reader.records() {
let record =
result.map_err(|e| EvalError::other(format!("CSV record error: {}", e)))?;
let row: Vec<Value> = record.iter().map(|s| Value::Str(s.to_string())).collect();
rows.push(Value::Array(row));
}
Ok(Value::Array(rows))
}
}
#[cfg(not(feature = "data-loading"))]
{
parse_csv_simple(input, has_header)
}
}
#[cfg(feature = "data-loading")]
fn json_to_value(json: serde_json::Value) -> EvalResult<Value> {
use serde_json::Value as JsonValue;
Ok(match json {
JsonValue::Null => Value::None,
JsonValue::Bool(b) => Value::Bool(b),
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int(i)
} else if let Some(f) = n.as_f64() {
Value::Float(f)
} else {
Value::Str(n.to_string())
}
}
JsonValue::String(s) => Value::Str(s),
JsonValue::Array(arr) => {
let values: Result<Vec<_>, _> = arr.into_iter().map(json_to_value).collect();
Value::Array(values?)
}
JsonValue::Object(obj) => {
let mut dict = IndexMap::new();
for (k, v) in obj {
dict.insert(k, json_to_value(v)?);
}
Value::Dict(dict)
}
})
}
#[cfg(feature = "data-loading")]
fn yaml_to_value(yaml: serde_yaml::Value) -> EvalResult<Value> {
use serde_yaml::Value as YamlValue;
Ok(match yaml {
YamlValue::Null => Value::None,
YamlValue::Bool(b) => Value::Bool(b),
YamlValue::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int(i)
} else if let Some(f) = n.as_f64() {
Value::Float(f)
} else {
Value::Str(n.to_string())
}
}
YamlValue::String(s) => Value::Str(s),
YamlValue::Sequence(arr) => {
let values: Result<Vec<_>, _> = arr.into_iter().map(yaml_to_value).collect();
Value::Array(values?)
}
YamlValue::Mapping(obj) => {
let mut dict = IndexMap::new();
for (k, v) in obj {
let key = match k {
YamlValue::String(s) => s,
other => yaml_to_value(other)?.display(),
};
dict.insert(key, yaml_to_value(v)?);
}
Value::Dict(dict)
}
YamlValue::Tagged(tagged) => yaml_to_value(tagged.value)?,
})
}
#[cfg(feature = "data-loading")]
fn toml_to_value(toml_val: toml::Value) -> EvalResult<Value> {
use toml::Value as TomlValue;
Ok(match toml_val {
TomlValue::String(s) => Value::Str(s),
TomlValue::Integer(i) => Value::Int(i),
TomlValue::Float(f) => Value::Float(f),
TomlValue::Boolean(b) => Value::Bool(b),
TomlValue::Datetime(dt) => Value::Str(dt.to_string()),
TomlValue::Array(arr) => {
let values: Result<Vec<_>, _> = arr.into_iter().map(toml_to_value).collect();
Value::Array(values?)
}
TomlValue::Table(obj) => {
let mut dict = IndexMap::new();
for (k, v) in obj {
dict.insert(k, toml_to_value(v)?);
}
Value::Dict(dict)
}
})
}
#[cfg(not(feature = "data-loading"))]
fn parse_json_simple(input: &str) -> EvalResult<Value> {
let input = input.trim();
if input == "null" {
return Ok(Value::None);
}
if input == "true" {
return Ok(Value::Bool(true));
}
if input == "false" {
return Ok(Value::Bool(false));
}
if let Ok(i) = input.parse::<i64>() {
return Ok(Value::Int(i));
}
if let Ok(f) = input.parse::<f64>() {
return Ok(Value::Float(f));
}
if input.starts_with('"') && input.ends_with('"') {
let s = &input[1..input.len() - 1];
let s = s
.replace("\\\"", "\"")
.replace("\\n", "\n")
.replace("\\t", "\t");
return Ok(Value::Str(s));
}
if input.starts_with('[') && input.ends_with(']') {
let inner = &input[1..input.len() - 1].trim();
if inner.is_empty() {
return Ok(Value::Array(Vec::new()));
}
let parts = split_json_elements(inner);
let values: Result<Vec<_>, _> = parts.iter().map(|s| parse_json_simple(s)).collect();
return Ok(Value::Array(values?));
}
if input.starts_with('{') && input.ends_with('}') {
let inner = &input[1..input.len() - 1].trim();
if inner.is_empty() {
return Ok(Value::Dict(IndexMap::new()));
}
let mut dict = IndexMap::new();
let pairs = split_json_elements(inner);
for pair in pairs {
let parts: Vec<&str> = pair.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(EvalError::other(format!("Invalid JSON pair: {}", pair)));
}
let key = parts[0].trim();
let value = parts[1].trim();
let key = if key.starts_with('"') && key.ends_with('"') {
key[1..key.len() - 1].to_string()
} else {
key.to_string()
};
dict.insert(key, parse_json_simple(value)?);
}
return Ok(Value::Dict(dict));
}
Err(EvalError::other(format!("Cannot parse JSON: {}", input)))
}
#[cfg(not(feature = "data-loading"))]
fn split_json_elements(input: &str) -> Vec<String> {
let mut elements = Vec::new();
let mut current = String::new();
let mut depth = 0;
let mut in_string = false;
let mut escape = false;
for c in input.chars() {
if escape {
current.push(c);
escape = false;
continue;
}
match c {
'\\' if in_string => {
current.push(c);
escape = true;
}
'"' => {
current.push(c);
in_string = !in_string;
}
'[' | '{' if !in_string => {
current.push(c);
depth += 1;
}
']' | '}' if !in_string => {
current.push(c);
depth -= 1;
}
',' if !in_string && depth == 0 => {
elements.push(current.trim().to_string());
current = String::new();
}
_ => current.push(c),
}
}
if !current.trim().is_empty() {
elements.push(current.trim().to_string());
}
elements
}
#[cfg(not(feature = "data-loading"))]
fn parse_csv_simple(input: &str, has_header: bool) -> EvalResult<Value> {
let lines: Vec<&str> = input.lines().collect();
if lines.is_empty() {
return Ok(Value::Array(Vec::new()));
}
let parse_row = |line: &str| -> Vec<String> {
line.split(',').map(|s| s.trim().to_string()).collect()
};
if has_header {
let headers = parse_row(lines[0]);
let mut rows = Vec::new();
for line in lines.iter().skip(1) {
if line.is_empty() {
continue;
}
let values = parse_row(line);
let mut row = IndexMap::new();
for (i, value) in values.into_iter().enumerate() {
let key = headers.get(i).cloned().unwrap_or_else(|| i.to_string());
row.insert(key, Value::Str(value));
}
rows.push(Value::Dict(row));
}
Ok(Value::Array(rows))
} else {
let mut rows = Vec::new();
for line in lines {
if line.is_empty() {
continue;
}
let values: Vec<Value> = parse_row(line).into_iter().map(Value::Str).collect();
rows.push(Value::Array(values));
}
Ok(Value::Array(rows))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_json_simple_primitives() {
assert_eq!(parse_json("null").unwrap(), Value::None);
assert_eq!(parse_json("true").unwrap(), Value::Bool(true));
assert_eq!(parse_json("false").unwrap(), Value::Bool(false));
assert_eq!(parse_json("42").unwrap(), Value::Int(42));
assert_eq!(parse_json("3.14").unwrap(), Value::Float(3.14));
assert_eq!(
parse_json("\"hello\"").unwrap(),
Value::Str("hello".to_string())
);
}
#[test]
fn test_parse_json_simple_array() {
let result = parse_json("[1, 2, 3]").unwrap();
if let Value::Array(arr) = result {
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], Value::Int(1));
assert_eq!(arr[1], Value::Int(2));
assert_eq!(arr[2], Value::Int(3));
} else {
panic!("Expected array");
}
}
#[test]
fn test_parse_json_simple_object() {
let result = parse_json("{\"name\": \"test\", \"value\": 42}").unwrap();
if let Value::Dict(dict) = result {
assert_eq!(dict.get("name"), Some(&Value::Str("test".to_string())));
assert_eq!(dict.get("value"), Some(&Value::Int(42)));
} else {
panic!("Expected dict");
}
}
#[test]
fn test_parse_csv_simple() {
let csv = "name,age\nAlice,30\nBob,25";
let result = parse_csv(csv, true).unwrap();
if let Value::Array(rows) = result {
assert_eq!(rows.len(), 2);
if let Value::Dict(ref row) = rows[0] {
assert_eq!(row.get("name"), Some(&Value::Str("Alice".to_string())));
assert_eq!(row.get("age"), Some(&Value::Str("30".to_string())));
}
} else {
panic!("Expected array");
}
}
}