Skip to main content

pnc_engine/
ops.rs

1use crate::error::PncError;
2use crate::value::Value;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5static RATIONAL_DIGIT_CAP: AtomicUsize = AtomicUsize::new(10_000);
6
7pub fn set_rational_digit_cap(cap: usize) {
8    RATIONAL_DIGIT_CAP.store(cap, Ordering::Relaxed);
9}
10
11fn rational_digit_cap() -> usize {
12    RATIONAL_DIGIT_CAP.load(Ordering::Relaxed)
13}
14use num_bigint::BigInt;
15use num_rational::BigRational;
16use num_traits::Signed;
17
18pub(crate) fn check_precision(v: &Value) -> Result<(), PncError> {
19    let cap = rational_digit_cap();
20    match v {
21        Value::Rational(r) => {
22            // Count digits of the magnitude only, so negative values are not
23            // penalised for their leading minus sign.
24            if r.numer().abs().to_string().len() > cap || r.denom().abs().to_string().len() > cap {
25                return Err(PncError::PrecisionBudgetExceeded);
26            }
27        }
28        Value::Unit { number, .. } => check_precision(number)?,
29        _ => {}
30    }
31    Ok(())
32}
33
34/// Add the top two stack values, pushing the result.
35pub fn add(stack: &mut Vec<Value>) -> Result<(), PncError> {
36    if stack.len() < 2 {
37        return Err(PncError::StackUnderflow);
38    }
39    let b = stack.pop().unwrap();
40    let a = stack.pop().unwrap();
41    let result = add_values(a, b);
42    check_precision(&result)?;
43    stack.push(result);
44    Ok(())
45}
46
47fn add_values(a: Value, b: Value) -> Value {
48    match (a, b) {
49        (Value::Int(x), Value::Int(y)) => Value::Int(x + y),
50        (Value::Int(x), Value::Rational(y)) => Value::Rational(BigRational::from_integer(x) + y),
51        (Value::Rational(x), Value::Int(y)) => Value::Rational(x + BigRational::from_integer(y)),
52        (Value::Rational(x), Value::Rational(y)) => Value::Rational(x + y),
53        (
54            Value::Unit {
55                number: an,
56                unit: au,
57            },
58            Value::Unit {
59                number: bn,
60                unit: bu,
61            },
62        ) if au == bu => Value::Unit {
63            number: Box::new(add_values(*an, *bn)),
64            unit: au,
65        },
66        (Value::Unit { number, .. }, other) => {
67            // strip unit and operate on numeric value
68            add_values(*number, other)
69        }
70        (other, Value::Unit { number, .. }) => add_values(other, *number),
71    }
72}
73
74pub fn sub(stack: &mut Vec<Value>) -> Result<(), PncError> {
75    if stack.len() < 2 {
76        return Err(PncError::StackUnderflow);
77    }
78    let b = stack.pop().unwrap();
79    let a = stack.pop().unwrap();
80    let result = sub_values(a, b);
81    check_precision(&result)?;
82    stack.push(result);
83    Ok(())
84}
85
86fn sub_values(a: Value, b: Value) -> Value {
87    match (a, b) {
88        (Value::Int(x), Value::Int(y)) => Value::Int(x - y),
89        (Value::Int(x), Value::Rational(y)) => Value::Rational(BigRational::from_integer(x) - y),
90        (Value::Rational(x), Value::Int(y)) => Value::Rational(x - BigRational::from_integer(y)),
91        (Value::Rational(x), Value::Rational(y)) => Value::Rational(x - y),
92        (
93            Value::Unit {
94                number: an,
95                unit: au,
96            },
97            Value::Unit {
98                number: bn,
99                unit: bu,
100            },
101        ) if au == bu => Value::Unit {
102            number: Box::new(sub_values(*an, *bn)),
103            unit: au,
104        },
105        (Value::Unit { number, .. }, other) => sub_values(*number, other),
106        (other, Value::Unit { number, .. }) => sub_values(other, *number),
107    }
108}
109
110pub fn mul(stack: &mut Vec<Value>) -> Result<(), PncError> {
111    if stack.len() < 2 {
112        return Err(PncError::StackUnderflow);
113    }
114    let b = stack.pop().unwrap();
115    let a = stack.pop().unwrap();
116    let result = mul_values(a, b);
117    check_precision(&result)?;
118    stack.push(result);
119    Ok(())
120}
121
122fn mul_values(a: Value, b: Value) -> Value {
123    match (a, b) {
124        (Value::Int(x), Value::Int(y)) => Value::Int(x * y),
125        (Value::Int(x), Value::Rational(y)) => Value::Rational(BigRational::from_integer(x) * y),
126        (Value::Rational(x), Value::Int(y)) => Value::Rational(x * BigRational::from_integer(y)),
127        (Value::Rational(x), Value::Rational(y)) => Value::Rational(x * y),
128        (
129            Value::Unit {
130                number: an,
131                unit: au,
132            },
133            Value::Unit {
134                number: bn,
135                unit: bu,
136            },
137        ) if au == bu => Value::Unit {
138            number: Box::new(mul_values(*an, *bn)),
139            unit: au,
140        },
141        (Value::Unit { number, .. }, other) => mul_values(*number, other),
142        (other, Value::Unit { number, .. }) => mul_values(other, *number),
143    }
144}
145
146fn unit_of(v: &Value) -> Option<crate::value::UnitId> {
147    match v {
148        Value::Unit { unit, .. } => Some(unit.clone()),
149        _ => None,
150    }
151}
152
153fn strip_unit(v: Value) -> Value {
154    match v {
155        Value::Unit { number, .. } => *number,
156        other => other,
157    }
158}
159
160pub fn div(stack: &mut Vec<Value>) -> Result<(), PncError> {
161    if stack.len() < 2 {
162        return Err(PncError::StackUnderflow);
163    }
164    let b = stack.pop().unwrap();
165    let a = stack.pop().unwrap();
166    let result_unit = if unit_of(&a).is_some() && unit_of(&a) == unit_of(&b) {
167        unit_of(&a)
168    } else {
169        None
170    };
171    let a = strip_unit(a);
172    let b = strip_unit(b);
173
174    let result = match (a, b) {
175        (Value::Int(x), Value::Int(y)) => {
176            if y == BigInt::from(0) {
177                return Err(PncError::DivisionByZero);
178            }
179            if x.clone() % y.clone() == BigInt::from(0) {
180                Value::Int(x / y)
181            } else {
182                Value::Rational(BigRational::new(x, y))
183            }
184        }
185        (Value::Int(x), Value::Rational(y)) => {
186            if y == BigRational::from_integer(BigInt::from(0)) {
187                return Err(PncError::DivisionByZero);
188            }
189            Value::Rational(BigRational::from_integer(x) / y)
190        }
191        (Value::Rational(x), Value::Int(y)) => {
192            if y == BigInt::from(0) {
193                return Err(PncError::DivisionByZero);
194            }
195            Value::Rational(x / BigRational::from_integer(y))
196        }
197        (Value::Rational(x), Value::Rational(y)) => {
198            if y == BigRational::from_integer(BigInt::from(0)) {
199                return Err(PncError::DivisionByZero);
200            }
201            Value::Rational(x / y)
202        }
203        _ => return Err(PncError::Parse("unsupported operand types".to_string())),
204    };
205
206    let result = match result_unit {
207        Some(u) => Value::Unit {
208            number: Box::new(result),
209            unit: u,
210        },
211        None => result,
212    };
213    check_precision(&result)?;
214    stack.push(result);
215    Ok(())
216}
217
218pub fn clear(stack: &mut Vec<Value>) {
219    stack.clear();
220}
221
222pub fn remove_at(stack: &mut Vec<Value>, index: usize) -> Result<(), PncError> {
223    if index >= stack.len() {
224        return Err(PncError::StackUnderflow);
225    }
226    stack.remove(index);
227    Ok(())
228}
229
230/// Swap the element at `index` with the top (last) element of `stack`.
231///
232/// The slice bound is intentional: swapping only needs indexing, so a slice
233/// works for `Vec`, arrays and sub-slices alike. `remove_at` keeps
234/// `&mut Vec<Value>` because removal shifts elements. Retained as `&mut [Value]`
235/// for consistency with the minimal requirement; change to `&mut Vec<Value>` only
236/// in a minor version bump if strict uniformity is required.
237pub fn swap_with_top(stack: &mut [Value], index: usize) -> Result<(), PncError> {
238    if stack.len() < 2 {
239        return Err(PncError::StackUnderflow);
240    }
241    if index >= stack.len() {
242        return Err(PncError::StackUnderflow);
243    }
244    let last = stack.len() - 1;
245    if index == last {
246        return Ok(());
247    }
248    stack.swap(index, last);
249    Ok(())
250}
251
252pub fn neg(stack: &mut Vec<Value>) -> Result<(), PncError> {
253    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
254    let result = neg_value(v);
255    check_precision(&result)?;
256    stack.push(result);
257    Ok(())
258}
259
260fn neg_value(v: Value) -> Value {
261    match v {
262        Value::Int(n) => Value::Int(-n),
263        Value::Rational(r) => Value::Rational(-r),
264        Value::Unit { number, unit } => Value::Unit {
265            number: Box::new(neg_value(*number)),
266            unit,
267        },
268    }
269}
270
271pub fn abs(stack: &mut Vec<Value>) -> Result<(), PncError> {
272    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
273    let result = abs_value(v);
274    check_precision(&result)?;
275    stack.push(result);
276    Ok(())
277}
278
279fn abs_value(v: Value) -> Value {
280    match v {
281        Value::Int(n) => Value::Int(n.abs()),
282        Value::Rational(r) => Value::Rational(r.abs()),
283        Value::Unit { number, unit } => Value::Unit {
284            number: Box::new(abs_value(*number)),
285            unit,
286        },
287    }
288}
289
290fn truncated_rem_rational(a: BigRational, b: BigRational) -> BigRational {
291    // Truncated-toward-zero remainder: q = trunc(a / b), r = a - q * b.
292    let q = (a.numer().clone() * b.denom().clone()) / (a.denom().clone() * b.numer().clone());
293    a - BigRational::from_integer(q) * b
294}
295
296pub fn rmod(stack: &mut Vec<Value>) -> Result<(), PncError> {
297    if stack.len() < 2 {
298        return Err(PncError::StackUnderflow);
299    }
300    // Check operand types before popping so a precision/type error does not
301    // silently consume the inputs.
302    {
303        let a = strip_unit(stack[stack.len() - 2].clone());
304        let b = strip_unit(stack[stack.len() - 1].clone());
305        match (&a, &b) {
306            (Value::Int(_), Value::Int(_))
307            | (Value::Int(_), Value::Rational(_))
308            | (Value::Rational(_), Value::Int(_))
309            | (Value::Rational(_), Value::Rational(_)) => {}
310            _ => return Err(PncError::Parse("unsupported operand types".to_string())),
311        }
312    }
313
314    let b = stack.pop().unwrap();
315    let a = stack.pop().unwrap();
316    let result_unit = if unit_of(&a).is_some() && unit_of(&a) == unit_of(&b) {
317        unit_of(&a)
318    } else {
319        None
320    };
321    let a = strip_unit(a);
322    let b = strip_unit(b);
323
324    let result = match (a, b) {
325        (Value::Int(x), Value::Int(y)) => {
326            if y == BigInt::from(0) {
327                return Err(PncError::DivisionByZero);
328            }
329            Value::Int(x % y)
330        }
331        (Value::Int(x), Value::Rational(y)) => {
332            if y == BigRational::from_integer(BigInt::from(0)) {
333                return Err(PncError::DivisionByZero);
334            }
335            Value::Rational(truncated_rem_rational(BigRational::from_integer(x), y))
336        }
337        (Value::Rational(x), Value::Int(y)) => {
338            if y == BigInt::from(0) {
339                return Err(PncError::DivisionByZero);
340            }
341            Value::Rational(truncated_rem_rational(x, BigRational::from_integer(y)))
342        }
343        (Value::Rational(x), Value::Rational(y)) => {
344            if y == BigRational::from_integer(BigInt::from(0)) {
345                return Err(PncError::DivisionByZero);
346            }
347            Value::Rational(truncated_rem_rational(x, y))
348        }
349        _ => return Err(PncError::Parse("unsupported operand types".to_string())),
350    };
351
352    let result = match result_unit {
353        Some(u) => Value::Unit {
354            number: Box::new(result),
355            unit: u,
356        },
357        None => result,
358    };
359    check_precision(&result)?;
360    stack.push(result);
361    Ok(())
362}
363
364pub fn over(stack: &mut Vec<Value>) -> Result<(), PncError> {
365    if stack.len() < 2 {
366        return Err(PncError::StackUnderflow);
367    }
368    let v = stack[stack.len() - 2].clone();
369    stack.push(v);
370    Ok(())
371}
372
373pub fn rot(stack: &mut Vec<Value>) -> Result<(), PncError> {
374    if stack.len() < 3 {
375        return Err(PncError::StackUnderflow);
376    }
377    let n = stack.len();
378    let v = stack.remove(n - 3);
379    stack.push(v);
380    Ok(())
381}
382
383pub fn to_rational(stack: &mut Vec<Value>) -> Result<(), PncError> {
384    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
385    let result = match v {
386        Value::Int(n) => Value::Rational(BigRational::from_integer(n)),
387        Value::Rational(r) => Value::Rational(r),
388        Value::Unit { number, unit } => {
389            let inner = to_rational_value(*number)?;
390            Value::Unit {
391                number: Box::new(Value::Rational(inner)),
392                unit,
393            }
394        }
395    };
396    check_precision(&result)?;
397    stack.push(result);
398    Ok(())
399}
400
401fn to_rational_value(v: Value) -> Result<BigRational, PncError> {
402    match v {
403        Value::Int(n) => Ok(BigRational::from_integer(n)),
404        Value::Rational(r) => Ok(r),
405        _ => Err(PncError::Parse("unsupported".to_string())),
406    }
407}
408
409pub fn to_int(stack: &mut Vec<Value>) -> Result<(), PncError> {
410    let v = stack.pop().ok_or(PncError::StackUnderflow)?;
411    let result = match v {
412        Value::Int(n) => Value::Int(n),
413        Value::Rational(r) => Value::Int(r.to_integer()),
414        Value::Unit { number, unit } => {
415            let n = match *number {
416                Value::Int(n) => n,
417                Value::Rational(r) => r.to_integer(),
418                _ => return Err(PncError::Parse("unsupported".to_string())),
419            };
420            Value::Unit {
421                number: Box::new(Value::Int(n)),
422                unit,
423            }
424        }
425    };
426    check_precision(&result)?;
427    stack.push(result);
428    Ok(())
429}