1use crate::error::Error;
5use crate::graph::{Graph, Node, NodeId};
6use crate::parse::Position;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum DiffNode {
11 Leaf {
14 axis: u8,
16 minuend: usize,
18 subtrahend: usize,
20 },
21 Neg(NodeId),
23 Add(NodeId, NodeId),
25 Sub(NodeId, NodeId),
27 Mul(NodeId, NodeId),
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct DiffGraph {
37 pub nodes: Vec<DiffNode>,
39 pub root: NodeId,
41 pub degrees: Vec<u32>,
45}
46
47#[derive(Clone, Copy)]
49enum Mapped {
50 Coord { point: usize, axis: u8 },
52 Literal(i64),
54 Node(NodeId),
56}
57
58pub fn difference_graph(graph: &Graph, file: &str, position: Position) -> Result<DiffGraph, Error> {
74 let fail = |message: String| Error {
75 file: file.to_string(),
76 line: position.line,
77 column: position.column,
78 message,
79 };
80 let mut out = DiffGraph {
81 nodes: Vec::new(),
82 root: 0,
83 degrees: Vec::new(),
84 };
85 let mut mapped: Vec<Mapped> = Vec::with_capacity(graph.nodes.len());
86 for node in &graph.nodes {
87 let value = match *node {
88 Node::Coord { point, axis } => Mapped::Coord { point, axis },
89 Node::Int(n) => Mapped::Literal(n),
90 Node::Sub(x, y) => match (mapped[x], mapped[y]) {
91 (Mapped::Coord { point: p, axis: i }, Mapped::Coord { point: q, axis: j }) => {
92 if i != j {
93 return Err(fail(format!(
94 "a difference of coordinates must use one axis, found {} and {}",
95 coordinate(p, i),
96 coordinate(q, j)
97 )));
98 }
99 if p == q {
100 return Err(fail(format!(
101 "a difference of {} with itself is always zero",
102 coordinate(p, i)
103 )));
104 }
105 Mapped::Node(push(
106 &mut out,
107 DiffNode::Leaf {
108 axis: i,
109 minuend: p,
110 subtrahend: q,
111 },
112 1,
113 ))
114 }
115 (Mapped::Literal(0), Mapped::Node(id)) => {
116 let degree = out.degrees[id];
117 Mapped::Node(push(&mut out, DiffNode::Neg(id), degree))
118 }
119 (Mapped::Literal(0), operand) => return Err(fail(operand_problem(operand))),
120 (x, y) => combine(&mut out, DiffNode::Sub, x, y, &fail)?,
121 },
122 Node::Add(x, y) => combine(&mut out, DiffNode::Add, mapped[x], mapped[y], &fail)?,
123 Node::Mul(x, y) => combine(&mut out, DiffNode::Mul, mapped[x], mapped[y], &fail)?,
124 };
125 mapped.push(value);
126 }
127 match mapped[graph.root] {
128 Mapped::Node(root) => {
129 out.root = root;
130 Ok(out)
131 }
132 other => Err(fail(operand_problem(other))),
133 }
134}
135
136fn combine(
138 out: &mut DiffGraph,
139 make: fn(NodeId, NodeId) -> DiffNode,
140 x: Mapped,
141 y: Mapped,
142 fail: &dyn Fn(String) -> Error,
143) -> Result<Mapped, Error> {
144 let (Mapped::Node(x), Mapped::Node(y)) = (x, y) else {
145 let bad = if matches!(x, Mapped::Node(_)) { y } else { x };
146 return Err(fail(operand_problem(bad)));
147 };
148 let (gx, gy) = (out.degrees[x], out.degrees[y]);
149 let node = make(x, y);
150 let degree = if matches!(node, DiffNode::Mul(..)) {
151 gx.checked_add(gy).ok_or_else(|| {
152 fail(format!(
153 "a product has a degree in the differences above {}",
154 u32::MAX
155 ))
156 })?
157 } else if gx == gy {
158 gx
159 } else {
160 return Err(fail(format!(
161 "a sum or difference combines terms of degree {gx} and {gy} in the differences"
162 )));
163 };
164 Ok(Mapped::Node(push(out, node, degree)))
165}
166
167fn push(out: &mut DiffGraph, node: DiffNode, degree: u32) -> NodeId {
168 out.nodes.push(node);
169 out.degrees.push(degree);
170 out.nodes.len() - 1
171}
172
173fn operand_problem(bad: Mapped) -> String {
174 match bad {
175 Mapped::Coord { point, axis } => format!(
176 "the coordinate {} is used outside a difference of coordinates",
177 coordinate(point, axis)
178 ),
179 Mapped::Literal(n) => format!(
180 "the integer literal {n} is not allowed in the main form, \
181 except as the zero of a negation"
182 ),
183 Mapped::Node(_) => unreachable!("only coordinates and literals are problems"),
184 }
185}
186
187fn coordinate(point: usize, axis: u8) -> String {
188 format!("`point {point}`.{}", ["x", "y", "z"][usize::from(axis)])
189}