1use alloc::string::String;
2use core::ops::Range;
3use core::str::{CharIndices, FromStr};
4
5use num::{One, Zero};
6
7use crate::err::TermFromStringError;
8
9#[derive(Debug, Clone, PartialEq)]
10pub enum Degree {
12 NegInf,
14 Num(usize),
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum Term<N> {
21 ZeroTerm,
23 Term(N, usize),
25}
26
27impl<N: Zero> Term<N> {
28 pub fn new(coeff: N, deg: usize) -> Term<N> {
29 if coeff.is_zero() {
30 Term::ZeroTerm
31 } else {
32 Term::Term(coeff, deg)
33 }
34 }
35}
36
37impl<N> FromStr for Term<N>
38where
39 N: Zero + One + FromStr + Copy,
40{
41 type Err = TermFromStringError;
42
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 let mut num_vec = vec![];
45 let mut coeff = N::one();
46 let mut seen_x = false;
47 let mut seen_caret = false;
48 for c in s.chars() {
49 match c {
50 ' ' | ',' | '_' => {}
55 '+' | '-' | '.' | '0'..='9' => num_vec.push(c),
58 'x' => {
59 if seen_x {
60 return Err(TermFromStringError::MoreThanOneX);
61 }
62 if !num_vec.is_empty() {
63 if num_vec.len() == 1 && (num_vec[0] == '-' || num_vec[0] == '+') {
64 num_vec.push('1')
65 }
66 let str: String = num_vec.iter().collect();
67 num_vec.clear();
68 coeff = match N::from_str(&str) {
69 Ok(val) => val,
70 Err(_) => {
71 return Err(TermFromStringError::CoeffCouldNotBeParsed);
72 }
73 };
74 }
75 seen_x = true;
76 }
77 '^' => {
78 if seen_caret {
79 return Err(TermFromStringError::MultipleCarets);
80 }
81 if !seen_x {
82 return Err(TermFromStringError::CaretWithoutXInFront);
83 }
84 seen_caret = true;
85 }
86 _ => {
87 return Err(TermFromStringError::UnexpectedChar(c));
88 }
89 }
90 }
91 return if seen_x {
92 if num_vec.is_empty() {
93 if seen_caret {
94 return Err(TermFromStringError::CaretWithoutDegree);
95 }
96 return Ok(Term::new(coeff, 1));
97 }
98 let str: String = num_vec.iter().collect();
99 if let Ok(degree) = usize::from_str(&str) {
100 Ok(Term::new(coeff, degree))
101 } else {
102 Err(TermFromStringError::CoeffCouldNotBeParsed)
103 }
104 } else {
105 let str: String = num_vec.iter().collect();
106 return match N::from_str(&str) {
107 Ok(val) => Ok(Term::new(val, 0)),
108 Err(_) => {
109 return Err(TermFromStringError::CoeffCouldNotBeParsed);
110 }
111 };
112 };
113 }
114}
115
116pub(crate) struct TermTokenizer<'a> {
117 char_indices: CharIndices<'a>,
118 seen_start: bool,
119 start_index: usize,
120 len: usize,
121}
122
123impl<'a> TermTokenizer<'a> {
124 pub(crate) fn new(s: &'a str) -> Self {
125 TermTokenizer {
126 char_indices: s.char_indices(),
127 seen_start: false,
128 start_index: 0,
129 len: s.len(),
130 }
131 }
132}
133
134impl<'a> Iterator for TermTokenizer<'a> {
135 type Item = Range<usize>;
136
137 fn next(&mut self) -> Option<Self::Item> {
138 while let Some((index, c)) = self.char_indices.next() {
139 if c == '+' || c == '-' {
140 if self.seen_start {
141 let range = self.start_index..index;
142 self.start_index = index;
143 return Some(range);
144 } else {
145 self.seen_start = true;
146 }
147 } else if !self.seen_start && !c.is_whitespace() {
148 self.seen_start = true;
149 }
150 }
151
152 if self.start_index != self.len {
153 let range = self.start_index..self.len;
154 self.start_index = self.len;
155 Some(range)
156 } else {
157 None
158 }
159 }
160}
161
162#[cfg(test)]
163mod test {
164 use crate::Term;
165 use core::str::FromStr;
166
167 #[test]
168 fn test_to_str_easy() {
169 assert_eq!(Ok(Term::Term(5i32, 2)), Term::from_str("5x^2"));
170 }
171
172 #[test]
173 fn test_to_str_harder() {
174 assert_eq!(Ok(Term::Term(5i32, 2)), Term::from_str("+5 x ^ 2"));
175 }
176
177 #[test]
178 fn test_to_str_dangling_caret() {
179 assert!(Term::<i32>::from_str("+5 x ^ ").is_err());
180 }
181
182 #[test]
183 fn test_to_str_multiple_carets() {
184 assert!(Term::<i32>::from_str("5x^^2").is_err());
185 }
186
187 #[test]
188 fn test_to_str_no_degree() {
189 assert_eq!(Ok(Term::Term(5i32, 1)), Term::from_str("+5 x "));
190 }
191
192 #[test]
193 fn test_to_str_no_x() {
194 assert_eq!(Ok(Term::Term(5i32, 0)), Term::from_str("+5 "));
195 }
196
197 #[test]
198 fn test_to_str_no_x_and_caret() {
199 assert!(Term::<i32>::from_str("+5^ ").is_err());
200 }
201
202 #[test]
203 fn test_to_str_negative() {
204 assert_eq!(Ok(Term::Term(-1i32, 0)), Term::from_str("-1"));
205 }
206
207 #[test]
208 fn x_no_coeff() {
209 assert_eq!(Ok(Term::Term(1i32, 1)), Term::from_str("x"));
210 }
211
212 #[test]
213 fn neg_x_no_coeff_neg() {
214 assert_eq!(Ok(Term::Term(-1i32, 1)), Term::from_str("-x"));
215 }
216
217 #[test]
218 fn neg_x_no_coeff_pos() {
219 assert_eq!(Ok(Term::Term(1i32, 1)), Term::from_str("+x"));
220 }
221
222 #[test]
223 fn x_no_coeff_float() {
224 assert_eq!(Ok(Term::Term(1.0f32, 1)), Term::from_str("x"));
225 }
226
227 #[test]
228 fn neg_x_no_coeff_neg_float() {
229 assert_eq!(Ok(Term::Term(-1.0f32, 1)), Term::from_str("-x"));
230 }
231
232 #[test]
233 fn neg_x_no_coeff_pos_float() {
234 assert_eq!(Ok(Term::Term(1.0f32, 1)), Term::from_str("+x"));
235 }
236
237 #[test]
238 fn term_from_zero() {
239 assert_eq!(Ok(Term::ZeroTerm), Term::<i32>::from_str("000"));
240 }
241
242 #[test]
243 fn sign_only() {
244 assert!(Term::<i32>::from_str("+").is_err());
245 assert!(Term::<i32>::from_str("-").is_err());
246 }
247
248 #[test]
249 fn empty_str() {
250 assert!(Term::<i32>::from_str("").is_err());
251 assert!(Term::<i32>::from_str(" ").is_err());
252 }
253}