use crate::error::{CsvError, Result};
use crate::from_csv::schema_inference::ColumnType;
use hedl_core::lex::{parse_expression_token, parse_tensor};
use hedl_core::Value;
pub(crate) fn parse_csv_value_with_type(field: &str, col_type: ColumnType) -> Result<Value> {
let trimmed = field.trim();
if trimmed.is_empty() || trimmed == "~" {
return Ok(Value::Null);
}
match col_type {
ColumnType::Null => Ok(Value::Null),
ColumnType::Bool => {
if trimmed == "true" {
Ok(Value::Bool(true))
} else if trimmed == "false" {
Ok(Value::Bool(false))
} else {
Ok(Value::String(field.to_string().into()))
}
}
ColumnType::Int => {
if let Ok(n) = trimmed.parse::<i64>() {
Ok(Value::Int(n))
} else {
Ok(Value::String(field.to_string().into()))
}
}
ColumnType::Float => {
if let Ok(f) = trimmed.parse::<f64>() {
Ok(Value::Float(f))
} else {
Ok(Value::String(field.to_string().into()))
}
}
ColumnType::String => {
parse_csv_value(field)
}
}
}
pub(crate) fn parse_csv_value(field: &str) -> Result<Value> {
let trimmed = field.trim();
if trimmed.is_empty() || trimmed == "~" {
return Ok(Value::Null);
}
if trimmed == "true" {
return Ok(Value::Bool(true));
}
if trimmed == "false" {
return Ok(Value::Bool(false));
}
match trimmed {
"NaN" => return Ok(Value::Float(f64::NAN)),
"Infinity" => return Ok(Value::Float(f64::INFINITY)),
"-Infinity" => return Ok(Value::Float(f64::NEG_INFINITY)),
_ => {}
}
if trimmed.starts_with('@') {
return parse_reference(trimmed);
}
if trimmed.starts_with("$(") && trimmed.ends_with(')') {
let expr = parse_expression_token(trimmed).map_err(|e| CsvError::ParseError {
line: 0,
message: format!("Invalid expression: {e}"),
})?;
return Ok(Value::Expression(Box::new(expr)));
}
if trimmed.starts_with('(') && trimmed.ends_with(')') && !trimmed.starts_with("$(") {
return parse_list_value(trimmed);
}
if let Ok(n) = trimmed.parse::<i64>() {
return Ok(Value::Int(n));
}
if let Ok(f) = trimmed.parse::<f64>() {
return Ok(Value::Float(f));
}
if trimmed.starts_with('[') && trimmed.ends_with(']') {
if let Ok(tensor) = parse_tensor(trimmed) {
return Ok(Value::Tensor(Box::new(tensor)));
}
}
Ok(Value::String(field.to_string().into()))
}
pub(crate) fn parse_reference(s: &str) -> Result<Value> {
let without_at = &s[1..];
if let Some(colon_pos) = without_at.find(':') {
let type_name = &without_at[..colon_pos];
let id = &without_at[colon_pos + 1..];
if type_name.is_empty() || id.is_empty() {
return Err(CsvError::ParseError {
line: 0,
message: format!("Invalid reference format: {s}"),
});
}
Ok(Value::Reference(hedl_core::Reference::qualified(
type_name, id,
)))
} else {
if without_at.is_empty() {
return Err(CsvError::ParseError {
line: 0,
message: "Empty reference ID".to_string(),
});
}
Ok(Value::Reference(hedl_core::Reference::local(without_at)))
}
}
fn parse_list_value(s: &str) -> Result<Value> {
let inner = &s[1..s.len() - 1];
let trimmed_inner = inner.trim();
if trimmed_inner.is_empty() {
return Ok(Value::List(Box::default()));
}
let mut items = Vec::new();
let mut current = String::new();
let mut paren_depth = 0;
let mut bracket_depth = 0;
let mut in_expr = false;
for ch in trimmed_inner.chars() {
match ch {
'(' if !in_expr => paren_depth += 1,
')' if !in_expr => paren_depth -= 1,
'[' => bracket_depth += 1,
']' => bracket_depth -= 1,
'$' if current.trim().is_empty() => in_expr = true,
',' if paren_depth == 0 && bracket_depth == 0 && !in_expr => {
let item_str = current.trim();
if !item_str.is_empty() {
items.push(parse_csv_value(item_str)?);
}
current.clear();
continue;
}
_ => {}
}
if in_expr && ch == ')' && paren_depth == 0 {
in_expr = false;
}
current.push(ch);
}
let item_str = current.trim();
if !item_str.is_empty() {
items.push(parse_csv_value(item_str)?);
}
Ok(Value::List(Box::new(items)))
}