1use ocas_atom::{Atom, AtomArena, Symbol, normalize::normalize};
8use ocas_calc::{diff, integrate, integrate_heuristic, substitute, taylor};
9use ocas_core::arena::Arena;
10use ocas_parse::parse;
11use ocas_rewrite::rules::default_rules;
12use ocas_rewrite::simplify::simplify;
13use pyo3::exceptions::PyValueError;
14use pyo3::prelude::*;
15
16struct ExprInner {
19 arena_ptr: *mut Arena,
20 ctx_ptr: *mut AtomArena<'static>,
21 atom: Atom<'static>,
22}
23
24unsafe impl Send for ExprInner {}
27unsafe impl Sync for ExprInner {}
33
34impl Drop for ExprInner {
35 fn drop(&mut self) {
36 unsafe {
39 let _ = Box::from_raw(self.ctx_ptr);
40 let _ = Box::from_raw(self.arena_ptr);
41 }
42 }
43}
44
45struct ArenaGuard {
48 arena_ptr: *mut Arena,
49 ctx_ptr: *mut AtomArena<'static>,
50 armed: bool,
51}
52
53impl ArenaGuard {
54 fn new(arena_ptr: *mut Arena, ctx_ptr: *mut AtomArena<'static>) -> Self {
55 ArenaGuard {
56 arena_ptr,
57 ctx_ptr,
58 armed: true,
59 }
60 }
61
62 fn disarm(&mut self) {
63 self.armed = false;
64 }
65}
66
67impl Drop for ArenaGuard {
68 fn drop(&mut self) {
69 if self.armed {
70 unsafe {
72 let _ = Box::from_raw(self.ctx_ptr);
73 let _ = Box::from_raw(self.arena_ptr);
74 }
75 }
76 }
77}
78
79impl ExprInner {
80 fn ctx(&self) -> &'static AtomArena<'static> {
82 unsafe { &*self.ctx_ptr }
84 }
85
86 fn new_pair() -> (*mut Arena, *mut AtomArena<'static>) {
88 let arena_box: Box<Arena> = Box::new(Arena::new());
89 let arena_ptr = Box::into_raw(arena_box);
90 let arena_ref: &'static Arena = unsafe { &*arena_ptr };
92 let ctx = AtomArena::new(arena_ref);
93 let ctx_ptr = Box::into_raw(Box::new(ctx));
94 (arena_ptr, ctx_ptr)
95 }
96
97 fn build<F>(f: F) -> PyResult<Box<Self>>
99 where
100 F: FnOnce(&'static AtomArena<'static>) -> Result<Atom<'static>, String>,
101 {
102 let (arena_ptr, ctx_ptr) = Self::new_pair();
103 let mut guard = ArenaGuard::new(arena_ptr, ctx_ptr);
104 let ctx = unsafe { &*ctx_ptr };
105 let atom = f(ctx).map_err(PyValueError::new_err)?;
108 let normalized = normalize(ctx, atom);
109 guard.disarm();
110 Ok(Box::new(ExprInner {
111 arena_ptr,
112 ctx_ptr,
113 atom: normalized,
114 }))
115 }
116
117 fn from_str(input: &str) -> PyResult<Box<Self>> {
119 Self::build(|ctx| match parse(ctx, input) {
120 Ok(a) => Ok(a),
121 Err(e) => Err(format!("parse error: {e}")),
122 })
123 }
124
125 fn from_string_src(src: String) -> PyResult<Box<Self>> {
127 Self::build(|ctx| match parse(ctx, &src) {
128 Ok(a) => Ok(a),
129 Err(e) => Err(format!("parse error: {e}")),
130 })
131 }
132}
133
134#[pyclass(name = "Expression")]
144pub struct Expression {
145 inner: Box<ExprInner>,
146}
147
148impl Expression {
149 pub(crate) fn ctx_ref(&self) -> &'static AtomArena<'static> {
151 self.inner.ctx()
152 }
153
154 pub(crate) fn atom(&self) -> Atom<'static> {
156 self.inner.atom
157 }
158}
159
160#[pymethods]
161impl Expression {
162 #[new]
164 fn new(input: &str) -> PyResult<Self> {
165 Ok(Expression {
166 inner: ExprInner::from_str(input)?,
167 })
168 }
169
170 fn __str__(&self) -> String {
171 self.inner.atom.to_string()
172 }
173
174 fn __repr__(&self) -> String {
175 format!("Expression({:?})", self.inner.atom.to_string())
176 }
177
178 fn __add__(&self, other: &Expression) -> PyResult<Expression> {
179 let left = self.inner.atom.to_string();
180 let right = other.inner.atom.to_string();
181 let combined = format!("({left}) + ({right})");
182 Ok(Expression {
183 inner: ExprInner::from_string_src(combined)?,
184 })
185 }
186
187 fn __sub__(&self, other: &Expression) -> PyResult<Expression> {
188 let left = self.inner.atom.to_string();
189 let right = other.inner.atom.to_string();
190 let combined = format!("({left}) + (-1)*({right})");
191 Ok(Expression {
192 inner: ExprInner::from_string_src(combined)?,
193 })
194 }
195
196 fn __mul__(&self, other: &Expression) -> PyResult<Expression> {
197 let left = self.inner.atom.to_string();
198 let right = other.inner.atom.to_string();
199 let combined = format!("({left})*({right})");
200 Ok(Expression {
201 inner: ExprInner::from_string_src(combined)?,
202 })
203 }
204
205 fn __pow__(&self, other: &Expression, _modulo: Option<&Expression>) -> PyResult<Expression> {
206 let left = self.inner.atom.to_string();
207 let right = other.inner.atom.to_string();
208 let combined = format!("({left})^({right})");
209 Ok(Expression {
210 inner: ExprInner::from_string_src(combined)?,
211 })
212 }
213
214 fn __neg__(&self) -> PyResult<Expression> {
215 let src = self.inner.atom.to_string();
216 Ok(Expression {
217 inner: ExprInner::from_string_src(format!("(-1)*({src})"))?,
218 })
219 }
220
221 fn __eq__(&self, other: &Expression) -> bool {
222 let a = normalize(self.inner.ctx(), self.inner.atom);
224 let b = normalize(other.inner.ctx(), other.inner.atom);
225 a.to_string() == b.to_string()
226 }
227
228 fn __hash__(&self) -> u64 {
229 use std::collections::hash_map::DefaultHasher;
230 use std::hash::{Hash, Hasher};
231 let mut h = DefaultHasher::new();
232 self.inner.atom.to_string().hash(&mut h);
233 h.finish()
234 }
235
236 fn clone(&self) -> PyResult<Expression> {
238 let src = self.inner.atom.to_string();
239 Ok(Expression {
240 inner: ExprInner::from_string_src(src)?,
241 })
242 }
243
244 fn simplify(&self) -> PyResult<Expression> {
246 let src = self.inner.atom.to_string();
247 ExprInner::build(|ctx| {
248 let a = parse(ctx, &src).map_err(|e| e.to_string())?;
249 let rules = default_rules(ctx, &());
250 Ok(simplify(ctx, a, &rules, 20))
251 })
252 .map(|inner| Expression { inner })
253 }
254
255 fn diff(&self, var: &str) -> PyResult<Expression> {
257 let src = self.inner.atom.to_string();
258 let var_sym = Symbol::new(var);
259 ExprInner::build(|ctx| match parse(ctx, &src) {
260 Ok(a) => Ok(diff(ctx, a, var_sym)),
261 Err(e) => Err(e.to_string()),
262 })
263 .map(|inner| Expression { inner })
264 }
265
266 fn integrate(&self, var: &str) -> PyResult<Expression> {
268 let src = self.inner.atom.to_string();
269 let var_sym = Symbol::new(var);
270 ExprInner::build(|ctx| match parse(ctx, &src) {
271 Ok(a) => Ok(integrate(ctx, a, var_sym)),
272 Err(e) => Err(e.to_string()),
273 })
274 .map(|inner| Expression { inner })
275 }
276
277 fn integrate_heuristic(&self, var: &str) -> PyResult<Expression> {
283 let src = self.inner.atom.to_string();
284 let var_sym = Symbol::new(var);
285 ExprInner::build(|ctx| match parse(ctx, &src) {
286 Ok(a) => Ok(integrate_heuristic(ctx, a, var_sym)),
287 Err(e) => Err(e.to_string()),
288 })
289 .map(|inner| Expression { inner })
290 }
291
292 fn taylor(&self, var: &str, point: &Expression, order: usize) -> PyResult<Expression> {
294 let expr_src = self.inner.atom.to_string();
295 let point_src = point.inner.atom.to_string();
296 let var_sym = Symbol::new(var);
297 ExprInner::build(|ctx| {
298 let e = parse(ctx, &expr_src).map_err(|e| e.to_string())?;
299 let p = parse(ctx, &point_src).map_err(|e| e.to_string())?;
300 Ok(taylor(ctx, e, var_sym, p, order))
301 })
302 .map(|inner| Expression { inner })
303 }
304
305 fn substitute(&self, var: &str, replacement: &Expression) -> PyResult<Expression> {
307 let expr_src = self.inner.atom.to_string();
308 let repl_src = replacement.inner.atom.to_string();
309 let var_sym = Symbol::new(var);
310 ExprInner::build(|ctx| {
311 let e = parse(ctx, &expr_src).map_err(|e| e.to_string())?;
312 let r = parse(ctx, &repl_src).map_err(|e| e.to_string())?;
313 Ok(substitute(ctx, e, var_sym, r))
314 })
315 .map(|inner| Expression { inner })
316 }
317}