Skip to main content

step_p21/parser/
token.rs

1//! Parser for tokens defined in the table 2 of ISO-10303-21
2
3use crate::{
4    ast::*,
5    parser::{basic::*, combinator::*},
6};
7use nom::{
8    Parser,
9    branch::alt,
10    bytes::complete::tag,
11    character::complete::{
12        char, digit0, digit1, multispace0, none_of, satisfy,
13    },
14    combinator::{map, opt},
15    multi::{many0, many1},
16};
17
18/// sign = `+` | `-` .
19pub fn sign(input: &str) -> ParseResult<'_, char> {
20    alt((char('+'), char('-'))).parse(input)
21}
22
23/// integer = \[ [sign] \] [digit] { [digit] } .
24pub fn integer(input: &str) -> ParseResult<'_, i64> {
25    tuple((opt(sign), multispace0, digit1))
26        .map(|(sign, _space, numbers)| {
27            let num: i64 =
28                numbers.parse().expect("Failed to parse into integer");
29            match sign {
30                Some('-') => -num,
31                _ => num,
32            }
33        })
34        .parse(input)
35}
36
37/// `E` \[ [sign] \] [digit] { [digit] } .
38fn exponent(input: &str) -> ParseResult<'_, i64> {
39    tuple((char('E'), multispace0, opt(sign), multispace0, digit1))
40        .map(|(_e, _sp1, sign, _sp2, digit)| {
41            let num: i64 =
42                digit.parse().expect("Failed to parse integer in exponent");
43            match sign {
44                Some('-') => -num,
45                _ => num,
46            }
47        })
48        .parse(input)
49}
50
51/// real = \[ [sign] \] [digit] { [digit] } `.` { [digit] } \[ `E` \[ [sign] \]
52/// [digit] { [digit] } \] .
53pub fn real(input: &str) -> ParseResult<'_, f64> {
54    tuple((
55        opt(sign),
56        multispace0,
57        digit1,
58        char('.'),
59        digit0,
60        opt(exponent),
61    ))
62    .map(|(sign, _space, integral, _point, fractional, exp)| {
63        let num: f64 =
64            format!("{}.{}e{}", integral, fractional, exp.unwrap_or(0))
65                .parse()
66                .expect("Failed to parse Float");
67        match sign {
68            Some('-') => -num,
69            _ => num,
70        }
71    })
72    .parse(input)
73}
74
75/// string = `'` { [special] | [digit] | [space] | [lower] | [upper] |
76/// high_codepoint | [apostrophe] [apostrophe] | [reverse_solidus]
77/// [reverse_solidus] | control_directive } `'` .
78pub fn string(input: &str) -> ParseResult<'_, String> {
79    let escaped_char = map(tag("''"), |_| '\''); // Parse '' as a single '
80    let normal_char = none_of("'"); // Parse any character except '
81
82    let string_content = many0(escaped_char.or(normal_char.map(|c| c)));
83
84    tuple((char('\''), string_content, char('\'')))
85        .map(|(_start, s, _end)| s.iter().collect())
86        .parse(input)
87}
88
89/// resource = `<` UNIVERSAL_RESOURCE_IDENTIFIER `>` .
90///
91/// Parse as string, without validating as URI
92pub fn resource(input: &str) -> ParseResult<'_, URI> {
93    tuple((char('<'), many0(none_of(">")), char('>')))
94        .map(|(_start, s, _end)| URI(s.iter().collect()))
95        .parse(input)
96}
97
98/// enumeration = `.` [upper] { [upper] | [digit] } `.` .
99pub fn enumeration(input: &str) -> ParseResult<'_, String> {
100    tuple((char('.'), standard_keyword, char('.')))
101        .map(|(_head, name, _tail)| name)
102        .parse(input)
103}
104
105// Root error for u64 overflow
106//
107// FIXME Though it works, should we use `VerboseErrorKind::Context` for this
108// usage?
109fn u64_overflow(
110    input: &str,
111) -> nom::Err<nom_language::error::VerboseError<&str>> {
112    nom::Err::Failure(nom_language::error::VerboseError {
113        errors: vec![(
114            input,
115            nom_language::error::VerboseErrorKind::Context("u64-overflow"),
116        )],
117    })
118}
119
120/// entity_instance_name = `#` ( [digit] ) { [digit] } .
121///
122/// As discussed in ISO-10303-21 6.4.4.3 Entity instance names,
123///
124/// > NOTE 2 Leading zeros in entity instance names are ignored so "#001" is the
125/// > same identifier as "#1".
126///
127/// leading zeros are ignored, and convert into `u64` type.
128///
129/// Error
130/// -------
131/// - FIXME: If the input cannot be represented by `u64`, i.e. larger than
132///   [u64::MAX]
133pub fn entity_instance_name(input: &str) -> ParseResult<'_, u64> {
134    let (input, name) = tuple((char('#'), digit1))
135        .map(|(_sharp, name): (_, &str)| name.parse())
136        .parse(input)?;
137    if let Ok(name) = name {
138        Ok((input, name))
139    } else {
140        Err(u64_overflow(input))
141    }
142}
143
144/// value_instance_name = `@` ( [digit] ) { [digit] } .
145///
146/// Leading zeros are ignored like as [entity_instance_name].
147///
148/// Error
149/// -------
150/// - FIXME: If the input cannot be represented by `u64`, i.e. larger than
151///   [u64::MAX]
152pub fn value_instance_name(input: &str) -> ParseResult<'_, u64> {
153    let (input, name) = tuple((char('@'), digit1))
154        .map(|(_sharp, name): (_, &str)| name.parse())
155        .parse(input)?;
156    if let Ok(name) = name {
157        Ok((input, name))
158    } else {
159        Err(u64_overflow(input))
160    }
161}
162
163/// constant_entity_name = `#` ( [upper] ) { [upper] | [digit] } .
164pub fn constant_entity_name(input: &str) -> ParseResult<'_, String> {
165    tuple((char('#'), standard_keyword))
166        .map(|(_sharp, name)| name)
167        .parse(input)
168}
169
170/// constant_value_name = `@` ( [upper] ) { [upper] | [digit] } .
171pub fn constant_value_name(input: &str) -> ParseResult<'_, String> {
172    tuple((char('@'), standard_keyword))
173        .map(|(_sharp, name)| name)
174        .parse(input)
175}
176
177/// lhs_occurrence_name = ( [entity_instance_name] | [value_instance_name] ) .
178pub fn lhs_occurrence_name(input: &str) -> ParseResult<'_, Name> {
179    alt((
180        entity_instance_name.map(Name::Entity),
181        value_instance_name.map(Name::Value),
182    ))
183    .parse(input)
184}
185
186/// rhs_occurrence_name = ( [entity_instance_name] | [value_instance_name] |
187/// [constant_entity_name] | [constant_value_name]) .
188pub fn rhs_occurrence_name(input: &str) -> ParseResult<'_, Name> {
189    alt((
190        entity_instance_name.map(Name::Entity),
191        value_instance_name.map(Name::Value),
192        constant_entity_name.map(Name::ConstantEntity),
193        constant_value_name.map(Name::ConstantValue),
194    ))
195    .parse(input)
196}
197
198/// anchor_name = `<` URI_FRAGMENT_IDENTIFIER `>` .
199///
200/// Parse as string, without validating as URI fragment identifier
201pub fn anchor_name(input: &str) -> ParseResult<'_, String> {
202    tuple((char('<'), many0(none_of(">")), char('>')))
203        .map(|(_start, s, _end)| s.iter().collect())
204        .parse(input)
205}
206
207/// keyword = [user_defined_keyword] | [standard_keyword] .
208pub fn keyword(input: &str) -> ParseResult<'_, String> {
209    alt((user_defined_keyword, standard_keyword)).parse(input)
210}
211
212/// standard_keyword = [upper] { [upper] | [digit] } .
213pub fn standard_keyword(input: &str) -> ParseResult<'_, String> {
214    tuple((upper, many0(alt((upper, digit)))))
215        .map(|(first, tail)| {
216            let head = &[first];
217            head.iter().chain(tail.iter()).collect()
218        })
219        .parse(input)
220}
221
222/// user_defined_keyword = `!` [upper] { [upper] | [digit] } .
223pub fn user_defined_keyword(input: &str) -> ParseResult<'_, String> {
224    tuple((char('!'), standard_keyword))
225        .map(|(_e, name)| name)
226        .parse(input)
227}
228
229/// tag_name = ( [upper] | [lower] ) { [upper] | [lower] | [digit] } .
230pub fn tag_name(input: &str) -> ParseResult<'_, String> {
231    tuple((alt((upper, lower)), many0(alt((upper, lower, digit)))))
232        .map(|(first, tail)| {
233            let head = &[first];
234            head.iter().chain(tail.iter()).collect()
235        })
236        .parse(input)
237}
238
239/// signature_content = BASE64 .
240pub fn signature_content(input: &str) -> ParseResult<'_, String> {
241    let base_char = satisfy(
242        |c| matches!(c, '0'..='9' | 'a'..='z' | 'A'..='Z' | '+' | '/' | '='),
243    );
244    many1(base_char)
245        .map(|chars| chars.iter().collect())
246        .parse(input)
247}
248
249#[cfg(test)]
250mod tests {
251    use nom::Finish;
252
253    #[test]
254    fn real() {
255        let (res, s) = super::real("1.23").finish().unwrap();
256        assert_eq!(res, "");
257        assert_eq!(s, 1.23);
258
259        let (res, s) = super::real("1.23E4").finish().unwrap();
260        assert_eq!(res, "");
261        assert_eq!(s, 1.23e4);
262
263        let (res, s) = super::real("1.23E-4").finish().unwrap();
264        assert_eq!(res, "");
265        assert_eq!(s, 1.23e-4);
266
267        let (res, s) = super::real("-1.23E4").finish().unwrap();
268        assert_eq!(res, "");
269        assert_eq!(s, -1.23e4);
270
271        let (res, s) = super::real("-1.23E-4").finish().unwrap();
272        assert_eq!(res, "");
273        assert_eq!(s, -1.23e-4);
274
275        assert!(super::real("123").finish().is_err());
276    }
277
278    #[test]
279    fn string() {
280        let (res, s) = super::string("'vim'").finish().unwrap();
281        assert_eq!(res, "");
282        assert_eq!(s, "vim");
283    }
284
285    #[test]
286    fn escaped_string() {
287        let (res, s) = super::string("'vim''s'").finish().unwrap();
288        assert_eq!(res, "");
289        assert_eq!(s, "vim's");
290    }
291
292    #[test]
293    fn instance_name() {
294        let (res, s) = super::entity_instance_name(
295            "#18446744073709551615", /* u64::MAX */
296        )
297        .finish()
298        .unwrap();
299        assert_eq!(res, "");
300        assert_eq!(s, u64::MAX);
301
302        let (res, s) = super::value_instance_name(
303            "@18446744073709551615", /* u64::MAX */
304        )
305        .finish()
306        .unwrap();
307        assert_eq!(res, "");
308        assert_eq!(s, u64::MAX);
309
310        // u64 overflow
311        assert!(
312            super::entity_instance_name(
313                "#18446744073709551616" /* u64::MAX + 1 */
314            )
315            .finish()
316            .is_err()
317        );
318        assert!(
319            super::value_instance_name(
320                "@18446744073709551616" /* u64::MAX + 1 */
321            )
322            .finish()
323            .is_err()
324        );
325
326        // zeros should be ignored
327        let (res, s) = super::entity_instance_name("#001").finish().unwrap();
328        assert_eq!(res, "");
329        assert_eq!(s, 1);
330        let (res, s) = super::value_instance_name("@001").finish().unwrap();
331        assert_eq!(res, "");
332        assert_eq!(s, 1);
333    }
334}