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