1use rust_decimal::Error;
2
3fn decimal_err_into_str(err: Error) -> &'static str {
4 match err {
5 Error::ExceedsMaximumPossibleValue => {
6 "number is exceeds than the maximum that can be represented"
7 }
8 Error::LessThanMinimumPossibleValue => {
9 "number is less than the minimum that can be represented"
10 }
11 Error::ScaleExceedsMaximumPrecision(_) => {
12 "precision necessary to represent exceeds the maximum possible"
13 }
14 Error::ErrorString(_) => "cannot parse as decimal (unable to display root cause)",
15 Error::Underflow => "number contains more fractional digits than can be represented",
16 Error::ConversionTo(_) => "cannot convert to/from decimal type",
17 }
18}
19
20peg::parser! {
21 pub grammar g_code() for str {
22 use super::super::token::*;
23 use super::super::ast::*;
24 use rust_decimal::Decimal;
25
26 pub rule newline() -> Newline = pos:position!() inner:(quiet!{ $("\r\n" / "\r" / "\n") } / expected!("newline")) {
27 Newline {
28 pos
29 }
30 };
31 pub rule dot() -> &'input str = quiet! { $(".") } / expected!("decimal point");
32 pub rule star() -> &'input str = quiet!{ $("*") } / expected!("checksum asterisk");
33 pub rule minus() -> &'input str = quiet!{ $("-") } / expected!("minus sign");
34 pub rule plus() -> &'input str = quiet!{ $("+") } / expected!("plus sign");
35 pub rule percent() -> Percent = pos:position!() inner:(quiet! { $("%") } / expected!("percent sign")) {
36 Percent {
37 pos,
38 }
39 };
40 rule quotation_mark() -> &'input str = quiet! { $("\"") } / expected!("quotation mark");
41 rule ascii_except_quote_or_newline() -> &'input str = quiet! { $(['\t' | ' '..='!'| '#'..='~']*) } / expected!("ASCII character except quote or newline");
42 pub rule string() -> &'input str = $(quotation_mark() ascii_except_quote_or_newline() ((quotation_mark() quotation_mark())+ ascii_except_quote_or_newline())* quotation_mark());
43 rule ascii_except_closing_parenthesis_or_newline() -> &'input str = quiet! { $(['\t' | ' '..='(' | '*'..='~']*) } / expected!("ASCII character except closing parenthesis or newline");
44 rule opening_parenthesis() -> &'input str = quiet! { $("(") } / expected!("opening parenthesis");
45 rule closing_parenthesis() -> &'input str = quiet! { $(")") } / expected!("closing parenthesis");
46
47 rule inline_comment_raw() -> &'input str = precedence! {
48 x:$(opening_parenthesis() inline_comment_raw() closing_parenthesis()) { x }
49 --
50 x:$(opening_parenthesis() ascii_except_closing_parenthesis_or_newline() closing_parenthesis()) { x }
51 };
52 pub rule inline_comment() -> InlineComment<'input> = pos:position!() inner:inline_comment_raw() {
53 InlineComment {
54 inner,
55 pos,
56 }
57 };
58 rule ascii_character_except_newline() -> &'input str = quiet!{ $(['\t' | ' '..='~']*) } / expected!("ASCII character");
59 rule semicolon() -> &'input str = quiet! { $(";") } / expected!("semicolon");
60 pub rule comment() -> Comment<'input> = pos:position!() inner:$(semicolon() ascii_character_except_newline()) {
61 Comment {
62 inner,
63 pos,
64 }
65 };
66 pub rule integer() -> &'input str = quiet! { $(['0'..='9']+) } / expected!("integer");
67 pub rule letter() -> &'input str = quiet! { $(['a'..='z' | 'A'..='Z']) } / expected!("letter");
68 pub rule letters() -> &'input str = quiet! { $(['a'..='z' | 'A'..='Z']+ / "$") } / expected!("letters");
69 pub rule whitespace() -> Whitespace<'input> = pos:position!() inner:(quiet! { $([' ' | '\t' ]+) } / expected!("whitespace")) {
70 Whitespace {
71 inner,
72 pos,
73 }
74 };
75
76 rule checksum() -> Checksum = left:position!() star:star() checksum:integer() right:position!() {?
77 Ok(Checksum {
78 inner: checksum.parse::<u8>().map_err(|e| "checksum is not an unsigned byte")?,
79 span: Span(left, right)
80 })
81 };
82
83 rule field() -> Field<'input>
84 = left:position!() letters:letters() pos:plus()? neg:minus()? lhs:integer() dot:dot() rhs:integer()? right:position!() {?
85 let lhs_start = left + letters.len() + pos.as_ref().map(|_| 1).unwrap_or(0) + neg.as_ref().map(|_| 1).unwrap_or(0);
86 let lhs_end = lhs_start + lhs.len();
87 let rhs_start = lhs_end + 1;
88 let rhs_end = rhs_start + rhs.map(|x| x.len()).unwrap_or(0);
89 Ok(Field {
90 letters,
91 value: Value::Rational(lhs.parse::<Decimal>()
92 .map_err(decimal_err_into_str)
93 .and_then(|lhs| if let Some(rhs_str) = rhs {
94 rhs_str.parse::<i64>()
95 .map_err(|_e| "fractional part does not fit in an i64")
96 .and_then(|rhs| if rhs_str.len() > 28 { Err("scale is higher than allowed maximum precision") } else { Ok(rhs)})
97 .map(|rhs| Decimal::new(rhs, rhs_str.len() as u32))
98 .map(|rhs| lhs + rhs)
99 .map(|value| if neg.is_some() { -value } else { value })
100 } else {
101 Ok(
102 if neg.is_some() { -lhs } else { lhs }
103 )
104 })?),
105 raw_value: match (pos.is_some(), neg.is_some()) {
106 (false, true) => vec!["-", lhs, ".", rhs.unwrap_or("")],
107 (true, false) => vec!["+", lhs, ".", rhs.unwrap_or("")],
108 (false, false) => vec![lhs, ".", rhs.unwrap_or("")],
109 (true, true) => unreachable!(),
110 },
111 span: Span(left, right)
112 })
113 }
114 / left:position!() letters:letters() pos:plus()? neg:minus()? dot:dot() rhs_str:integer() right:position!() {?
115 let rhs_start = left + letters.len() + pos.as_ref().map(|_| 1).unwrap_or(0) + neg.as_ref().map(|_| 1).unwrap_or(0) + 1;
116 let rhs_end = right;
117 Ok(Field {
118 letters,
119 value: Value::Rational(rhs_str.parse::<i64>()
120 .map_err(|_e| "fractional part does not fit in an i64")
121 .and_then(|rhs| if rhs_str.len() > 28 { Err("scale is higher than allowed maximum precision") } else { Ok(rhs)})
122 .map(|rhs| Decimal::new(rhs, rhs_str.len() as u32))
123 .map(|rhs| if neg.is_some() { -rhs } else { rhs })?),
124 raw_value: match (pos.is_some(), neg.is_some()) {
125 (false, true) => vec!["-", ".", rhs_str],
126 (true, false) => vec!["+", ".", rhs_str],
127 (false, false) => vec![".", rhs_str],
128 (true, true) => unreachable!(),
129 },
130 span: Span(left, right)
131 })
132 }
133 / left:position!() letters:letters() pos:plus()? value:integer() right:position!() {?
134 Ok(Field {
135 letters,
136 value: Value::Integer(value.parse::<usize>().map_err(|_e| "integer does not fit in usize")?),
137 raw_value: if pos.is_some() { vec!["+", value] } else { vec![value] },
138 span: Span(left, right)
139 })
140 }
141
142 / left:position!() letters:letters() minus:minus() value:integer() right:position!() {?
143 let value_start = left + letters.len() + 1;
144 let value_end = right;
145 Ok(Field {
146 letters,
147 value: Value::Rational(-value.parse::<Decimal>().map_err(decimal_err_into_str)?),
148 raw_value: vec!["-", value],
149 span: Span(left, right)
150 })
151 }
152 / left:position!() letters:letters() string:string() right:position!() {
153 Field {
154 letters,
155 value: Value::String(string),
156 raw_value: vec![string],
157 span: Span(left, right)
158 }
159 };
160
161 pub rule flag() -> Flag<'input> = left:position!() letter:letter() right:position!() {
162 Flag { letter, span: Span(left, right) }
163 };
164
165 rule line_component() -> LineComponent<'input>
166 = field:field() { LineComponent { field: Some(field), ..Default::default() } }
167 / flag:flag() { LineComponent { flag: Some(flag), ..Default::default() }}
168 / whitespace:whitespace() { LineComponent { whitespace: Some(whitespace), ..Default::default() } }
169 / inline_comment:inline_comment() { LineComponent { inline_comment: Some(inline_comment), ..Default::default() } };
170
171 pub rule line() -> Line<'input> =
172 left:position!()
173 line_components:line_component()*
175 checksum:checksum()?
176 comment:comment()?
177 right:position!() {
178 Line {
179 line_components,
180 checksum,
181 comment,
182 span: Span(left, right)
183 }
184 };
185
186 pub rule file_parser() -> File<'input>
188 = left:position!() start_percent:percent() lines:(a:line() b:newline() { (a, b) })* last_line:line() end_percent:percent() right:position!() {
189 File {
190 percents: vec![start_percent, end_percent],
191 lines,
192 last_line: if last_line.line_components.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() {
193 None
194 } else {
195 Some(last_line)
196 },
197 span: Span(left, right)
198 }
199 }
200 / left:position!() lines:(a:line() b:newline() { (a, b) })* last_line:line() right:position!() {
201 File {
202 percents: vec![],
203 lines,
204 last_line: if last_line.line_components.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() {
205 None
206 } else {
207 Some(last_line)
208 },
209 span: Span(left, right)
210 }
211 };
212
213 pub rule snippet_parser() -> Snippet<'input> = left:position!() lines:(a:line() b:newline() { (a, b) })* last_line:line() right:position!() {
215 Snippet {
216 lines,
217 last_line: if last_line.line_components.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() {
218 None
219 } else {
220 Some(last_line)
221 },
222 span: Span(left, right)
223 }
224 }
225 }
226}