use crate::marshal::{register_typed_fn_1, register_typed_fn_2_full};
use crate::module_exports::{ModuleExports, ModuleParam};
use crate::type_schema::register_predeclared_any_schema;
use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
use shape_value::heap_value::{HeapValue, TypedObjectStorage};
use shape_value::{NativeKind, ValueSlot};
use std::sync::Arc;
fn rows_from_heap_array(
rows: &[Arc<HeapValue>],
fn_name: &str,
) -> Result<Vec<Vec<String>>, String> {
let _ = rows;
Err(format!(
"{}: V3-S5 ckpt-5-prime²c SURFACE — per-row outer-array-arm \
consumer needs Vec<Arc<HeapValue>> rewire for the deleted \
outer-array-arm. Round 2 follow-up. ADR-006 §2.7.24 Q25.A \
SUPERSEDED.",
fn_name
))
}
pub fn create_csv_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::csv");
module.description = "CSV parsing and serialization".to_string();
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"parse",
"Parse CSV text into an array of rows (each row is an array of strings)",
"text",
"string",
ConcreteType::ArrayHeapValue("Array<Array<string>>".to_string()),
|text, _ctx| {
let _ = text;
Err(format!(
"csv.parse(): SURFACE — `Array<Array<string>>` needs a \
nested-array variant in ADR-006 \
§2.7.24 Q25.A's spec list. Tracked as \
W17-typed-carrier-array-typedarray follow-up (out of \
bundle-A-followups scope). Use `csv.parse_records` for \
per-record TypedObject access. ADR-006 §2.7.24 Q25.A."
))
},
);
register_typed_fn_2_full::<_, Vec<Arc<HeapValue>>, Arc<String>>(
&mut module,
"stringify",
"Convert an array of rows to a CSV string",
[
ModuleParam {
name: "data".to_string(),
type_name: "Array<Array<string>>".to_string(),
required: true,
description: "Array of rows, each row is an array of field strings".to_string(),
..Default::default()
},
ModuleParam {
name: "delimiter".to_string(),
type_name: "string".to_string(),
required: false,
description: "Field delimiter character (default: comma)".to_string(),
default_snippet: Some("\",\"".to_string()),
..Default::default()
},
],
ConcreteType::String,
|data, delimiter, _ctx| {
let rows = rows_from_heap_array(&data, "csv.stringify()")?;
let delim_byte = delimiter
.as_bytes()
.first()
.copied()
.unwrap_or(b',');
let mut writer = csv::WriterBuilder::new()
.delimiter(delim_byte)
.from_writer(Vec::new());
for row in &rows {
writer
.write_record(row)
.map_err(|e| format!("csv.stringify() failed: {}", e))?;
}
let bytes = writer
.into_inner()
.map_err(|e| format!("csv.stringify() failed to flush: {}", e))?;
let output = String::from_utf8(bytes)
.map_err(|e| format!("csv.stringify() UTF-8 error: {}", e))?;
Ok(TypedReturn::Concrete(ConcreteReturn::String(output)))
},
);
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"read_file",
"Read and parse a CSV file into an array of rows",
"path",
"string",
ConcreteType::Result(Box::new(ConcreteType::ArrayHeapValue(
"Array<Array<string>>".to_string(),
))),
|path, _ctx| {
let _ = path;
Err(format!(
"csv.read_file(): SURFACE — `Array<Array<string>>` needs a \
nested-array variant in ADR-006 \
§2.7.24 Q25.A's spec list. Tracked as \
W17-typed-carrier-array-typedarray follow-up. ADR-006 §2.7.24 Q25.A."
))
},
);
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"is_valid",
"Check if a string is valid CSV",
"text",
"string",
ConcreteType::Bool,
|text, _ctx| {
let mut reader = csv::ReaderBuilder::new()
.has_headers(false)
.from_reader(text.as_bytes());
let valid = reader.records().all(|r| r.is_ok());
Ok(TypedReturn::Concrete(ConcreteReturn::Bool(valid)))
},
);
register_typed_fn_1::<_, Arc<String>>(
&mut module,
"parse_records",
"Parse CSV text using the header row as keys, returning an array of typed records",
"text",
"string",
ConcreteType::ArrayHeapValue("Array<object>".to_string()),
|text, _ctx| {
let mut reader = csv::ReaderBuilder::new()
.has_headers(true)
.from_reader(text.as_bytes());
let headers: Vec<String> = reader
.headers()
.map_err(|e| format!("csv.parse_records() failed to read headers: {}", e))?
.iter()
.map(|h| h.to_string())
.collect();
let schema_id = register_predeclared_any_schema(&headers);
let field_kinds: Arc<[NativeKind]> = Arc::from(
vec![NativeKind::String; headers.len()].into_boxed_slice(),
);
let heap_mask: u64 = if headers.len() >= 64 {
u64::MAX
} else {
(1u64 << headers.len()) - 1
};
let mut records: Vec<Arc<HeapValue>> = Vec::new();
for result in reader.records() {
let record =
result.map_err(|e| format!("csv.parse_records() failed: {}", e))?;
let n = headers.len().min(record.len());
let mut slots: Vec<ValueSlot> = Vec::with_capacity(headers.len());
for i in 0..headers.len() {
let cell = if i < n {
record.get(i).unwrap_or("").to_string()
} else {
String::new()
};
slots.push(ValueSlot::from_string_arc(Arc::new(cell)));
}
let storage = TypedObjectStorage::_new(
schema_id as u64,
slots.into_boxed_slice(),
heap_mask,
Arc::clone(&field_kinds),
);
records.push(Arc::new(HeapValue::TypedObject(
shape_value::heap_value::TypedObjectPtr::new(storage),
)));
}
Ok(TypedReturn::Concrete(ConcreteReturn::ArrayHeapValue(
records,
)))
},
);
register_typed_fn_2_full::<_, Vec<Arc<HeapValue>>, Vec<Arc<String>>>(
&mut module,
"stringify_records",
"Convert an array of hashmaps to a CSV string with headers",
[
ModuleParam {
name: "data".to_string(),
type_name: "Array<HashMap<string, string>>".to_string(),
required: true,
description: "Array of records (hashmaps with string keys and values)"
.to_string(),
..Default::default()
},
ModuleParam {
name: "headers".to_string(),
type_name: "Array<string>".to_string(),
required: false,
description: "Explicit header order (default: keys from first record)"
.to_string(),
default_snippet: Some("[]".to_string()),
..Default::default()
},
],
ConcreteType::String,
|data, explicit_headers, _ctx| {
let headers: Vec<String> = if !explicit_headers.is_empty() {
explicit_headers.iter().map(|s| (**s).clone()).collect()
} else if let Some(first) = data.first() {
match &**first {
HeapValue::HashMap(kref) => {
let keys_ptr = match kref {
shape_value::heap_value::HashMapKindedRef::I64(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::F64(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::Bool(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::Char(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::String(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::Decimal(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::TypedObject(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::TraitObject(arc) => arc.keys,
shape_value::heap_value::HashMapKindedRef::HashMap(arc) => arc.keys,
};
let n = unsafe { shape_value::v2::typed_array::TypedArray::len(keys_ptr) as usize };
(0..n)
.map(|i| unsafe {
let ptr = shape_value::v2::typed_array::TypedArray::get_unchecked(keys_ptr, i as u32);
shape_value::v2::string_obj::StringObj::as_str(ptr).to_owned()
})
.collect()
}
HeapValue::TypedObject(s) => {
let schema = crate::type_schema::lookup_schema_by_id_public(
s.schema_id as u32,
)
.ok_or_else(|| {
format!(
"csv.stringify_records(): TypedObject schema id {} \
not registered",
s.schema_id
)
})?;
schema.fields.iter().map(|f| f.name.clone()).collect()
}
other => {
return Err(format!(
"csv.stringify_records(): each element must be a record \
(HashMap or TypedObject), got {}",
other.type_name()
));
}
}
} else {
return Ok(TypedReturn::Concrete(ConcreteReturn::String(
String::new(),
)));
};
let mut writer = csv::WriterBuilder::new().from_writer(Vec::new());
writer
.write_record(&headers)
.map_err(|e| format!("csv.stringify_records() header write failed: {}", e))?;
for record_arc in data.iter() {
let row: Vec<String> = match &**record_arc {
HeapValue::HashMap(kref) => {
use shape_value::heap_value::HashMapKindedRef;
match kref {
HashMapKindedRef::String(arc) => headers
.iter()
.map(|h| {
arc.get_index(h.as_str())
.map(|idx| {
let ptr: *const shape_value::v2::string_obj::StringObj =
unsafe { *(*arc.values).data.add(idx) };
unsafe {
shape_value::v2::string_obj::StringObj::as_str(ptr).to_owned()
}
})
.unwrap_or_default()
})
.collect(),
other => {
return Err(format!(
"csv.stringify_records(): HashMap records must be \
HashMap<string, string>, got V={:?}",
other.values_kind()
));
}
}
}
HeapValue::TypedObject(storage) => {
let schema = crate::type_schema::lookup_schema_by_id_public(
storage.schema_id as u32,
)
.ok_or_else(|| {
format!(
"csv.stringify_records(): TypedObject schema id {} \
not registered",
storage.schema_id
)
})?;
let mut r = Vec::with_capacity(headers.len());
for header in &headers {
let cell = match schema
.fields
.iter()
.position(|f| f.name == *header)
{
Some(idx) if idx < storage.slots.len() => {
let bits = storage.slots[idx].raw();
if bits == 0 {
String::new()
} else {
unsafe {
let arc_ptr = bits as *const String;
Arc::increment_strong_count(arc_ptr);
let arc = Arc::from_raw(arc_ptr);
let owned = (*arc).clone();
owned
}
}
}
_ => String::new(),
};
r.push(cell);
}
r
}
other => {
return Err(format!(
"csv.stringify_records(): each element must be a record \
(HashMap or TypedObject), got {}",
other.type_name()
));
}
};
writer
.write_record(&row)
.map_err(|e| format!("csv.stringify_records() row write failed: {}", e))?;
}
let bytes = writer
.into_inner()
.map_err(|e| format!("csv.stringify_records() flush failed: {}", e))?;
let output = String::from_utf8(bytes)
.map_err(|e| format!("csv.stringify_records() UTF-8 error: {}", e))?;
Ok(TypedReturn::Concrete(ConcreteReturn::String(output)))
},
);
module
}