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> {
123 let tokens: Vec<&str> = line.split_whitespace().collect();
124 let mut i = 0;
125 while i < tokens.len() {
126 let tok = tokens[i];
127 if tok == "to" {
128 if i + 1 >= tokens.len() {
129 return Err(PncError::Parse("missing unit after 'to'".to_string()));
130 }
131 let target = parse_unit_symbol(tokens[i + 1])
132 .ok_or_else(|| PncError::Parse(tokens[i + 1].to_string()))?;
133 let top = stack.last().cloned().ok_or(PncError::StackUnderflow)?;
134 let converted =
135 unit::convert(&top, target).map_err(|_| PncError::InvalidUnitConversion)?;
136 stack.pop();
137 stack.push(converted);
138 i += 2;
139 continue;
140 }
141 if let Some(num) = parse_number(tok) {
142 if i + 1 < tokens.len()
144 && let Some(u) = parse_unit_symbol(tokens[i + 1])
145 {
146 stack.push(Value::Unit {
147 number: Box::new(num),
148 unit: u,
149 });
150 i += 2;
151 continue;
152 }
153 stack.push(num);
154 i += 1;
155 continue;
156 }
157 match tok {
158 "+" => ops::add(stack)?,
159 "-" => ops::sub(stack)?,
160 "*" => ops::mul(stack)?,
161 "/" => ops::div(stack)?,
162 "neg" => ops::neg(stack)?,
163 "abs" => ops::abs(stack)?,
164 "mod" => ops::rmod(stack)?,
165 "over" => ops::over(stack)?,
166 "rot" => ops::rot(stack)?,
167 "clear" => ops::clear(stack),
168 "to_rational" => ops::to_rational(stack)?,
169 "to_int" => ops::to_int(stack)?,
170 _ => {
171 if tok.chars().all(char::is_alphabetic) {
172 return Err(PncError::UnknownCommand(tok.to_string()));
173 }
174 return Err(PncError::Parse(tok.to_string()));
175 }
176 }
177 i += 1;
178 }
179 Ok(())
180}
181
182pub fn is_number_token(tok: &str) -> bool {
184 parse_number(tok).is_some()
185}