use super::error::LexError;
use super::expression::{parse_expression_token, Expression};
use super::span::SourcePos;
use super::tokens::{parse_reference, Reference};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
Reference(Reference),
Expression(Expression),
Tensor(Vec<TensorValue>),
List(Vec<Value>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum TensorValue {
Number(f64),
Array(Vec<TensorValue>),
}
pub fn infer_value(s: &str, aliases: Option<&HashMap<String, String>>) -> Result<Value, LexError> {
infer_value_with_null_char(s, aliases, '~')
}
pub fn infer_value_with_null_char(
s: &str,
aliases: Option<&HashMap<String, String>>,
null_char: char,
) -> Result<Value, LexError> {
infer_value_impl(s, aliases, false, None, null_char)
}
pub fn infer_cell_value(
s: &str,
column_idx: usize,
prev_row: Option<&[Value]>,
aliases: Option<&HashMap<String, String>>,
) -> Result<Value, LexError> {
infer_cell_value_with_null_char(s, column_idx, prev_row, aliases, '~')
}
pub fn infer_cell_value_with_null_char(
s: &str,
column_idx: usize,
prev_row: Option<&[Value]>,
aliases: Option<&HashMap<String, String>>,
null_char: char,
) -> Result<Value, LexError> {
infer_value_impl(
s,
aliases,
true,
prev_row.and_then(|r| r.get(column_idx)),
null_char,
)
}
fn infer_value_impl(
s: &str,
aliases: Option<&HashMap<String, String>>,
allow_ditto: bool,
prev_value: Option<&Value>,
null_char: char,
) -> Result<Value, LexError> {
let trimmed = s.trim();
let value_type = detect_value_type(
trimmed,
allow_ditto,
prev_value.is_some(),
aliases,
null_char,
)?;
construct_value(value_type, trimmed, aliases, prev_value)
}
#[derive(Debug, Clone, PartialEq)]
enum ValueType {
Null,
Ditto,
List,
Tensor,
Reference,
Expression,
Alias(String),
Boolean(bool),
Number,
String,
}
fn detect_value_type(
trimmed: &str,
allow_ditto: bool,
has_prev_value: bool,
aliases: Option<&HashMap<String, String>>,
null_char: char,
) -> Result<ValueType, LexError> {
if trimmed.len() == 1 && trimmed.starts_with(null_char) {
return Ok(ValueType::Null);
}
if allow_ditto && trimmed == "^" {
if has_prev_value {
return Ok(ValueType::Ditto);
} else {
return Err(LexError::InvalidToken {
message: "ditto operator (^) used without previous row value".to_string(),
pos: SourcePos::default(),
});
}
}
if trimmed.starts_with('(') {
return Ok(ValueType::List);
}
if trimmed.starts_with('[') {
return Ok(ValueType::Tensor);
}
if trimmed.starts_with('@') {
return Ok(ValueType::Reference);
}
if trimmed.starts_with("$(") {
return Ok(ValueType::Expression);
}
if let Some(key) = trimmed.strip_prefix('%') {
if let Some(aliases_map) = aliases {
if aliases_map.contains_key(key) {
return Ok(ValueType::Alias(key.to_string()));
}
}
}
if trimmed == "true" {
return Ok(ValueType::Boolean(true));
}
if trimmed == "false" {
return Ok(ValueType::Boolean(false));
}
if is_number(trimmed) {
return Ok(ValueType::Number);
}
Ok(ValueType::String)
}
fn construct_value(
value_type: ValueType,
trimmed: &str,
aliases: Option<&HashMap<String, String>>,
prev_value: Option<&Value>,
) -> Result<Value, LexError> {
match value_type {
ValueType::Null => Ok(Value::Null),
ValueType::Ditto => {
Ok(prev_value
.expect("detect_value_type guarantees prev_value")
.clone())
}
ValueType::List => {
let (value, _consumed) = parse_list_literal(trimmed, 0)?;
Ok(value)
}
ValueType::Tensor => parse_tensor(trimmed).map(Value::Tensor),
ValueType::Reference => parse_reference(trimmed).map(Value::Reference),
ValueType::Expression => parse_expression_token(trimmed).map(Value::Expression),
ValueType::Alias(key) => {
let alias_map = aliases.expect("detect_value_type guarantees aliases");
let expanded = alias_map
.get(&key)
.expect("detect_value_type guarantees key exists");
infer_value_impl(expanded, None, false, None, '~')
}
ValueType::Boolean(b) => Ok(Value::Bool(b)),
ValueType::Number => parse_number(trimmed),
ValueType::String => Ok(Value::String(trimmed.to_string())),
}
}
fn is_number(s: &str) -> bool {
if !s.contains('.') && s.parse::<i64>().is_ok() {
return true;
}
s.parse::<f64>().is_ok()
}
fn parse_number(s: &str) -> Result<Value, LexError> {
if !s.contains('.') {
if let Ok(i) = s.parse::<i64>() {
return Ok(Value::Int(i));
}
}
if let Ok(f) = s.parse::<f64>() {
return Ok(Value::Float(f));
}
Err(LexError::InvalidToken {
message: format!("invalid number: {}", s),
pos: SourcePos::default(),
})
}
fn parse_tensor(s: &str) -> Result<Vec<TensorValue>, LexError> {
let trimmed = s.trim();
if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
return Err(LexError::InvalidToken {
message: "tensor must be enclosed in []".to_string(),
pos: SourcePos::default(),
});
}
let content = &trimmed[1..trimmed.len() - 1].trim();
if content.is_empty() {
return Ok(Vec::new());
}
parse_tensor_content(content)
}
fn parse_tensor_content(s: &str) -> Result<Vec<TensorValue>, LexError> {
let mut result = Vec::new();
let mut current = String::new();
let mut depth = 0;
let chars: Vec<char> = s.chars().collect();
let mut i = 0;
while i < chars.len() {
let ch = chars[i];
match ch {
'[' => {
depth += 1;
current.push(ch);
}
']' => {
depth -= 1;
current.push(ch);
if depth < 0 {
return Err(LexError::InvalidToken {
message: "unbalanced brackets in tensor".to_string(),
pos: SourcePos::default(),
});
}
}
',' if depth == 0 => {
let elem = current.trim();
if !elem.is_empty() {
result.push(parse_tensor_element(elem)?);
}
current.clear();
}
_ => {
current.push(ch);
}
}
i += 1;
}
let elem = current.trim();
if !elem.is_empty() {
result.push(parse_tensor_element(elem)?);
}
if depth != 0 {
return Err(LexError::InvalidToken {
message: "unbalanced brackets in tensor".to_string(),
pos: SourcePos::default(),
});
}
Ok(result)
}
fn parse_tensor_element(s: &str) -> Result<TensorValue, LexError> {
let trimmed = s.trim();
if trimmed.starts_with('[') {
let nested = parse_tensor(trimmed)?;
Ok(TensorValue::Array(nested))
} else {
let num = trimmed.parse::<f64>().map_err(|_| LexError::InvalidToken {
message: format!("invalid number in tensor: {}", trimmed),
pos: SourcePos::default(),
})?;
Ok(TensorValue::Number(num))
}
}
fn strip_quotes(s: &str) -> &str {
if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
&s[1..s.len() - 1]
} else {
s
}
}
pub fn parse_list_literal(input: &str, pos: usize) -> Result<(Value, usize), LexError> {
let chars: Vec<char> = input.chars().collect();
if pos >= chars.len() {
return Err(LexError::InvalidToken {
message: "unexpected end of input, expected '('".to_string(),
pos: SourcePos::default(),
});
}
if chars[pos] != '(' {
return Err(LexError::InvalidToken {
message: format!("expected '(' to start list, found '{}'", chars[pos]),
pos: SourcePos::default(),
});
}
let mut current_pos = pos + 1; let mut elements = Vec::new();
let mut current_element = String::new();
let mut in_quotes = false;
let mut in_expression = false;
let mut paren_depth = 0;
let mut bracket_depth = 0;
while current_pos < chars.len() {
let ch = chars[current_pos];
match ch {
'"' if !in_expression => {
in_quotes = !in_quotes;
current_element.push(ch);
}
'$' if !in_quotes && current_pos + 1 < chars.len() && chars[current_pos + 1] == '(' => {
in_expression = true;
current_element.push(ch);
}
'(' if !in_quotes => {
if in_expression {
paren_depth += 1;
}
current_element.push(ch);
}
')' if !in_quotes => {
if in_expression && paren_depth > 0 {
paren_depth -= 1;
current_element.push(ch);
if paren_depth == 0 {
in_expression = false;
}
} else {
let trimmed = current_element.trim();
if !trimmed.is_empty() {
let unquoted = strip_quotes(trimmed);
let value = infer_value_impl(unquoted, None, false, None, '~')?;
elements.push(value);
} else if !elements.is_empty() {
return Err(LexError::InvalidToken {
message: "trailing comma in list not allowed".to_string(),
pos: SourcePos::default(),
});
}
return Ok((Value::List(elements), current_pos + 1 - pos));
}
}
'[' if !in_quotes && !in_expression => {
bracket_depth += 1;
current_element.push(ch);
}
']' if !in_quotes && !in_expression => {
bracket_depth -= 1;
if bracket_depth < 0 {
return Err(LexError::InvalidToken {
message: "unmatched ']' in list element".to_string(),
pos: SourcePos::default(),
});
}
current_element.push(ch);
}
',' if !in_quotes && !in_expression && paren_depth == 0 && bracket_depth == 0 => {
let trimmed = current_element.trim();
if trimmed.is_empty() {
return Err(LexError::InvalidToken {
message: "empty element in list (consecutive commas)".to_string(),
pos: SourcePos::default(),
});
}
let unquoted = strip_quotes(trimmed);
let value = infer_value_impl(unquoted, None, false, None, '~')?;
elements.push(value);
current_element.clear();
}
_ => {
current_element.push(ch);
}
}
current_pos += 1;
}
Err(LexError::InvalidToken {
message: "unclosed list literal, expected ')'".to_string(),
pos: SourcePos::default(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infer_null() {
let v = infer_value("~", None).unwrap();
assert!(matches!(v, Value::Null));
}
#[test]
fn test_infer_null_with_whitespace() {
let v = infer_value(" ~ ", None).unwrap();
assert!(matches!(v, Value::Null));
}
#[test]
fn test_infer_bool_true() {
let v = infer_value("true", None).unwrap();
assert!(matches!(v, Value::Bool(true)));
}
#[test]
fn test_infer_bool_false() {
let v = infer_value("false", None).unwrap();
assert!(matches!(v, Value::Bool(false)));
}
#[test]
fn test_infer_bool_with_whitespace() {
let v = infer_value(" true ", None).unwrap();
assert!(matches!(v, Value::Bool(true)));
}
#[test]
fn test_infer_bool_case_sensitive() {
let v = infer_value("TRUE", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "TRUE"));
let v = infer_value("True", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "True"));
let v = infer_value("FALSE", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "FALSE"));
}
#[test]
fn test_infer_int() {
let v = infer_value("42", None).unwrap();
assert!(matches!(v, Value::Int(42)));
}
#[test]
fn test_infer_negative_int() {
let v = infer_value("-123", None).unwrap();
assert!(matches!(v, Value::Int(-123)));
}
#[test]
fn test_infer_zero() {
let v = infer_value("0", None).unwrap();
assert!(matches!(v, Value::Int(0)));
}
#[test]
fn test_infer_large_int() {
let v = infer_value("9223372036854775807", None).unwrap(); assert!(matches!(v, Value::Int(9223372036854775807)));
}
#[test]
fn test_infer_min_int() {
let v = infer_value("-9223372036854775808", None).unwrap(); assert!(matches!(v, Value::Int(-9223372036854775808)));
}
#[test]
fn test_infer_int_with_whitespace() {
let v = infer_value(" 42 ", None).unwrap();
assert!(matches!(v, Value::Int(42)));
}
#[test]
fn test_infer_float() {
let v = infer_value("3.25", None).unwrap();
assert!(matches!(v, Value::Float(f) if (f - 3.25).abs() < 0.001));
}
#[test]
fn test_infer_float_explicit() {
let v = infer_value("42.0", None).unwrap();
assert!(matches!(v, Value::Float(f) if (f - 42.0).abs() < 0.001));
}
#[test]
fn test_infer_negative_float() {
let v = infer_value("-3.5", None).unwrap();
assert!(matches!(v, Value::Float(f) if (f + 3.5).abs() < 0.001));
}
#[test]
fn test_infer_float_scientific() {
let v = infer_value("1e10", None).unwrap();
assert!(matches!(v, Value::Float(f) if (f - 1e10).abs() < 1e5));
}
#[test]
fn test_infer_float_scientific_negative_exp() {
let v = infer_value("1e-10", None).unwrap();
assert!(matches!(v, Value::Float(f) if (f - 1e-10).abs() < 1e-15));
}
#[test]
fn test_infer_float_very_small() {
let v = infer_value("0.000001", None).unwrap();
assert!(matches!(v, Value::Float(f) if (f - 0.000001).abs() < 1e-12));
}
#[test]
fn test_infer_string() {
let v = infer_value("hello", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "hello"));
}
#[test]
fn test_infer_string_with_spaces() {
let v = infer_value(" hello world ", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "hello world"));
}
#[test]
fn test_infer_string_with_special_chars() {
let v = infer_value("hello@world.com", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "hello@world.com"));
}
#[test]
fn test_infer_string_looks_like_number_but_isnt() {
let v = infer_value("42abc", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "42abc"));
let v = infer_value("3.5.15", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "3.5.15"));
}
#[test]
fn test_infer_string_unicode() {
let v = infer_value("日本語テã‚スト", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "日本語テã‚スト"));
let v = infer_value("émoji 😀", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "émoji 😀"));
}
#[test]
fn test_infer_string_empty_after_trim() {
let v = infer_value(" ", None).unwrap();
assert!(matches!(v, Value::String(s) if s.is_empty()));
}
#[test]
fn test_infer_reference() {
let v = infer_value("@user_1", None).unwrap();
assert!(matches!(v, Value::Reference(r) if r.id == "user_1"));
}
#[test]
fn test_infer_qualified_reference() {
let v = infer_value("@User:user_1", None).unwrap();
assert!(matches!(v, Value::Reference(r)
if r.type_name == Some("User".to_string()) && r.id == "user_1"));
}
#[test]
fn test_infer_reference_with_hyphen() {
let v = infer_value("@user-1", None).unwrap();
assert!(matches!(v, Value::Reference(r) if r.id == "user-1"));
}
#[test]
fn test_infer_reference_with_whitespace() {
let v = infer_value(" @user_1 ", None).unwrap();
assert!(matches!(v, Value::Reference(r) if r.id == "user_1"));
}
#[test]
fn test_infer_expression() {
let v = infer_value("$(now())", None).unwrap();
match v {
Value::Expression(expr) => {
assert!(
matches!(expr, Expression::Call { name, args, .. } if name == "now" && args.is_empty())
);
}
_ => panic!("expected Expression"),
}
}
#[test]
fn test_infer_expression_with_args() {
let v = infer_value("$(concat(a, b))", None).unwrap();
match v {
Value::Expression(expr) => {
assert!(matches!(expr, Expression::Call { name, .. } if name == "concat"));
}
_ => panic!("expected Expression"),
}
}
#[test]
fn test_infer_expression_with_whitespace() {
let v = infer_value(" $(now()) ", None).unwrap();
assert!(matches!(v, Value::Expression(_)));
}
#[test]
fn test_infer_tensor_simple() {
let v = infer_value("[1, 2, 3]", None).unwrap();
assert!(matches!(v, Value::Tensor(_)));
}
#[test]
fn test_infer_tensor_nested() {
let v = infer_value("[[1, 2], [3, 4]]", None).unwrap();
assert!(matches!(v, Value::Tensor(_)));
}
#[test]
fn test_infer_tensor_with_whitespace() {
let v = infer_value(" [ 1 , 2 , 3 ] ", None).unwrap();
assert!(matches!(v, Value::Tensor(_)));
}
#[test]
fn test_infer_tensor_floats() {
let v = infer_value("[1.5, 2.5, 3.5]", None).unwrap();
if let Value::Tensor(t) = v {
assert_eq!(t.len(), 3);
assert!(matches!(t[0], TensorValue::Number(n) if (n - 1.5).abs() < 0.001));
} else {
panic!("expected Tensor");
}
}
#[test]
fn test_infer_tensor_negative() {
let v = infer_value("[-1, -2, -3]", None).unwrap();
if let Value::Tensor(t) = v {
assert_eq!(t.len(), 3);
assert!(matches!(t[0], TensorValue::Number(n) if (n + 1.0).abs() < 0.001));
} else {
panic!("expected Tensor");
}
}
#[test]
fn test_infer_alias_expansion() {
let mut aliases = HashMap::new();
aliases.insert("active".to_string(), "true".to_string());
let v = infer_value("%active", Some(&aliases)).unwrap();
assert!(matches!(v, Value::Bool(true)));
}
#[test]
fn test_infer_alias_expansion_number() {
let mut aliases = HashMap::new();
aliases.insert("rate".to_string(), "1.23456".to_string());
let v = infer_value("%rate", Some(&aliases)).unwrap();
assert!(matches!(v, Value::Float(_)));
}
#[test]
fn test_infer_alias_not_found_becomes_string() {
let v = infer_value("%unknown", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "%unknown"));
}
#[test]
fn test_infer_alias_expansion_to_string() {
let mut aliases = HashMap::new();
aliases.insert("greeting".to_string(), "hello world".to_string());
let v = infer_value("%greeting", Some(&aliases)).unwrap();
assert!(matches!(v, Value::String(s) if s == "hello world"));
}
#[test]
fn test_infer_ditto_in_keyvalue_is_string() {
let v = infer_value("^", None).unwrap();
assert!(matches!(v, Value::String(s) if s == "^"));
}
#[test]
fn test_infer_cell_ditto() {
let prev_row = vec![Value::Int(42), Value::String("test".to_string())];
let v = infer_cell_value("^", 0, Some(&prev_row), None).unwrap();
assert!(matches!(v, Value::Int(42)));
}
#[test]
fn test_infer_cell_ditto_second_column() {
let prev_row = vec![Value::Int(42), Value::String("test".to_string())];
let v = infer_cell_value("^", 1, Some(&prev_row), None).unwrap();
assert!(matches!(v, Value::String(s) if s == "test"));
}
#[test]
fn test_infer_cell_ditto_bool() {
let prev_row = vec![Value::Bool(true)];
let v = infer_cell_value("^", 0, Some(&prev_row), None).unwrap();
assert!(matches!(v, Value::Bool(true)));
}
#[test]
fn test_infer_cell_ditto_null() {
let prev_row = vec![Value::Null];
let v = infer_cell_value("^", 0, Some(&prev_row), None).unwrap();
assert!(matches!(v, Value::Null));
}
#[test]
fn test_infer_cell_ditto_no_prev_row_error() {
let result = infer_cell_value("^", 0, None, None);
assert!(result.is_err());
}
#[test]
fn test_infer_cell_ditto_out_of_bounds() {
let prev_row = vec![Value::Int(42)];
let result = infer_cell_value("^", 1, Some(&prev_row), None);
assert!(result.is_err());
}
#[test]
fn test_infer_cell_regular_value() {
let prev_row = vec![Value::Int(42)];
let v = infer_cell_value("100", 0, Some(&prev_row), None).unwrap();
assert!(matches!(v, Value::Int(100)));
}
#[test]
fn test_parse_tensor_simple() {
let result = parse_tensor("[1, 2, 3]").unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_parse_tensor_nested() {
let result = parse_tensor("[[1, 2], [3, 4]]").unwrap();
assert_eq!(result.len(), 2);
assert!(matches!(result[0], TensorValue::Array(_)));
}
#[test]
fn test_parse_tensor_empty() {
let result = parse_tensor("[]").unwrap();
assert!(result.is_empty());
}
#[test]
fn test_parse_tensor_deeply_nested() {
let result = parse_tensor("[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]").unwrap();
assert_eq!(result.len(), 2);
}
#[test]
fn test_parse_tensor_single_element() {
let result = parse_tensor("[42]").unwrap();
assert_eq!(result.len(), 1);
assert!(matches!(result[0], TensorValue::Number(n) if (n - 42.0).abs() < 0.001));
}
#[test]
fn test_parse_tensor_floats() {
let result = parse_tensor("[1.5, 2.5]").unwrap();
assert_eq!(result.len(), 2);
assert!(matches!(result[0], TensorValue::Number(n) if (n - 1.5).abs() < 0.001));
}
#[test]
fn test_parse_tensor_unbalanced_error() {
assert!(parse_tensor("[1, 2, [3, 4]").is_err());
assert!(parse_tensor("[1, 2]]").is_err());
}
#[test]
fn test_parse_tensor_invalid_content() {
assert!(parse_tensor("[1, abc, 3]").is_err());
}
#[test]
fn test_value_clone() {
let v1 = Value::Int(42);
let v2 = v1.clone();
assert_eq!(v1, v2);
let v1 = Value::String("test".to_string());
let v2 = v1.clone();
assert_eq!(v1, v2);
}
#[test]
fn test_value_equality() {
assert_eq!(Value::Null, Value::Null);
assert_eq!(Value::Bool(true), Value::Bool(true));
assert_eq!(Value::Int(42), Value::Int(42));
assert_ne!(Value::Int(42), Value::Int(43));
assert_ne!(Value::Int(42), Value::Float(42.0));
}
#[test]
fn test_tensor_value_equality() {
let t1 = TensorValue::Number(1.0);
let t2 = TensorValue::Number(1.0);
assert_eq!(t1, t2);
let t3 = TensorValue::Array(vec![TensorValue::Number(1.0)]);
let t4 = TensorValue::Array(vec![TensorValue::Number(1.0)]);
assert_eq!(t3, t4);
}
#[test]
fn test_inference_ladder_null_first() {
let v = infer_value("~", None).unwrap();
assert!(matches!(v, Value::Null));
}
#[test]
fn test_inference_ladder_tensor_before_string() {
let v = infer_value("[1]", None).unwrap();
assert!(matches!(v, Value::Tensor(_)));
}
#[test]
fn test_inference_ladder_reference_before_string() {
let v = infer_value("@valid_id", None).unwrap();
assert!(matches!(v, Value::Reference(_)));
}
#[test]
fn test_inference_ladder_bool_before_string() {
let v = infer_value("true", None).unwrap();
assert!(matches!(v, Value::Bool(true)));
}
#[test]
fn test_inference_ladder_number_before_string() {
let v = infer_value("42", None).unwrap();
assert!(matches!(v, Value::Int(42)));
}
}