pdl/
parse.rs

1use std::ops::RangeFrom;
2use std::str::FromStr;
3
4use nom::{
5    branch::alt,
6    bytes::complete::{is_a, tag, take_until, take_while},
7    character::complete::char,
8    combinator::{map, map_res, opt, recognize, verify},
9    error::ParseError,
10    multi::{many0, many1},
11    sequence::{pair, preceded, tuple},
12    AsChar, Compare, IResult, InputIter, InputLength, InputTake, InputTakeAtPosition, Slice,
13};
14
15use crate::*;
16
17/// Parse a `Protocol` from a string of PDL format.
18pub fn parse(input: &str) -> IResult<&str, Protocol> {
19    protocol(input)
20}
21
22fn protocol(input: &str) -> IResult<&str, Protocol> {
23    map(
24        tuple((
25            description,
26            empty_lines,
27            version,
28            many1(preceded(empty_lines, domain)),
29        )),
30        |(description, _, version, domains)| Protocol {
31            description,
32            version,
33            domains,
34        },
35    )(input)
36}
37
38fn indent(input: &str) -> IResult<&str, &str> {
39    recognize(many1(is_a(" \t")))(input)
40}
41
42fn empty_lines(input: &str) -> IResult<&str, &str> {
43    recognize(many0(eol))(input)
44}
45
46fn eol(input: &str) -> IResult<&str, char> {
47    char('\n')(input)
48}
49
50fn description(input: &str) -> IResult<&str, Description> {
51    map(many0(comment), Description)(input)
52}
53
54fn comment(input: &str) -> IResult<&str, &str> {
55    map(
56        tuple((
57            opt(indent),
58            char('#'),
59            map(take_until("\n"), str::trim),
60            eol,
61        )),
62        |(_, _, s, _eol)| s,
63    )(input)
64}
65
66fn version(input: &str) -> IResult<&str, Version> {
67    map(
68        tuple((
69            tuple((tag("version"), eol)),
70            tuple((
71                indent,
72                tag("major"),
73                char(' '),
74                map_res(take_while(|c: char| c.is_ascii_digit()), FromStr::from_str),
75                eol,
76            )),
77            tuple((
78                indent,
79                tag("minor"),
80                char(' '),
81                map_res(take_while(|c: char| c.is_ascii_digit()), FromStr::from_str),
82                eol,
83            )),
84        )),
85        |((_version, _), (_, _major, _, major, _), (_, _minor, _, minor, _))| Version {
86            major,
87            minor,
88        },
89    )(input)
90}
91
92fn domain(input: &str) -> IResult<&str, Domain> {
93    enum Item<'a> {
94        TypeDef(TypeDef<'a>),
95        Command(Command<'a>),
96        Event(Event<'a>),
97    }
98
99    map(
100        tuple((
101            description,
102            tuple((
103                optional("experimental"),
104                optional("deprecated"),
105                tag("domain"),
106                char(' '),
107                take_until("\n"),
108                eol,
109            )),
110            many0(depends_on),
111            many0(preceded(
112                empty_lines,
113                alt((
114                    map(type_def, Item::TypeDef),
115                    map(command, Item::Command),
116                    map(event, Item::Event),
117                )),
118            )),
119        )),
120        |(description, (experimental, deprecated, _domain, _, name, _eol), dependencies, items)| {
121            let (types, commands, events) = items.into_iter().fold(
122                (vec![], vec![], vec![]),
123                |(mut types, mut commands, mut events), item| {
124                    match item {
125                        Item::TypeDef(ty) => types.push(ty),
126                        Item::Command(cmd) => commands.push(cmd),
127                        Item::Event(evt) => events.push(evt),
128                    }
129
130                    (types, commands, events)
131                },
132            );
133
134            Domain {
135                description,
136                experimental,
137                deprecated,
138                name,
139                dependencies,
140                types,
141                commands,
142                events,
143            }
144        },
145    )(input)
146}
147
148fn depends_on(input: &str) -> IResult<&str, &str> {
149    map(
150        tuple((
151            indent,
152            tag("depends on"),
153            char(' '),
154            take_while(|c: char| !c.is_whitespace()),
155            eol,
156        )),
157        |(_, _depends_on, _, name, _eol)| name,
158    )(input)
159}
160
161fn type_def(input: &str) -> IResult<&str, TypeDef> {
162    map(
163        tuple((
164            description,
165            tuple((
166                indent,
167                optional("experimental"),
168                optional("deprecated"),
169                tag("type"),
170                char(' '),
171                take_while(|c: char| !c.is_whitespace()),
172                char(' '),
173                tag("extends"),
174                char(' '),
175                ty,
176                eol,
177            )),
178            opt(item),
179        )),
180        |(
181            description,
182            (_, experimental, deprecated, _type, _, id, _, _extends, _, extends, _),
183            item,
184        )| {
185            let ty = TypeDef {
186                description,
187                experimental,
188                deprecated,
189                id,
190                extends,
191                item,
192            };
193
194            trace!("{:?}", ty);
195
196            ty
197        },
198    )(input)
199}
200
201fn ty(input: &str) -> IResult<&str, Type> {
202    map(
203        tuple((
204            optional("array of"),
205            take_while(|c: char| !c.is_whitespace()),
206        )),
207        |(is_array, ty)| Type::new(ty, is_array),
208    )(input)
209}
210
211impl Type<'_> {
212    fn new(ty: &str, is_array: bool) -> Type {
213        if is_array {
214            Type::ArrayOf(Box::new(Type::new(ty, false)))
215        } else {
216            match ty {
217                "enum" => Type::Enum(vec![]),
218                "integer" => Type::Integer,
219                "number" => Type::Number,
220                "boolean" => Type::Boolean,
221                "string" => Type::String,
222                "object" => Type::Object,
223                "any" => Type::Any,
224                "binary" => Type::Binary,
225                _ => Type::Ref(ty),
226            }
227        }
228    }
229}
230
231fn item(input: &str) -> IResult<&str, Item> {
232    alt((
233        map(
234            preceded(tuple((indent, tag("enum"), eol)), many1(variant)),
235            Item::Enum,
236        ),
237        map(
238            preceded(tuple((indent, tag("properties"), eol)), many1(param)),
239            Item::Properties,
240        ),
241    ))(input)
242}
243
244fn variant(input: &str) -> IResult<&str, Variant> {
245    map(
246        tuple((
247            description,
248            tuple((
249                indent,
250                verify(take_while(|c: char| !c.is_whitespace()), |s: &str| {
251                    !s.is_empty() && s != "returns"
252                }),
253                eol,
254            )),
255        )),
256        |(description, (_, name, _))| {
257            let variant = Variant { description, name };
258
259            trace!("{:?}", variant);
260
261            variant
262        },
263    )(input)
264}
265
266fn param(input: &str) -> IResult<&str, Param> {
267    let (input, mut param) = map(
268        tuple((
269            description,
270            tuple((
271                indent,
272                optional("experimental"),
273                optional("deprecated"),
274                optional("optional"),
275                ty,
276                char(' '),
277                verify(take_while(|c: char| !c.is_whitespace()), |s: &str| {
278                    !s.is_empty()
279                }),
280                eol,
281            )),
282        )),
283        |(description, (_, experimental, deprecated, optional, ty, _, name, _))| {
284            let param = Param {
285                experimental,
286                deprecated,
287                optional,
288                ty,
289                description,
290                name,
291            };
292
293            trace!("{:?}", param);
294
295            param
296        },
297    )(input)?;
298
299    if let Type::Enum(ref mut variants) = param.ty {
300        let (input, mut vars) = many1(variant)(input)?;
301
302        trace!("{:?}", vars);
303
304        variants.append(&mut vars);
305
306        Ok((input, param))
307    } else {
308        Ok((input, param))
309    }
310}
311
312fn command(input: &str) -> IResult<&str, Command> {
313    map(
314        tuple((
315            description,
316            tuple((
317                indent,
318                optional("experimental"),
319                optional("deprecated"),
320                tag("command"),
321                char(' '),
322                take_until("\n"),
323                eol,
324            )),
325            opt(redirect),
326            opt(preceded(
327                tuple((indent, tag("parameters"), eol)),
328                many1(param),
329            )),
330            empty_lines,
331            opt(preceded(tuple((indent, tag("returns"), eol)), many1(param))),
332        )),
333        |(
334            description,
335            (_, experimental, deprecated, _, _, name, _),
336            redirect,
337            parameters,
338            _,
339            returns,
340        )| {
341            let command = Command {
342                description,
343                experimental,
344                deprecated,
345                name,
346                redirect,
347                parameters: parameters.unwrap_or_default(),
348                returns: returns.unwrap_or_default(),
349            };
350
351            trace!("{:?}", command);
352
353            command
354        },
355    )(input)
356}
357
358fn event(input: &str) -> IResult<&str, Event> {
359    map(
360        tuple((
361            description,
362            tuple((
363                indent,
364                optional("experimental"),
365                optional("deprecated"),
366                tag("event"),
367                char(' '),
368                take_until("\n"),
369                eol,
370            )),
371            opt(preceded(
372                tuple((indent, tag("parameters"), eol)),
373                many1(param),
374            )),
375        )),
376        |(description, (_, experimental, deprecated, _, _, name, _), parameters)| {
377            let event = Event {
378                description,
379                experimental,
380                deprecated,
381                name,
382                parameters: parameters.unwrap_or_default(),
383            };
384
385            trace!("{:?}", event);
386
387            event
388        },
389    )(input)
390}
391
392fn redirect(input: &str) -> IResult<&str, Redirect> {
393    map(
394        tuple((
395            description,
396            tuple((
397                indent,
398                tag("redirect"),
399                char(' '),
400                take_while(|c: char| !c.is_whitespace()),
401                eol,
402            )),
403        )),
404        |(description, (_, _redirect, _, to, _))| {
405            let redirect = Redirect { description, to };
406
407            trace!("{:?}", redirect);
408
409            redirect
410        },
411    )(input)
412}
413
414fn optional<T, I, E>(name: T) -> impl Fn(I) -> IResult<I, bool, E>
415where
416    T: InputLength + InputIter + Clone,
417    I: Slice<RangeFrom<usize>> + InputTake + InputTakeAtPosition + InputIter + Compare<T> + Clone,
418    <I as InputIter>::Item: AsChar,
419    <I as InputTakeAtPosition>::Item: AsChar + Clone,
420    E: ParseError<I>,
421{
422    map(opt(pair(tag(name), char(' '))), |v| v.is_some())
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428
429    #[test]
430    fn parse_protocol() {
431        assert_eq!(
432            protocol(
433                r#"# Copyright 2017 The Chromium Authors. All rights reserved.
434# Use of this source code is governed by a BSD-style license that can be
435# found in the LICENSE file.
436
437version
438  major 1
439  minor 3
440
441experimental domain Accessibility
442  depends on DOM
443
444  # Unique accessibility node identifier.
445  type AXNodeId extends string
446
447  # Enum of possible property types.
448  type AXValueType extends string
449    enum
450      boolean
451      tristate
452      booleanOrUndefined
453
454  # A single source for a computed AX property.
455  type AXValueSource extends object
456    properties
457      # What type of source this is.
458      AXValueSourceType type
459      # The value of this property source.
460      optional AXValue value
461      # The name of the relevant attribute, if any.
462      optional string attribute
463"#
464            )
465            .unwrap(),
466            (
467                "",
468                Protocol {
469                    description: vec![
470                        "Copyright 2017 The Chromium Authors. All rights reserved.",
471                        "Use of this source code is governed by a BSD-style license that can be",
472                        "found in the LICENSE file."
473                    ]
474                    .into(),
475                    version: Version { major: 1, minor: 3 },
476                    domains: vec![Domain {
477                        description: Default::default(),
478                        experimental: true,
479                        deprecated: false,
480                        name: "Accessibility",
481                        dependencies: vec!["DOM"],
482                        types: vec![
483                            TypeDef {
484                                description: "Unique accessibility node identifier.".into(),
485                                experimental: false,
486                                deprecated: false,
487                                id: "AXNodeId",
488                                extends: Type::String,
489                                item: None,
490                            },
491                            TypeDef {
492                                description: "Enum of possible property types.".into(),
493                                experimental: false,
494                                deprecated: false,
495                                id: "AXValueType",
496                                extends: Type::String,
497                                item: Some(Item::Enum(vec![
498                                    Variant {
499                                        description: Default::default(),
500                                        name: "boolean"
501                                    },
502                                    Variant {
503                                        description: Default::default(),
504                                        name: "tristate"
505                                    },
506                                    Variant {
507                                        description: Default::default(),
508                                        name: "booleanOrUndefined"
509                                    }
510                                ]))
511                            },
512                            TypeDef {
513                                description: "A single source for a computed AX property.".into(),
514                                experimental: false,
515                                deprecated: false,
516                                id: "AXValueSource",
517                                extends: Type::Object,
518                                item: Some(Item::Properties(vec![
519                                    Param {
520                                        description: "What type of source this is.".into(),
521                                        experimental: false,
522                                        deprecated: false,
523                                        optional: false,
524                                        ty: Type::Ref("AXValueSourceType"),
525                                        name: "type"
526                                    },
527                                    Param {
528                                        description: "The value of this property source.".into(),
529                                        experimental: false,
530                                        deprecated: false,
531                                        optional: true,
532                                        ty: Type::Ref("AXValue"),
533                                        name: "value"
534                                    },
535                                    Param {
536                                        description: "The name of the relevant attribute, if any."
537                                            .into(),
538                                        experimental: false,
539                                        deprecated: false,
540                                        optional: true,
541                                        ty: Type::String,
542                                        name: "attribute"
543                                    }
544                                ]))
545                            }
546                        ],
547                        commands: vec![],
548                        events: vec![]
549                    }],
550                }
551            )
552        )
553    }
554
555    #[test]
556    fn parse_comment() {
557        assert_eq!(
558            comment("# Copyright 2017 The Chromium Authors. All rights reserved.\n").unwrap(),
559            (
560                "",
561                "Copyright 2017 The Chromium Authors. All rights reserved."
562            )
563        )
564    }
565
566    #[test]
567    fn parse_version() {
568        assert_eq!(
569            version(
570                r#"version
571  major 1
572  minor 3
573"#
574            )
575            .unwrap(),
576            ("", Version { major: 1, minor: 3 })
577        )
578    }
579
580    #[test]
581    fn parse_domain() {
582        assert_eq!(
583            domain("experimental domain Accessibility\n").unwrap(),
584            (
585                "",
586                Domain {
587                    description: Default::default(),
588                    experimental: true,
589                    deprecated: false,
590                    name: "Accessibility",
591                    dependencies: vec![],
592                    types: vec![],
593                    commands: vec![],
594                    events: vec![],
595                }
596            )
597        );
598    }
599
600    #[test]
601    fn parse_depends_on() {
602        assert_eq!(depends_on("  depends on DOM\n").unwrap(), ("", "DOM"));
603    }
604
605    #[test]
606    fn parse_type_def() {
607        assert_eq!(
608            type_def(
609                r#"  type AXProperty extends object
610    properties
611      # The name of this property.
612      AXPropertyName name
613      # The value of this property.
614      AXValue value
615"#
616            )
617            .unwrap(),
618            (
619                "",
620                TypeDef {
621                    description: Default::default(),
622                    experimental: false,
623                    deprecated: false,
624                    id: "AXProperty",
625                    extends: Type::Object,
626                    item: Some(Item::Properties(vec![
627                        Param {
628                            description: "The name of this property.".into(),
629                            experimental: false,
630                            deprecated: false,
631                            optional: false,
632                            ty: Type::Ref("AXPropertyName"),
633                            name: "name"
634                        },
635                        Param {
636                            description: "The value of this property.".into(),
637                            experimental: false,
638                            deprecated: false,
639                            optional: false,
640                            ty: Type::Ref("AXValue"),
641                            name: "value"
642                        }
643                    ]))
644                }
645            )
646        )
647    }
648
649    #[test]
650    fn parse_enum() {
651        assert_eq!(
652            type_def(
653                r#"  # Enum of possible property sources.
654  type AXValueSourceType extends string
655    enum
656      attribute
657      implicit
658      style
659      contents
660      placeholder
661      relatedElement
662"#
663            )
664            .unwrap(),
665            (
666                "",
667                TypeDef {
668                    description: "Enum of possible property sources.".into(),
669                    experimental: false,
670                    deprecated: false,
671                    id: "AXValueSourceType",
672                    extends: Type::String,
673                    item: Some(Item::Enum(vec![
674                        Variant {
675                            description: Default::default(),
676                            name: "attribute"
677                        },
678                        Variant {
679                            description: Default::default(),
680                            name: "implicit"
681                        },
682                        Variant {
683                            description: Default::default(),
684                            name: "style"
685                        },
686                        Variant {
687                            description: Default::default(),
688                            name: "contents"
689                        },
690                        Variant {
691                            description: Default::default(),
692                            name: "placeholder"
693                        },
694                        Variant {
695                            description: Default::default(),
696                            name: "relatedElement"
697                        }
698                    ]))
699                }
700            )
701        );
702
703        assert_eq!(
704            type_def(
705                r#"  # Pseudo element type.
706  type PseudoType extends string
707    enum
708      first-line
709      first-letter
710      before
711"#
712            )
713            .unwrap(),
714            (
715                "",
716                TypeDef {
717                    description: "Pseudo element type.".into(),
718                    experimental: false,
719                    deprecated: false,
720                    id: "PseudoType",
721                    extends: Type::String,
722                    item: Some(Item::Enum(vec![
723                        Variant {
724                            description: Default::default(),
725                            name: "first-line"
726                        },
727                        Variant {
728                            description: Default::default(),
729                            name: "first-letter"
730                        },
731                        Variant {
732                            description: Default::default(),
733                            name: "before"
734                        }
735                    ]))
736                }
737            )
738        );
739    }
740
741    #[test]
742    fn parse_params() {
743        assert_eq!(
744            item(
745                r#"    properties
746      # The type of this value.
747      AXValueType type
748      # The computed value of this property.
749      optional any value
750      # One or more related nodes, if applicable.
751      optional array of AXRelatedNode relatedNodes
752      # Animation type of `Animation`.
753      enum type
754        CSSTransition
755        CSSAnimation
756        WebAnimation
757"#
758            )
759            .unwrap(),
760            (
761                "",
762                Item::Properties(vec![
763                    Param {
764                        description: "The type of this value.".into(),
765                        experimental: false,
766                        deprecated: false,
767                        optional: false,
768                        ty: Type::Ref("AXValueType"),
769                        name: "type"
770                    },
771                    Param {
772                        description: "The computed value of this property.".into(),
773                        experimental: false,
774                        deprecated: false,
775                        optional: true,
776                        ty: Type::Any,
777                        name: "value"
778                    },
779                    Param {
780                        description: "One or more related nodes, if applicable.".into(),
781                        experimental: false,
782                        deprecated: false,
783                        optional: true,
784                        ty: Type::ArrayOf(Box::new(Type::Ref("AXRelatedNode"))),
785                        name: "relatedNodes"
786                    },
787                    Param {
788                        description: "Animation type of `Animation`.".into(),
789                        experimental: false,
790                        deprecated: false,
791                        optional: false,
792                        ty: Type::Enum(vec![
793                            Variant::new("CSSTransition"),
794                            Variant::new("CSSAnimation"),
795                            Variant::new("WebAnimation"),
796                        ]),
797                        name: "type"
798                    }
799                ])
800            )
801        )
802    }
803
804    #[test]
805    fn parse_command() {
806        assert_eq!(
807            command(
808                r#"  # Returns the DER-encoded certificate.
809  experimental command getCertificate
810    parameters
811      # Origin to get certificate for.
812      string origin
813    returns
814      array of string tableNames
815"#
816            )
817            .unwrap(),
818            (
819                "",
820                Command {
821                    description: "Returns the DER-encoded certificate.".into(),
822                    experimental: true,
823                    deprecated: false,
824                    name: "getCertificate",
825                    redirect: None,
826                    parameters: vec![Param {
827                        description: "Origin to get certificate for.".into(),
828                        experimental: false,
829                        deprecated: false,
830                        optional: false,
831                        ty: Type::String,
832                        name: "origin"
833                    }],
834                    returns: vec![Param {
835                        description: Default::default(),
836                        experimental: false,
837                        deprecated: false,
838                        optional: false,
839                        ty: Type::ArrayOf(Box::new(Type::String)),
840                        name: "tableNames"
841                    }],
842                }
843            )
844        );
845
846        assert_eq!(
847            command(
848                r#"  # Hides any highlight.
849  command hideHighlight
850    # Use 'Overlay.hideHighlight' instead
851    redirect Overlay
852"#
853            )
854            .unwrap(),
855            (
856                "",
857                Command {
858                    description: "Hides any highlight.".into(),
859                    experimental: false,
860                    deprecated: false,
861                    name: "hideHighlight",
862                    redirect: Some(Redirect {
863                        description: "Use 'Overlay.hideHighlight' instead".into(),
864                        to: "Overlay"
865                    }),
866                    parameters: vec![],
867                    returns: vec![],
868                }
869            )
870        );
871    }
872
873    #[test]
874    fn parse_event() {
875        assert_eq!(
876            event(r#"  # Notification sent after the virtual time has advanced.
877  experimental event virtualTimeAdvanced
878    parameters
879      # The amount of virtual time that has elapsed in milliseconds since virtual time was first
880      # enabled.
881      number virtualTimeElapsed
882"#).unwrap(),
883            (
884                "",
885                Event {
886                    description: "Notification sent after the virtual time has advanced.".into(),
887                    experimental: true,
888                    deprecated: false,
889                    name: "virtualTimeAdvanced",
890                    parameters: vec![
891                        Param {
892                            description: vec![
893                                "The amount of virtual time that has elapsed in milliseconds since virtual time was first",
894                                "enabled."
895                            ].into(),
896                            experimental: false,
897                            deprecated: false,
898                            optional: false,
899                            ty: Type::Number,
900                            name: "virtualTimeElapsed"
901                        },
902                    ],
903                }
904            )
905        );
906    }
907
908    #[test]
909    fn parse_redirect() {
910        assert_eq!(
911            redirect(
912                r#"    # Use 'Emulation.clearGeolocationOverride' instead
913    redirect Emulation
914"#
915            )
916            .unwrap(),
917            (
918                "",
919                Redirect {
920                    description: "Use 'Emulation.clearGeolocationOverride' instead".into(),
921                    to: "Emulation"
922                }
923            )
924        )
925    }
926}