use std::collections::BTreeMap;
use crate::graph::{Graph, Node, NodeId};
#[derive(Default)]
pub(super) struct Symbolic {
nodes: Vec<Node>,
}
impl Symbolic {
pub(super) fn push(&mut self, node: Node) -> NodeId {
self.nodes.push(node);
self.nodes.len() - 1
}
pub(super) fn coordinate(&mut self, point: usize, axis: u8) -> NodeId {
self.push(Node::Coord { point, axis })
}
pub(super) fn negate(&mut self, x: NodeId) -> NodeId {
let zero = self.push(Node::Int(0));
self.push(Node::Sub(zero, x))
}
pub(super) fn finish(self, root: NodeId) -> Graph {
Graph {
nodes: self.nodes,
root,
}
}
}
fn coordinate(text: &str, array: &str) -> Option<(usize, u8)> {
let rest = text.strip_prefix(array)?.strip_prefix('[')?;
let (point, axis) = rest.strip_suffix(']')?.split_once("][")?;
Some((point.parse().ok()?, axis.parse().ok()?))
}
pub(super) fn interpret_statements(code: &str, array: &str) -> Graph {
let mut g = Symbolic::default();
let mut names: BTreeMap<String, NodeId> = BTreeMap::new();
let operand = |g: &mut Symbolic, names: &BTreeMap<String, NodeId>, text: &str| {
let text = text.strip_prefix('&').unwrap_or(text);
match coordinate(text, array) {
Some((point, axis)) => g.coordinate(point, axis),
None => names[text],
}
};
let mut last = None;
for line in code.lines() {
let Some(statement) = line.strip_prefix("let ") else {
continue;
};
let Some((name, value)) = statement.trim_end_matches(';').split_once(" = ") else {
continue;
};
if !(name == "det" || name.starts_with('t')) {
continue;
}
let tokens: Vec<&str> = value.split(' ').collect();
let id = match tokens.as_slice() {
[x] => {
let x = x.strip_prefix('-').expect("a negation");
let x = operand(&mut g, &names, x);
g.negate(x)
}
[x, op, y] => {
let (x, y) = (operand(&mut g, &names, x), operand(&mut g, &names, y));
g.push(match *op {
"+" => Node::Add(x, y),
"-" => Node::Sub(x, y),
"*" => Node::Mul(x, y),
_ => panic!("{line}"),
})
}
_ => panic!("{line}"),
};
names.insert(name.to_string(), id);
last = Some(name.to_string());
}
assert_eq!(last.as_deref(), Some("det"));
let root = names["det"];
g.finish(root)
}
pub(super) fn function<'a>(code: &'a str, name: &str) -> &'a str {
let start = code
.find(&format!("pub fn {name}("))
.unwrap_or_else(|| panic!("no function {name}"));
let rest = &code[start..];
&rest[..rest.find("\n}\n\n").expect("the end of the function")]
}
fn arguments<'a>(line: &'a str, prefix: &str) -> Option<Vec<&'a str>> {
let inner = line.strip_prefix(prefix)?.strip_suffix(");")?;
Some(inner.split(", ").collect())
}
fn numbered(text: &str, letter: char) -> Option<usize> {
text.strip_prefix(letter)?.parse().ok()
}
pub(super) fn interpret_stage(code: &str) -> Graph {
let mut g = Symbolic::default();
let mut scalars: BTreeMap<usize, NodeId> = BTreeMap::new();
let mut expansions: BTreeMap<usize, NodeId> = BTreeMap::new();
let scalar = |g: &mut Symbolic, scalars: &BTreeMap<usize, NodeId>, text: &str| {
if let Some((point, axis)) = coordinate(text, "p") {
g.coordinate(point, axis)
} else if let Some(n) = numbered(text, 'v') {
scalars[&n]
} else {
let value: f64 = text.parse().unwrap_or_else(|_| panic!("{text}"));
assert_eq!(value.fract(), 0.0, "{text}");
g.push(Node::Int(value as i64))
}
};
let slice = |expansions: &BTreeMap<usize, NodeId>, text: &str| {
let (e, l) = text
.strip_prefix("&e")
.and_then(|t| t.strip_suffix(']'))
.and_then(|t| t.split_once("[..l"))
.unwrap_or_else(|| panic!("not a slice: {text}"));
assert_eq!(e, l, "{text}");
expansions[&e.parse::<usize>().unwrap()]
};
let output = |text: &str| -> usize {
numbered(text.strip_prefix("&mut ").expect("an output"), 'e').expect("an expansion")
};
let mut root = None;
for line in code.lines().map(str::trim) {
if let Some((n, call)) = line
.strip_prefix("let (v")
.and_then(|t| t.split_once(") = "))
{
let (n, r) = n.split_once(", r").unwrap_or_else(|| panic!("{line}"));
assert_eq!(n, r, "{line}");
let a = arguments(call, "eft::two_diff(").unwrap_or_else(|| panic!("{line}"));
let (x, y) = (
scalar(&mut g, &scalars, a[0]),
scalar(&mut g, &scalars, a[1]),
);
scalars.insert(n.parse().unwrap(), g.push(Node::Sub(x, y)));
continue;
}
if let Some(rest) = line.strip_prefix("let v") {
let (n, value) = rest.split_once(" = -").unwrap_or_else(|| panic!("{line}"));
let x = scalar(&mut g, &scalars, value.trim_end_matches(';'));
scalars.insert(n.parse().unwrap(), g.negate(x));
continue;
}
if let Some(rest) = line.strip_prefix("let e") {
if let Some(n) = rest.strip_suffix(": [f64; 0] = [];") {
expansions.insert(n.parse().unwrap(), g.push(Node::Int(0)));
}
continue;
}
if let Some((target, call)) = line.strip_prefix("let l").and_then(|t| t.split_once(" = ")) {
let n: usize = target.parse().unwrap_or_else(|_| panic!("{line}"));
let call_of = |name: &str| arguments(call, name);
let value = if let Some(a) = call_of("expansion::from_f64(") {
assert_eq!(output(a[1]), n, "{line}");
scalar(&mut g, &scalars, a[0])
} else if let Some(a) = call_of("expansion::from_two_diff(") {
assert_eq!(output(a[2]), n, "{line}");
let (x, y) = (
scalar(&mut g, &scalars, a[0]),
scalar(&mut g, &scalars, a[1]),
);
g.push(Node::Sub(x, y))
} else if let Some(a) = call_of("expansion::from_two_product(") {
assert_eq!(output(a[2]), n, "{line}");
let (x, y) = (
scalar(&mut g, &scalars, a[0]),
scalar(&mut g, &scalars, a[1]),
);
g.push(Node::Mul(x, y))
} else if let Some(a) = call_of("expansion::scale(") {
assert_eq!(output(a[2]), n, "{line}");
let (x, y) = (slice(&expansions, a[0]), scalar(&mut g, &scalars, a[1]));
g.push(Node::Mul(x, y))
} else if let Some(a) = call_of("product(") {
assert_eq!(a[2..4], ["&mut scaled", "&mut spare"], "{line}");
assert_eq!(output(a[4]), n, "{line}");
let (x, y) = (slice(&expansions, a[0]), slice(&expansions, a[1]));
g.push(Node::Mul(x, y))
} else if let Some(a) = call_of("expansion::sum(") {
assert_eq!(output(a[2]), n, "{line}");
let (x, y) = (slice(&expansions, a[0]), slice(&expansions, a[1]));
g.push(Node::Add(x, y))
} else if let Some(a) = call_of("expansion::diff(") {
assert_eq!(output(a[2]), n, "{line}");
let (x, y) = (slice(&expansions, a[0]), slice(&expansions, a[1]));
g.push(Node::Sub(x, y))
} else if call == "0;" {
assert!(expansions.contains_key(&n), "{line}");
continue;
} else if call.strip_prefix('l').is_some() {
assert!(expansions.contains_key(&n), "{line}");
continue;
} else {
panic!("unknown call: {line}");
};
assert!(expansions.insert(n, value).is_none(), "e{n} written twice");
continue;
}
if let Some((target, source)) = line
.strip_suffix(");")
.and_then(|t| t.split_once(".copy_from_slice("))
{
let (n, length) = target
.strip_prefix('e')
.and_then(|t| t.strip_suffix(']'))
.and_then(|t| t.split_once("[..l"))
.unwrap_or_else(|| panic!("{line}"));
assert_eq!(format!("&e{length}[..l{length}]"), source, "{line}");
let copy = expansions[&length.parse::<usize>().unwrap()];
assert!(
expansions.insert(n.parse().unwrap(), copy).is_none(),
"{line}"
);
continue;
}
if let Some(target) = line
.strip_prefix("expansion::negate(&mut e")
.and_then(|t| t.strip_suffix(");"))
{
let (n, _) = target
.split_once("[..l")
.unwrap_or_else(|| panic!("{line}"));
let n: usize = n.parse().unwrap();
let negated = g.negate(expansions[&n]);
expansions.insert(n, negated);
continue;
}
if let Some(text) = line
.strip_prefix("Some(expansion::sign(")
.and_then(|t| t.strip_suffix("))"))
{
root = Some(slice(&expansions, text));
}
}
g.finish(root.expect("the sign of an expansion is returned"))
}