xschem_parser/
parse.rs

1//! Parser combinator functions.
2use std::collections::HashMap;
3use std::hash::Hash;
4
5use nom::branch::alt;
6use nom::bytes::complete::{tag, take_while, take_while1};
7use nom::character::complete::{
8    char, multispace0, multispace1, none_of, one_of, space1, u64, usize,
9};
10use nom::combinator::{consumed, cut, eof, opt, value as nom_value};
11use nom::error::{ContextError, ErrorKind, ParseError, context};
12use nom::multi::{fold_many0, length_count};
13use nom::number::complete::recognize_float;
14use nom::sequence::{preceded, separated_pair, terminated};
15use nom::{AsChar, Compare, Err, Finish, IResult, Input, Offset, ParseTo, Parser};
16
17use crate::token::{
18    Arc, Component, Coordinate, Embedding, FiniteDouble, Flip, Line, Object, Polygon, Property,
19    Rectangle, Rotation, Schematic, Size, SpiceProperty, SymbolProperty, TedaXProperty, Text, Vec2,
20    VerilogProperty, Version, VhdlProperty, Wire,
21};
22
23/// Reserved escapable characters in property strings.
24pub const ESCAPED_CHARS: &str = r"\{}";
25/// Reserved escapable characters in attribute values.
26pub const ESCAPED_VALUE_CHARS: &str = r#"\""#;
27/// Escape character in property strings.
28pub const ESCAPE_CHAR: char = '\\';
29
30pub(crate) fn escaped0<'a, I, Error, F, G>(
31    mut normal: F,
32    control_char: char,
33    mut escapable: G,
34) -> impl FnMut(I) -> IResult<I, I, Error>
35where
36    I: Input + Offset + 'a,
37    <I as Input>::Item: AsChar,
38    F: Parser<I, Error = Error>,
39    G: Parser<I, Error = Error>,
40    Error: ParseError<I>,
41{
42    move |input: I| {
43        let mut i = input.clone();
44        let mut consumed_nothing = false;
45
46        while i.input_len() > 0 {
47            let current_len = i.input_len();
48
49            match (normal.parse(i.clone()), consumed_nothing) {
50                (Ok((i2, _)), false) => {
51                    if i2.input_len() == 0 {
52                        return Ok((input.take_from(input.input_len()), input));
53                    }
54                    if i2.input_len() == current_len {
55                        consumed_nothing = true;
56                    }
57                    i = i2;
58                }
59                (Ok(..), true) | (Err(Err::Error(_)), _) => {
60                    let next_char = i
61                        .iter_elements()
62                        .next()
63                        .ok_or_else(|| {
64                            Err::Error(Error::from_error_kind(i.clone(), ErrorKind::Escaped))
65                        })?
66                        .as_char();
67                    if next_char == control_char {
68                        let next = control_char.len_utf8();
69                        if next >= i.input_len() {
70                            return Err(Err::Error(Error::from_error_kind(
71                                input,
72                                ErrorKind::Escaped,
73                            )));
74                        }
75                        match escapable.parse(i.take_from(next)) {
76                            Ok((i2, _)) => {
77                                if i2.input_len() == 0 {
78                                    return Ok((input.take_from(input.input_len()), input));
79                                }
80                                consumed_nothing = false;
81                                i = i2;
82                            }
83                            Err(_) => {
84                                return Err(Err::Error(Error::from_error_kind(
85                                    i,
86                                    ErrorKind::Escaped,
87                                )));
88                            }
89                        }
90                    } else {
91                        let index = input.offset(&i);
92                        return Ok(input.take_split(index));
93                    }
94                }
95                (Err(e), _) => {
96                    return Err(e);
97                }
98            }
99        }
100
101        Ok((input.take_from(input.input_len()), input))
102    }
103}
104
105pub(crate) fn try_skip<'a, I, O, E, F>(
106    mut parser: F,
107) -> impl Parser<I, Output = Option<O>, Error = E>
108where
109    I: Input + 'a,
110    <I as Input>::Item: AsChar,
111    E: ParseError<I>,
112    F: Parser<I, Output = O, Error = (I, ErrorKind)> + 'a,
113{
114    move |input: I| match parser.parse(input) {
115        Ok((rest, kv)) => Ok((rest, Some(kv))),
116        Err(Err::Error((rest, _))) => Ok((rest, None)),
117        Err(Err::Failure((input, kind))) => Err(Err::Failure(E::from_error_kind(input, kind))),
118        Err(Err::Incomplete(e)) => Err(Err::Incomplete(e)),
119    }
120}
121
122fn is_key_char<C: AsChar>(c: C) -> bool {
123    c.is_alphanum() || c.as_char() == '_'
124}
125
126pub(crate) fn key<'a, I, E>(input: I) -> IResult<I, I, E>
127where
128    I: Offset + Input + 'a,
129    <I as Input>::Item: AsChar,
130    E: ParseError<I> + ContextError<I>,
131{
132    context("key", take_while1(is_key_char)).parse(input)
133}
134
135fn is_value_char<C: AsChar>(c: C) -> bool {
136    c.is_alphanum() || c.as_char().is_ascii_punctuation()
137}
138
139pub(crate) fn value<'a, I, E>(input: I) -> IResult<I, I, E>
140where
141    I: Offset + Input + for<'s> Compare<&'s str> + 'a,
142    <I as Input>::Item: AsChar,
143    E: ParseError<I> + ContextError<I>,
144{
145    context(
146        "value",
147        alt((
148            preceded(
149                char('"'),
150                cut(terminated(
151                    escaped0(
152                        none_of(ESCAPED_VALUE_CHARS),
153                        ESCAPE_CHAR,
154                        alt((tag(r#"\""#), tag(r"\"), tag(r"{"), tag(r"}"))),
155                    ),
156                    char('"'),
157                )),
158            ),
159            take_while1(is_value_char),
160        )),
161    )
162    .parse(input)
163}
164
165pub(crate) fn key_value<'a, I, E>(input: I) -> IResult<I, (I, I), E>
166where
167    I: Offset + Input + for<'s> Compare<&'s str> + 'a,
168    <I as Input>::Item: AsChar,
169    E: ParseError<I> + ContextError<I>,
170{
171    context("key_value", separated_pair(key, char('='), value)).parse(input)
172}
173
174pub(crate) fn attributes<'a, I, E>(mut input: I) -> IResult<I, HashMap<I, I>, E>
175where
176    I: Eq + Hash + Offset + Input + for<'s> Compare<&'s str> + 'a,
177    <I as Input>::Item: AsChar,
178    E: ParseError<I> + ContextError<I>,
179{
180    let mut attrs = HashMap::new();
181
182    while input.input_len() > 0 {
183        input = match preceded(take_while(|c| !is_key_char(c)), try_skip(key_value)).parse(input) {
184            Ok((rest, Some((k, v)))) => {
185                attrs.insert(k, v);
186                rest
187            }
188            Ok((rest, None)) => rest,
189            Err(e) => return Err(e),
190        };
191    }
192
193    Ok((input, attrs))
194}
195
196pub(crate) fn brace_enclosed<'a, I, O, P, E>(parser: P) -> impl Parser<I, Output = O, Error = E>
197where
198    I: Input + 'a,
199    <I as Input>::Item: AsChar,
200    E: ParseError<I>,
201    P: Parser<I, Output = O, Error = E>,
202{
203    preceded(char('{'), cut(terminated(parser, char('}'))))
204}
205
206pub(crate) fn property_string<'a, I, E>(input: I) -> IResult<I, I, E>
207where
208    I: Input + Offset + 'a,
209    <I as Input>::Item: AsChar,
210    E: ParseError<I> + ContextError<I>,
211{
212    escaped0(none_of(ESCAPED_CHARS), ESCAPE_CHAR, one_of(ESCAPED_CHARS)).parse(input)
213}
214
215pub(crate) fn property<'a, I, E>(input: I) -> IResult<I, Property<I>, E>
216where
217    I: Eq + Hash + Input + Offset + for<'s> nom::Compare<&'s str> + 'a,
218    <I as Input>::Item: AsChar,
219    E: ParseError<I> + ContextError<I>,
220{
221    brace_enclosed(context("property", property_string))
222        .and_then(consumed(attributes))
223        .map(|(prop, attrs)| Property { prop, attrs })
224        .parse(input)
225}
226
227pub(crate) fn text<'a, I, E>(input: I) -> IResult<I, I, E>
228where
229    I: Input + Offset + 'a,
230    <I as Input>::Item: AsChar,
231    E: ParseError<I> + ContextError<I>,
232{
233    brace_enclosed(context("text", property_string)).parse(input)
234}
235
236pub(crate) fn reference<'a, I, E>(input: I) -> IResult<I, I, E>
237where
238    I: Input + Offset + 'a,
239    <I as Input>::Item: AsChar,
240    E: ParseError<I> + ContextError<I>,
241{
242    brace_enclosed(context("reference", property_string)).parse(input)
243}
244
245pub(crate) fn object<'a, I, O, P, E>(
246    name: &'static str,
247    tag: char,
248    parser: P,
249) -> impl Parser<I, Output = O, Error = E>
250where
251    I: Input + 'a,
252    <I as Input>::Item: AsChar,
253    E: ParseError<I> + ContextError<I>,
254    P: Parser<I, Output = O, Error = E>,
255{
256    context(name, preceded(char(tag), cut(parser)))
257}
258
259pub(crate) fn layer<'a, I, E>(input: I) -> IResult<I, u64, E>
260where
261    I: Input + 'a,
262    <I as Input>::Item: AsChar,
263    E: ParseError<I>,
264{
265    u64(input)
266}
267
268pub(crate) fn finite_double<'a, I, E>(input: I) -> IResult<I, FiniteDouble, E>
269where
270    I: Input + Offset + ParseTo<f64> + 'a,
271    <I as Input>::Item: AsChar,
272    E: ParseError<I>,
273{
274    let (i, s) = recognize_float(input)?;
275    match s.parse_to() {
276        // Safe to unwrap here cause recognize_float should only recognize
277        // finite numbers.
278        Some(f) => Ok((i, f.try_into().unwrap())),
279        None => Err(Err::Error(E::from_error_kind(i, ErrorKind::Float))),
280    }
281}
282
283pub(crate) fn vec2<'a, I, E>(input: I) -> IResult<I, Vec2, E>
284where
285    I: Input + Offset + ParseTo<f64> + 'a,
286    <I as Input>::Item: AsChar,
287    E: ParseError<I>,
288{
289    Parser::into(separated_pair(finite_double, multispace1, finite_double)).parse(input)
290}
291
292pub(crate) fn coordinate<'a, I, E>(input: I) -> IResult<I, Coordinate, E>
293where
294    I: Input + Offset + ParseTo<f64> + 'a,
295    <I as Input>::Item: AsChar,
296    E: ParseError<I> + ContextError<I>,
297{
298    context("coordinate", vec2).parse(input)
299}
300
301pub(crate) fn size<'a, I, E>(input: I) -> IResult<I, Size, E>
302where
303    I: Input + Offset + ParseTo<f64> + 'a,
304    <I as Input>::Item: AsChar,
305    E: ParseError<I> + ContextError<I>,
306{
307    context("size", vec2).parse(input)
308}
309
310pub(crate) fn rotation<'a, I, E>(input: I) -> IResult<I, Rotation, E>
311where
312    I: Input + 'a,
313    <I as Input>::Item: AsChar,
314    E: ParseError<I> + ContextError<I>,
315{
316    context(
317        "rotation",
318        alt((
319            nom_value(Rotation::Zero, char('0')),
320            nom_value(Rotation::One, char('1')),
321            nom_value(Rotation::Two, char('2')),
322            nom_value(Rotation::Three, char('3')),
323        )),
324    )
325    .parse(input)
326}
327
328pub(crate) fn flip<'a, I, E>(input: I) -> IResult<I, Flip, E>
329where
330    I: Input + 'a,
331    <I as Input>::Item: AsChar,
332    E: ParseError<I> + ContextError<I>,
333{
334    context(
335        "flip",
336        alt((
337            nom_value(Flip::Unflipped, char('0')),
338            nom_value(Flip::Flipped, char('1')),
339        )),
340    )
341    .parse(input)
342}
343
344pub(crate) fn embedding<'a, I, E>(input: I) -> IResult<I, Embedding<I>, E>
345where
346    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> nom::Compare<&'s str> + 'a,
347    <I as Input>::Item: AsChar,
348    E: ParseError<I> + ContextError<I>,
349{
350    object(
351        "embedded symbol",
352        '[',
353        terminated(
354            preceded(multispace1, Parser::into(schematic)),
355            preceded(multispace1, char(']')),
356        ),
357    )
358    .parse(input)
359}
360
361pub(crate) fn version_object<'a, I, E>(input: I) -> IResult<I, Version<I>, E>
362where
363    I: Eq + Hash + Input + Offset + for<'s> Compare<&'s str> + 'a,
364    <I as Input>::Item: AsChar,
365    E: ParseError<I> + ContextError<I>,
366{
367    object("version", 'v', preceded(multispace1, property))
368        .map(Version)
369        .parse(input)
370}
371
372pub(crate) fn property_object<'a, I, E>(
373    tag: char,
374) -> impl Parser<I, Output = Property<I>, Error = E>
375where
376    I: Eq + Hash + Input + Offset + for<'s> Compare<&'s str> + 'a,
377    <I as Input>::Item: AsChar,
378    E: ParseError<I> + ContextError<I>,
379{
380    object("global property", tag, preceded(multispace1, property))
381}
382
383pub(crate) fn arc_object<'a, I, E>(input: I) -> IResult<I, Arc<I>, E>
384where
385    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
386    <I as Input>::Item: AsChar,
387    E: ParseError<I> + ContextError<I>,
388{
389    object(
390        "arc",
391        'A',
392        (
393            preceded(multispace1, layer),
394            preceded(multispace1, coordinate),
395            preceded(multispace1, finite_double),
396            preceded(multispace1, finite_double),
397            preceded(multispace1, finite_double),
398            preceded(multispace1, property),
399        ),
400    )
401    .map(
402        |(layer, center, radius, start_angle, sweep_angle, property)| Arc {
403            layer,
404            center,
405            radius,
406            start_angle,
407            sweep_angle,
408            property,
409        },
410    )
411    .parse(input)
412}
413
414pub(crate) fn component_instance<'a, I, E>(input: I) -> IResult<I, Component<I>, E>
415where
416    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
417    <I as Input>::Item: AsChar,
418    E: ParseError<I> + ContextError<I>,
419{
420    object(
421        "component",
422        'C',
423        (
424            preceded(multispace1, reference),
425            preceded(multispace1, coordinate),
426            preceded(multispace1, rotation),
427            preceded(multispace1, flip),
428            preceded(multispace1, property),
429            opt(preceded(multispace1, embedding)),
430        ),
431    )
432    .map(
433        |(reference, position, rotation, flip, property, embedding)| Component {
434            reference,
435            position,
436            rotation,
437            flip,
438            property,
439            embedding,
440        },
441    )
442    .parse(input)
443}
444
445pub(crate) fn line_object<'a, I, E>(input: I) -> IResult<I, Line<I>, E>
446where
447    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
448    <I as Input>::Item: AsChar,
449    E: ParseError<I> + ContextError<I>,
450{
451    object(
452        "line",
453        'L',
454        (
455            preceded(multispace1, layer),
456            preceded(multispace1, coordinate),
457            preceded(multispace1, coordinate),
458            preceded(multispace1, property),
459        ),
460    )
461    .map(|(layer, start, end, property)| Line {
462        layer,
463        start,
464        end,
465        property,
466    })
467    .parse(input)
468}
469
470pub(crate) fn polygon_object<'a, I, E>(input: I) -> IResult<I, Polygon<I>, E>
471where
472    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
473    <I as Input>::Item: AsChar,
474    E: ParseError<I> + ContextError<I>,
475{
476    object(
477        "polygon",
478        'P',
479        (
480            preceded(multispace1, layer),
481            preceded(
482                multispace1,
483                length_count(usize, preceded(space1, coordinate)),
484            ),
485            preceded(multispace1, property),
486        ),
487    )
488    .map(|(layer, points, property)| Polygon {
489        layer,
490        points: points.into(),
491        property,
492    })
493    .parse(input)
494}
495
496pub(crate) fn rectangle_object<'a, I, E>(input: I) -> IResult<I, Rectangle<I>, E>
497where
498    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
499    <I as Input>::Item: AsChar,
500    E: ParseError<I> + ContextError<I>,
501{
502    object(
503        "rectangle",
504        'B',
505        (
506            preceded(multispace1, layer),
507            preceded(multispace1, coordinate),
508            preceded(multispace1, coordinate),
509            preceded(multispace1, property),
510        ),
511    )
512    .map(|(layer, start, end, property)| Rectangle {
513        layer,
514        start,
515        end,
516        property,
517    })
518    .parse(input)
519}
520
521pub(crate) fn text_object<'a, I, E>(input: I) -> IResult<I, Text<I>, E>
522where
523    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
524    <I as Input>::Item: AsChar,
525    E: ParseError<I> + ContextError<I>,
526{
527    object(
528        "text",
529        'T',
530        (
531            preceded(multispace1, text),
532            preceded(multispace1, coordinate),
533            preceded(multispace1, rotation),
534            preceded(multispace1, flip),
535            preceded(multispace1, size),
536            preceded(multispace1, property),
537        ),
538    )
539    .map(|(text, position, rotation, flip, size, property)| Text {
540        text,
541        position,
542        rotation,
543        flip,
544        size,
545        property,
546    })
547    .parse(input)
548}
549
550pub(crate) fn wire_object<'a, I, E>(input: I) -> IResult<I, Wire<I>, E>
551where
552    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
553    <I as Input>::Item: AsChar,
554    E: ParseError<I> + ContextError<I>,
555{
556    object(
557        "wire",
558        'N',
559        (
560            preceded(multispace1, coordinate),
561            preceded(multispace1, coordinate),
562            preceded(multispace1, property),
563        ),
564    )
565    .map(|(start, end, property)| Wire {
566        start,
567        end,
568        property,
569    })
570    .parse(input)
571}
572
573pub(crate) fn any_object<'a, I, E>(input: I) -> IResult<I, Object<I>, E>
574where
575    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
576    <I as Input>::Item: AsChar,
577    E: ParseError<I> + ContextError<I>,
578{
579    alt((
580        Parser::into(Parser::into::<VhdlProperty<I>, E>(property_object('G'))),
581        Parser::into(Parser::into::<SymbolProperty<I>, E>(property_object('K'))),
582        Parser::into(Parser::into::<VerilogProperty<I>, E>(property_object('V'))),
583        Parser::into(Parser::into::<SpiceProperty<I>, E>(property_object('S'))),
584        Parser::into(Parser::into::<TedaXProperty<I>, E>(property_object('E'))),
585        Parser::into(arc_object),
586        Parser::into(component_instance),
587        Parser::into(line_object),
588        Parser::into(polygon_object),
589        Parser::into(rectangle_object),
590        Parser::into(text_object),
591        Parser::into(wire_object),
592    ))
593    .parse(input)
594}
595
596/// Parse a [`Schematic`] from input.
597pub fn schematic<'a, I, E>(input: I) -> IResult<I, Schematic<I>, E>
598where
599    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
600    <I as Input>::Item: AsChar,
601    E: ParseError<I> + ContextError<I>,
602{
603    preceded(
604        multispace0,
605        version_object.flat_map(|version| {
606            fold_many0(
607                preceded(multispace1, any_object),
608                move || Schematic::new(version.clone()),
609                Schematic::add_object,
610            )
611        }),
612    )
613    .parse(input)
614}
615
616/// Parses a schematic to the end of the input.
617pub fn schematic_full<'a, I, E>(input: I) -> Result<Schematic<I>, E>
618where
619    I: Eq + Hash + Input + Offset + ParseTo<f64> + for<'s> Compare<&'s str> + 'a,
620    <I as Input>::Item: AsChar,
621    E: ParseError<I> + ContextError<I>,
622{
623    terminated(schematic, preceded(multispace0, eof))
624        .parse(input)
625        .finish()
626        .map(|r| r.1)
627}