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 percent() -> Percent = pos:position!() inner:(quiet! { $("%") } / expected!("percent sign")) {
35 Percent {
36 pos,
37 }
38 };
39 rule quotation_mark() -> &'input str = quiet! { $("\"") } / expected!("quotation mark");
40 rule ascii_except_quote_or_newline() -> &'input str = quiet! { $(['\t' | ' '..='!'| '#'..='~']*) } / expected!("ASCII character except quote or newline");
41 pub rule string() -> &'input str = $(quotation_mark() ascii_except_quote_or_newline() ((quotation_mark() quotation_mark())+ ascii_except_quote_or_newline())* quotation_mark());
42 rule ascii_except_closing_parenthesis_or_newline() -> &'input str = quiet! { $(['\t' | ' '..='(' | '*'..='~']*) } / expected!("ASCII character except closing parenthesis or newline");
43 rule opening_parenthesis() -> &'input str = quiet! { $("(") } / expected!("opening parenthesis");
44 rule closing_parenthesis() -> &'input str = quiet! { $(")") } / expected!("closing parenthesis");
45
46 rule inline_comment_raw() -> &'input str = precedence! {
47 x:$(opening_parenthesis() inline_comment_raw() closing_parenthesis()) { x }
48 --
49 x:$(opening_parenthesis() ascii_except_closing_parenthesis_or_newline() closing_parenthesis()) { x }
50 };
51 pub rule inline_comment() -> InlineComment<'input> = pos:position!() inner:inline_comment_raw() {
52 InlineComment {
53 inner,
54 pos,
55 }
56 };
57 rule ascii_character_except_newline() -> &'input str = quiet!{ $(['\t' | ' '..='~']*) } / expected!("ASCII character");
58 rule semicolon() -> &'input str = quiet! { $(";") } / expected!("semicolon");
59 pub rule comment() -> Comment<'input> = pos:position!() inner:$(semicolon() ascii_character_except_newline()) {
60 Comment {
61 inner,
62 pos,
63 }
64 };
65 pub rule integer() -> &'input str = quiet! { $(['0'..='9']+) } / expected!("integer");
66 pub rule letter() -> &'input str = quiet! { $(['a'..='z' | 'A'..='Z']) } / expected!("letter");
67 pub rule letters() -> &'input str = quiet! { $(['a'..='z' | 'A'..='Z']+) } / expected!("letters");
68 pub rule whitespace() -> Whitespace<'input> = pos:position!() inner:(quiet! { $([' ' | '\t' ]+) } / expected!("whitespace")) {
69 Whitespace {
70 inner,
71 pos,
72 }
73 };
74
75 rule checksum() -> Checksum = left:position!() star:star() checksum:integer() right:position!() {?
76 Ok(Checksum {
77 inner: checksum.parse::<u8>().map_err(|e| "checksum is not an unsigned byte")?,
78 span: Span(left, right)
79 })
80 };
81
82 rule field() -> Field<'input>
83 = left:position!() letters:letters() neg:minus()? lhs:integer() dot:dot() rhs:integer()? right:position!() {?
84 let lhs_start = left + letters.len() + neg.as_ref().map(|_| 1).unwrap_or(0);
85 let lhs_end = lhs_start + lhs.len();
86 let rhs_start = lhs_end + 1;
87 let rhs_end = rhs_start + rhs.map(|x| x.len()).unwrap_or(0);
88 Ok(Field {
89 letters,
90 value: Value::Rational(lhs.parse::<Decimal>()
91 .map_err(decimal_err_into_str)
92 .and_then(|lhs| if let Some(rhs_str) = rhs {
93 rhs_str.parse::<i64>()
94 .map_err(|e| "fractional part does not fit in an i64")
95 .and_then(|rhs| if rhs_str.len() > 28 { Err("scale is higher than allowed maximum precision") } else { Ok(rhs)})
96 .map(|rhs| Decimal::new(rhs, rhs_str.len() as u32))
97 .map(|rhs| lhs + rhs)
98 .map(|value| if neg.is_some() { -value } else { value })
99 } else {
100 Ok(
101 if neg.is_some() { -lhs } else { lhs }
102 )
103 })?),
104 raw_value: if neg.is_some() { vec!["-", lhs, ".", rhs.unwrap_or("")] } else { vec![lhs, ".", rhs.unwrap_or("")] },
105 span: Span(left, right)
106 })
107 }
108 / left:position!() letters:letters() neg:minus()? dot:dot() rhs_str:integer() right:position!() {?
109 let rhs_start = left + letters.len() + neg.as_ref().map(|_| 1).unwrap_or(0) + 1;
110 let rhs_end = right;
111 Ok(Field {
112 letters,
113 value: Value::Rational(rhs_str.parse::<i64>()
114 .map_err(|e| "fractional part does not fit in an i64")
115 .and_then(|rhs| if rhs_str.len() > 28 { Err("scale is higher than allowed maximum precision") } else { Ok(rhs)})
116 .map(|rhs| Decimal::new(rhs, rhs_str.len() as u32))
117 .map(|rhs| if neg.is_some() { -rhs } else { rhs })?),
118 raw_value: if neg.is_some() { vec!["-", ".", rhs_str] } else { vec![".", rhs_str] },
119 span: Span(left, right)
120 })
121 }
122 / left:position!() letters:letters() value:integer() right:position!() {?
123 Ok(Field {
124 letters,
125 value: Value::Integer(value.parse::<usize>().map_err(|e| "integer does not fit in usize")?),
126 raw_value: vec![value],
127 span: Span(left, right)
128 })
129 }
130
131 / left:position!() letters:letters() minus:minus() value:integer() right:position!() {?
132 let value_start = left + letters.len() + 1;
133 let value_end = right;
134 Ok(Field {
135 letters,
136 value: Value::Rational(-value.parse::<Decimal>().map_err(decimal_err_into_str)?),
137 raw_value: vec!["-", value],
138 span: Span(left, right)
139 })
140 }
141 / left:position!() letters:letters() string:string() right:position!() {
142 Field {
143 letters,
144 value: Value::String(string),
145 raw_value: vec![string],
146 span: Span(left, right)
147 }
148 };
149
150 pub rule flag() -> Flag<'input> = left:position!() letter:letter() right:position!() {
151 Flag { letter, span: Span(left, right) }
152 };
153
154 rule line_component() -> LineComponent<'input>
155 = field:field() { LineComponent { field: Some(field), ..Default::default() } }
156 / flag:flag() { LineComponent { flag: Some(flag), ..Default::default() }}
157 / whitespace:whitespace() { LineComponent { whitespace: Some(whitespace), ..Default::default() } }
158 / inline_comment:inline_comment() { LineComponent { inline_comment: Some(inline_comment), ..Default::default() } };
159
160 pub rule line() -> Line<'input> =
161 left:position!()
162 line_components:line_component()*
164 checksum:checksum()?
165 comment:comment()?
166 right:position!() {
167 Line {
168 line_components,
169 checksum,
170 comment,
171 span: Span(left, right)
172 }
173 };
174
175 pub rule file_parser() -> File<'input>
177 = left:position!() start_percent:percent() lines:(a:line() b:newline() { (a, b) })* last_line:line() end_percent:percent() right:position!() {
178 File {
179 percents: vec![start_percent, end_percent],
180 lines,
181 last_line: if last_line.line_components.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() {
182 None
183 } else {
184 Some(last_line)
185 },
186 span: Span(left, right)
187 }
188 }
189 / left:position!() lines:(a:line() b:newline() { (a, b) })* last_line:line() right:position!() {
190 File {
191 percents: vec![],
192 lines,
193 last_line: if last_line.line_components.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() {
194 None
195 } else {
196 Some(last_line)
197 },
198 span: Span(left, right)
199 }
200 };
201
202 pub rule snippet_parser() -> Snippet<'input> = left:position!() lines:(a:line() b:newline() { (a, b) })* last_line:line() right:position!() {
204 Snippet {
205 lines,
206 last_line: if last_line.line_components.is_empty() && last_line.checksum.is_none() && last_line.comment.is_none() {
207 None
208 } else {
209 Some(last_line)
210 },
211 span: Span(left, right)
212 }
213 }
214 }
215}