1use crate::error::PncError;
2use crate::ops;
3use crate::unit::{self, UnitId};
4use crate::value::Value;
5use num_bigint::BigInt;
6use num_traits::Num;
7
8fn parse_unit_symbol(s: &str) -> Option<UnitId> {
9 UnitId::from_symbol(s)
10}
11
12fn has_prefix(s: &str) -> bool {
13 let body = s.strip_prefix('-').unwrap_or(s);
14 body.starts_with("0b")
15 || body.starts_with("0B")
16 || body.starts_with("0o")
17 || body.starts_with("0O")
18 || body.starts_with("0x")
19 || body.starts_with("0X")
20}
21
22fn parse_integer(tok: &str) -> Option<Value> {
23 let (negative, body) = if let Some(rest) = tok.strip_prefix('-') {
24 (true, rest)
25 } else {
26 (false, tok)
27 };
28
29 if body.starts_with('+') {
31 return None;
32 }
33
34 let (base, digits) =
36 if let Some(rest) = body.strip_prefix("0b").or_else(|| body.strip_prefix("0B")) {
37 (2u32, rest)
38 } else if let Some(rest) = body.strip_prefix("0o").or_else(|| body.strip_prefix("0O")) {
39 (8u32, rest)
40 } else if let Some(rest) = body.strip_prefix("0x").or_else(|| body.strip_prefix("0X")) {
41 (16u32, rest)
42 } else {
43 (10u32, body)
44 };
45
46 if digits.is_empty() {
48 return None;
49 }
50
51 if digits.contains('_') || digits.starts_with('+') {
53 return None;
54 }
55
56 let n = if base == 10 {
57 digits.parse::<BigInt>().ok()?
58 } else {
59 if base == 16 {
61 BigInt::from_str_radix(&digits.to_lowercase(), base).ok()?
62 } else {
63 BigInt::from_str_radix(digits, base).ok()?
64 }
65 };
66
67 if negative {
68 Some(Value::Int(-n))
69 } else {
70 Some(Value::Int(n))
71 }
72}
73
74fn parse_number(tok: &str) -> Option<Value> {
75 if let Some((a, b)) = tok.split_once('/') {
77 let a = a.trim();
78 let b = b.trim();
79
80 if has_prefix(a) || has_prefix(b) {
82 return None;
83 }
84
85 let (neg_n, n_body) = if let Some(rest) = a.strip_prefix('-') {
87 (true, rest)
88 } else {
89 (false, a)
90 };
91
92 if n_body.starts_with('+') || n_body.contains('_') {
94 return None;
95 }
96
97 let n: BigInt = n_body.parse().ok()?;
98
99 if b.starts_with('-') || b.starts_with('+') || b.contains('_') {
101 return None;
102 }
103 let d: BigInt = b.parse().ok()?;
104 if d == BigInt::from(0) {
105 return None;
106 }
107 if d < BigInt::from(0) {
108 return None;
109 }
110
111 let n = if neg_n { -n } else { n };
112 Some(Value::Rational(num_rational::BigRational::new(n, d)))
113 } else {
114 parse_integer(tok)
115 }
116}
117
118pub fn eval_line(stack: &mut Vec<Value>, line: &str) -> Result<(), PncError> {
119 let tokens: Vec<&str> = line.split_whitespace().collect();
120 let mut i = 0;
121 while i < tokens.len() {
122 let tok = tokens[i];
123 if tok == "to" {
124 if i + 1 >= tokens.len() {
125 return Err(PncError::Parse("missing unit after 'to'".to_string()));
126 }
127 let target = parse_unit_symbol(tokens[i + 1])
128 .ok_or_else(|| PncError::Parse(tokens[i + 1].to_string()))?;
129 let top = stack.last().cloned().ok_or(PncError::StackUnderflow)?;
130 let converted =
131 unit::convert(&top, target).map_err(|_| PncError::InvalidUnitConversion)?;
132 stack.pop();
133 stack.push(converted);
134 i += 2;
135 continue;
136 }
137 if let Some(num) = parse_number(tok) {
138 if i + 1 < tokens.len()
140 && let Some(u) = parse_unit_symbol(tokens[i + 1])
141 {
142 stack.push(Value::Unit {
143 number: Box::new(num),
144 unit: u,
145 });
146 i += 2;
147 continue;
148 }
149 stack.push(num);
150 i += 1;
151 continue;
152 }
153 match tok {
154 "+" => ops::add(stack)?,
155 "-" => ops::sub(stack)?,
156 "*" => ops::mul(stack)?,
157 "/" => ops::div(stack)?,
158 "neg" => ops::neg(stack)?,
159 "abs" => ops::abs(stack)?,
160 "mod" => ops::rmod(stack)?,
161 "over" => ops::over(stack)?,
162 "rot" => ops::rot(stack)?,
163 "clear" => ops::clear(stack),
164 "to_rational" => ops::to_rational(stack)?,
165 "to_int" => ops::to_int(stack)?,
166 _ => {
167 if tok.chars().all(char::is_alphabetic) {
168 return Err(PncError::UnknownCommand(tok.to_string()));
169 }
170 return Err(PncError::Parse(tok.to_string()));
171 }
172 }
173 i += 1;
174 }
175 Ok(())
176}
177
178pub fn is_number_token(tok: &str) -> bool {
179 parse_number(tok).is_some()
180}