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
17pub 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}