use earthsci_ast::adapter_support::{parse_manifest_output_args, write_report};
use earthsci_ast::{
Key, Num, Ranking, SemiringOp, canonical_index_set_json, distinct, group_aggregate, rank,
serialize_pairs, skolem, skolem_edge,
};
use serde_json::{Map, Value, json};
use std::path::Path;
use std::process::ExitCode;
fn main() -> ExitCode {
let args = match parse_manifest_output_args() {
Ok(a) => a,
Err(e) => {
eprintln!("determinism-adapter-rust: {e}");
return ExitCode::FAILURE;
}
};
match run(&args.manifest, &args.output) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("determinism-adapter-rust: {e}");
ExitCode::FAILURE
}
}
}
fn run(manifest_path: &Path, output_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let manifest: Value = serde_json::from_str(&std::fs::read_to_string(manifest_path)?)?;
let fixtures = manifest
.get("fixtures")
.and_then(|v| v.as_array())
.ok_or("manifest.fixtures must be an array")?;
let mut out_fixtures = Map::new();
for fx in fixtures {
let id = fx.get("id").and_then(|v| v.as_str()).ok_or("fixture.id")?;
let inputs = fx.get("inputs").ok_or("fixture.inputs")?;
let canonical = inputs.get("canonical").ok_or("fixture.inputs.canonical")?;
let mut record = compute_payload(fx, canonical)?;
if let Some(variants) = inputs.get("variants").and_then(|v| v.as_object()) {
let mut vout = Map::new();
for (vname, vpayload) in variants {
vout.insert(vname.clone(), compute_payload(fx, vpayload)?);
}
if let Value::Object(ref mut obj) = record {
obj.insert("variants".to_string(), Value::Object(vout));
}
}
out_fixtures.insert(id.to_string(), record);
}
let report = json!({ "binding": "rust", "fixtures": Value::Object(out_fixtures) });
write_report(output_path, &report)
}
fn compute_payload(fx: &Value, payload: &Value) -> Result<Value, Box<dyn std::error::Error>> {
let primitive = fx
.get("primitive")
.and_then(|v| v.as_str())
.ok_or("fixture.primitive")?;
match primitive {
"skolem_distinct_rank" => {
let mode = fx
.get("skolem")
.and_then(|v| v.as_str())
.ok_or("fixture.skolem")?;
let edges = edges_from_payload(payload)?;
let keys: Vec<Key> = match mode {
"undirected" => edges
.into_iter()
.map(|mut e| {
let b = e.pop().ok_or("edge needs 2 components")?;
let a = e.pop().ok_or("edge needs 2 components")?;
Ok::<Key, Box<dyn std::error::Error>>(skolem_edge(a, b))
})
.collect::<Result<_, _>>()?,
"directed" => edges.into_iter().map(|e| skolem(e, false)).collect(),
other => return Err(format!("unknown skolem mode {other:?}").into()),
};
let index_set = distinct(&keys);
let serialized = canonical_index_set_json(&keys);
let ranking: Ranking = rank(&keys); let dense: Vec<i64> = index_set.iter().map(|t| ranking.id[t]).collect();
Ok(json!({
"index_set": index_set.iter().map(key_to_json).collect::<Vec<_>>(),
"serialized": serialized,
"dense_ids_canonical": dense,
}))
}
"group_by_sum" => {
let rows_json = payload
.get("rows")
.and_then(|v| v.as_array())
.ok_or("payload.rows")?;
let mut rows: Vec<(Key, Num)> = Vec::with_capacity(rows_json.len());
for row in rows_json {
let pair = row
.as_array()
.ok_or("group row must be a [key,value] array")?;
let key = Key::try_from_json(pair.first().ok_or("group row needs a key")?)?;
let val = pair.get(1).ok_or("group row needs a value")?;
let num = val
.as_i64()
.map(Num::Int)
.ok_or("group_by_sum value must be an integer")?;
rows.push((key, num));
}
let pairs = group_aggregate(&rows, SemiringOp::Sum);
let serialized = serialize_pairs(&pairs);
let index_set: Vec<Value> = pairs
.iter()
.map(|(k, v)| Value::Array(vec![key_to_json(k), num_to_json(v)]))
.collect();
let dense: Vec<i64> = (0..pairs.len() as i64).collect(); Ok(json!({
"index_set": index_set,
"serialized": serialized,
"dense_ids_canonical": dense,
}))
}
other => Err(format!("unknown primitive {other:?}").into()),
}
}
fn edges_from_payload(payload: &Value) -> Result<Vec<Vec<Key>>, Box<dyn std::error::Error>> {
if let Some(faces) = payload.get("faces").and_then(|v| v.as_array()) {
let mut edges = Vec::new();
for face in faces {
let verts = face.as_array().ok_or("face must be an array")?;
let n = verts.len();
for i in 0..n {
let a = Key::try_from_json(&verts[i])?;
let b = Key::try_from_json(&verts[(i + 1) % n])?;
edges.push(vec![a, b]);
}
}
Ok(edges)
} else if let Some(tuples) = payload.get("tuples").and_then(|v| v.as_array()) {
let mut edges = Vec::new();
for t in tuples {
let comps = t.as_array().ok_or("tuple must be an array")?;
let mut row = Vec::with_capacity(comps.len());
for c in comps {
row.push(Key::try_from_json(c)?);
}
edges.push(row);
}
Ok(edges)
} else {
Err("payload needs 'faces' or 'tuples'".into())
}
}
fn key_to_json(k: &Key) -> Value {
match k {
Key::Int(i) => json!(i),
Key::Str(s) => json!(s),
Key::Bool(b) => json!(b),
Key::Tuple(items) => Value::Array(items.iter().map(key_to_json).collect()),
}
}
fn num_to_json(n: &Num) -> Value {
match n {
Num::Int(i) => json!(i),
Num::Float(f) => json!(f),
}
}