From 5f670cf87ac217930babb0b8ef0f953fd2ee3447 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Wed, 22 Aug 2018 01:42:39 +0200 Subject: Move all code in `lib.rs` to `parser.rs` Put our parsing code in its own module. We'll be creating a new `cocoa` module and doing this allows us to keep `lib` free as a module container. --- src/lib.rs | 514 +--------------------------------------------------------- src/parser.rs | 513 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 514 insertions(+), 513 deletions(-) create mode 100644 src/parser.rs (limited to 'src') diff --git a/src/lib.rs b/src/lib.rs index 346dcef..cf634d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,516 +1,4 @@ #[macro_use] extern crate combine; -use std::collections::HashMap; - -use combine::*; -use combine::parser::choice::or; -use combine::parser::char::{ - newline, - space, - string, - string_cmp, - tab, -}; -use combine::parser::repeat::take_until; - -#[derive(Debug, Hash, Eq, PartialEq)] -pub enum HeadphoneButton { - Play, - Up, - Down, -} -type Trigger = Vec; -type Action = String; - -#[derive(Debug, PartialEq)] -pub enum MapKind { - Map, - Command, -} - -#[derive(Debug, PartialEq)] -pub struct MapAction { - pub action: Action, - pub kind: MapKind, -} - -#[derive(Debug, PartialEq)] -struct Map { - trigger: Trigger, - action: Action, - kind: MapKind, -} - -type MapCollection = HashMap; - -#[derive(Debug, PartialEq)] -struct Mode { - trigger: Trigger, - maps: MapCollection, -} - -#[derive(Debug, PartialEq)] -pub struct MapGroup { - maps: MapCollection, - modes: HashMap, -} - -#[derive(Debug, PartialEq)] -enum Definition { - Map(Map), - Mode(Mode), -} - - -fn map_kind() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - or( - string("map").map(|_| MapKind::Map), - string("cmd").map(|_| MapKind::Command), - ) -} - -fn headphone_button() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - between( - token('<'), - token('>'), - choice!( - string_cmp("play", |l, r| l.eq_ignore_ascii_case(&r)) - .map(|_| HeadphoneButton::Play), - string_cmp("up", |l, r| l.eq_ignore_ascii_case(&r)) - .map(|_| HeadphoneButton::Up), - string_cmp("down", |l, r| l.eq_ignore_ascii_case(&r)) - .map(|_| HeadphoneButton::Down) - ), - ) -} - -fn trigger() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - many1(headphone_button()) -} - -fn action() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - take_until(newline()) -} - -fn whitespace_separator() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - skip_many1(space().or(tab())) -} - -fn map() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - ( - map_kind(), - whitespace_separator(), - trigger(), - whitespace_separator(), - action() - ).map(|(kind, _, trigger, _, action)| - Map { - trigger: trigger, - action: action, - kind: kind, - } - ) -} - -fn map_collection() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - ( - blank(), - many::, _>(map().skip(blank())), - ).map(|(_, collection)| { - let mut maps = HashMap::new(); - - for map in collection { - maps.insert( - map.trigger, - MapAction { - action: map.action, - kind: map.kind, - } - ); - } - - maps - }) -} - -fn mode() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - ( - string("mode"), - whitespace_separator(), - trigger(), - whitespace_separator(), - token('{'), - map_collection(), - token('}'), // Verify that this is parsed on its own line, not inside a map - ).map(|(_, _, trigger, _, _, collection, _)| - Mode { - trigger: trigger, - maps: collection, - } - ) -} - -fn definitions() -> impl Parser> -where - I: Stream, - I::Error: ParseError, -{ - ( - blank(), - many( - choice!( - try(map()).map(|map| Definition::Map(map)), - try(mode()).map(|mode| Definition::Mode(mode)) - ).skip(blank()) - ) - ).map(|(_, definitions)| definitions) -} - -fn map_group() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - definitions() - .map(|definitions| { - let mut maps = HashMap::new(); - let mut modes = HashMap::new(); - - for definition in definitions { - match definition { - Definition::Map(map) => { - maps.insert( - map.trigger, - MapAction { - action: map.action, - kind: map.kind, - } - ); - }, - Definition::Mode(mode) => { - modes.insert( - mode.trigger, - mode.maps, - ); - }, - } - } - - MapGroup { - maps: maps, - modes: modes, - } - }) -} - -fn comment() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - ( - token('#'), - skip_many(satisfy(|c| c != '\n')), - ) -} - - -fn blank() -> impl Parser -where - I: Stream, - I::Error: ParseError, -{ - let comment = comment().map(|_| ()); - let whitespace = whitespace_separator().map(|_| ()); - skip_many(skip_many1(newline()).or(whitespace).or(comment)) -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn map_kind_parses_kind_map() { - let text = "map"; - let result = map_kind().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(MapKind::Map)); - } - - #[test] - fn map_kind_parses_kind_command() { - let text = "cmd"; - let result = map_kind().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(MapKind::Command)); - } - - #[test] - fn headphone_button_parses_play() { - let text = ""; - let result = headphone_button().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(HeadphoneButton::Play)); - } - - #[test] - fn headphone_button_ignores_case() { - let text = ""; - let result = headphone_button().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(HeadphoneButton::Play)); - } - - #[test] - fn trigger_parses_headphone_button_sequence() { - let text = ""; - let result = trigger().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(vec![ - HeadphoneButton::Up, - HeadphoneButton::Down, - HeadphoneButton::Play, - ])); - } - - #[test] - fn action_parses_string_to_end_of_line() { - let text = "/usr/bin/say 'hello' -"; - let expected: Action = "/usr/bin/say 'hello'".to_owned(); - let result = action().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(expected)); - } - - #[test] - fn map_parses_map_line() { - let text = "map test -"; - let expected = Map { - trigger: vec![HeadphoneButton::Play, HeadphoneButton::Down], - action: "test".to_owned(), - kind: MapKind::Map, - }; - let result = map().parse(text).map(|t| t.0); - - assert_eq!(result, Ok(expected)); - } - - #[test] - fn map_collection_parses_maps() { - let text = " -# Test comment - # continued - -map test -map salt and pepper - -# Another comment -cmd /usr/bin/say 'hello' -"; - let result = map_collection().easy_parse(text).map(|t| t.0); - - let mut expected = HashMap::new(); - expected.insert( - vec![HeadphoneButton::Up, HeadphoneButton::Down], - MapAction { - action: "test".to_owned(), - kind: MapKind::Map, - }, - ); - expected.insert( - vec![HeadphoneButton::Play], - MapAction { - action: "salt and pepper".to_owned(), - kind: MapKind::Map, - }, - ); - expected.insert( - vec![HeadphoneButton::Down], - MapAction { - action: "/usr/bin/say 'hello'".to_owned(), - kind: MapKind::Command, - }, - ); - - assert_eq!(result, Ok(expected)); - } - - #[test] - fn mode_parses_a_mode() { - let text = "mode { - cmd echo hello - map insert {} - }"; - let result = mode().parse(text).map(|t| t.0); - - let mut expected = Mode { - trigger: vec![HeadphoneButton::Down, HeadphoneButton::Up], - maps: HashMap::new(), - }; - - expected.maps.insert( - vec![HeadphoneButton::Up, HeadphoneButton::Play], - MapAction { - action: "echo hello".to_owned(), - kind: MapKind::Command, - }, - ); - expected.maps.insert( - vec![HeadphoneButton::Down], - MapAction { - action: "insert {}".to_owned(), - kind: MapKind::Map, - }, - ); - - assert_eq!(result, Ok(expected)); - } - - #[test] - fn definitions_parses_modes_and_maps() { - let text = " - -mode { - cmd j -} -map m -mode { - cmd j -} - -map k -"; - let result = definitions().easy_parse(text).map(|t| t.0); - - let mut mode_up_maps = HashMap::new(); - mode_up_maps.insert( - vec![HeadphoneButton::Down], - MapAction { - action: "j".to_owned(), - kind: MapKind::Command, - } - ); - - let mut mode_down_up_maps = HashMap::new(); - mode_down_up_maps.insert( - vec![HeadphoneButton::Down], - MapAction { - action: "j".to_owned(), - kind: MapKind::Command, - } - ); - - let expected = vec![ - Definition::Mode(Mode { - trigger: vec![HeadphoneButton::Up], - maps: mode_up_maps, - }), - Definition::Map(Map { - trigger: vec![HeadphoneButton::Play], - action: "m".to_owned(), - kind: MapKind::Map, - }), - Definition::Mode(Mode { - trigger: vec![HeadphoneButton::Down, HeadphoneButton::Up], - maps: mode_down_up_maps, - }), - Definition::Map(Map { - trigger: vec![HeadphoneButton::Down], - action: "k".to_owned(), - kind: MapKind::Map, - }), - ]; - - assert_eq!(result, Ok(expected)); - } - - #[test] - fn map_group_parses_a_whole_map_file_string() { - let text = "map some text - -# The following does nothing -cmd /bin/echo nothing - -mode { - map p -} - -cmd /usr/bin/say hello -"; - let result = map_group().easy_parse(text).map(|t| t.0); - - let mut maps: MapCollection = HashMap::new(); - let mut modes: HashMap = HashMap::new(); - let mut mode_maps: MapCollection = HashMap::new(); - - maps.insert( - vec![HeadphoneButton::Down], - MapAction { - action: "/bin/echo nothing".to_owned(), - kind: MapKind::Command, - }, - ); - maps.insert( - vec![HeadphoneButton::Play], - MapAction { - action: "/usr/bin/say hello".to_owned(), - kind: MapKind::Command, - }, - ); - - mode_maps.insert( - vec![HeadphoneButton::Play], - MapAction { - action: "p".to_owned(), - kind: MapKind::Map, - }, - ); - modes.insert( - vec![HeadphoneButton::Down, HeadphoneButton::Up], - mode_maps, - ); - - let expected = MapGroup { - maps: maps, - modes: modes, - }; - - assert_eq!(result, Ok(expected)); - } -} +mod parser; diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..e30c5f4 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,513 @@ +use std::collections::HashMap; + +use combine::*; +use combine::parser::choice::or; +use combine::parser::char::{ + newline, + space, + string, + string_cmp, + tab, +}; +use combine::parser::repeat::take_until; + +#[derive(Debug, Hash, Eq, PartialEq)] +pub enum HeadphoneButton { + Play, + Up, + Down, +} +type Trigger = Vec; +type Action = String; + +#[derive(Debug, PartialEq)] +pub enum MapKind { + Map, + Command, +} + +#[derive(Debug, PartialEq)] +pub struct MapAction { + pub action: Action, + pub kind: MapKind, +} + +#[derive(Debug, PartialEq)] +struct Map { + trigger: Trigger, + action: Action, + kind: MapKind, +} + +type MapCollection = HashMap; + +#[derive(Debug, PartialEq)] +struct Mode { + trigger: Trigger, + maps: MapCollection, +} + +#[derive(Debug, PartialEq)] +pub struct MapGroup { + maps: MapCollection, + modes: HashMap, +} + +#[derive(Debug, PartialEq)] +enum Definition { + Map(Map), + Mode(Mode), +} + + +fn map_kind() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + or( + string("map").map(|_| MapKind::Map), + string("cmd").map(|_| MapKind::Command), + ) +} + +fn headphone_button() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + between( + token('<'), + token('>'), + choice!( + string_cmp("play", |l, r| l.eq_ignore_ascii_case(&r)) + .map(|_| HeadphoneButton::Play), + string_cmp("up", |l, r| l.eq_ignore_ascii_case(&r)) + .map(|_| HeadphoneButton::Up), + string_cmp("down", |l, r| l.eq_ignore_ascii_case(&r)) + .map(|_| HeadphoneButton::Down) + ), + ) +} + +fn trigger() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + many1(headphone_button()) +} + +fn action() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + take_until(newline()) +} + +fn whitespace_separator() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + skip_many1(space().or(tab())) +} + +fn map() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + ( + map_kind(), + whitespace_separator(), + trigger(), + whitespace_separator(), + action() + ).map(|(kind, _, trigger, _, action)| + Map { + trigger: trigger, + action: action, + kind: kind, + } + ) +} + +fn map_collection() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + ( + blank(), + many::, _>(map().skip(blank())), + ).map(|(_, collection)| { + let mut maps = HashMap::new(); + + for map in collection { + maps.insert( + map.trigger, + MapAction { + action: map.action, + kind: map.kind, + } + ); + } + + maps + }) +} + +fn mode() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + ( + string("mode"), + whitespace_separator(), + trigger(), + whitespace_separator(), + token('{'), + map_collection(), + token('}'), // Verify that this is parsed on its own line, not inside a map + ).map(|(_, _, trigger, _, _, collection, _)| + Mode { + trigger: trigger, + maps: collection, + } + ) +} + +fn definitions() -> impl Parser> +where + I: Stream, + I::Error: ParseError, +{ + ( + blank(), + many( + choice!( + try(map()).map(|map| Definition::Map(map)), + try(mode()).map(|mode| Definition::Mode(mode)) + ).skip(blank()) + ) + ).map(|(_, definitions)| definitions) +} + +fn map_group() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + definitions() + .map(|definitions| { + let mut maps = HashMap::new(); + let mut modes = HashMap::new(); + + for definition in definitions { + match definition { + Definition::Map(map) => { + maps.insert( + map.trigger, + MapAction { + action: map.action, + kind: map.kind, + } + ); + }, + Definition::Mode(mode) => { + modes.insert( + mode.trigger, + mode.maps, + ); + }, + } + } + + MapGroup { + maps: maps, + modes: modes, + } + }) +} + +fn comment() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + ( + token('#'), + skip_many(satisfy(|c| c != '\n')), + ) +} + + +fn blank() -> impl Parser +where + I: Stream, + I::Error: ParseError, +{ + let comment = comment().map(|_| ()); + let whitespace = whitespace_separator().map(|_| ()); + skip_many(skip_many1(newline()).or(whitespace).or(comment)) +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn map_kind_parses_kind_map() { + let text = "map"; + let result = map_kind().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(MapKind::Map)); + } + + #[test] + fn map_kind_parses_kind_command() { + let text = "cmd"; + let result = map_kind().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(MapKind::Command)); + } + + #[test] + fn headphone_button_parses_play() { + let text = ""; + let result = headphone_button().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(HeadphoneButton::Play)); + } + + #[test] + fn headphone_button_ignores_case() { + let text = ""; + let result = headphone_button().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(HeadphoneButton::Play)); + } + + #[test] + fn trigger_parses_headphone_button_sequence() { + let text = ""; + let result = trigger().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(vec![ + HeadphoneButton::Up, + HeadphoneButton::Down, + HeadphoneButton::Play, + ])); + } + + #[test] + fn action_parses_string_to_end_of_line() { + let text = "/usr/bin/say 'hello' +"; + let expected: Action = "/usr/bin/say 'hello'".to_owned(); + let result = action().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(expected)); + } + + #[test] + fn map_parses_map_line() { + let text = "map test +"; + let expected = Map { + trigger: vec![HeadphoneButton::Play, HeadphoneButton::Down], + action: "test".to_owned(), + kind: MapKind::Map, + }; + let result = map().parse(text).map(|t| t.0); + + assert_eq!(result, Ok(expected)); + } + + #[test] + fn map_collection_parses_maps() { + let text = " +# Test comment + # continued + +map test +map salt and pepper + +# Another comment +cmd /usr/bin/say 'hello' +"; + let result = map_collection().easy_parse(text).map(|t| t.0); + + let mut expected = HashMap::new(); + expected.insert( + vec![HeadphoneButton::Up, HeadphoneButton::Down], + MapAction { + action: "test".to_owned(), + kind: MapKind::Map, + }, + ); + expected.insert( + vec![HeadphoneButton::Play], + MapAction { + action: "salt and pepper".to_owned(), + kind: MapKind::Map, + }, + ); + expected.insert( + vec![HeadphoneButton::Down], + MapAction { + action: "/usr/bin/say 'hello'".to_owned(), + kind: MapKind::Command, + }, + ); + + assert_eq!(result, Ok(expected)); + } + + #[test] + fn mode_parses_a_mode() { + let text = "mode { + cmd echo hello + map insert {} + }"; + let result = mode().parse(text).map(|t| t.0); + + let mut expected = Mode { + trigger: vec![HeadphoneButton::Down, HeadphoneButton::Up], + maps: HashMap::new(), + }; + + expected.maps.insert( + vec![HeadphoneButton::Up, HeadphoneButton::Play], + MapAction { + action: "echo hello".to_owned(), + kind: MapKind::Command, + }, + ); + expected.maps.insert( + vec![HeadphoneButton::Down], + MapAction { + action: "insert {}".to_owned(), + kind: MapKind::Map, + }, + ); + + assert_eq!(result, Ok(expected)); + } + + #[test] + fn definitions_parses_modes_and_maps() { + let text = " + +mode { + cmd j +} +map m +mode { + cmd j +} + +map k +"; + let result = definitions().easy_parse(text).map(|t| t.0); + + let mut mode_up_maps = HashMap::new(); + mode_up_maps.insert( + vec![HeadphoneButton::Down], + MapAction { + action: "j".to_owned(), + kind: MapKind::Command, + } + ); + + let mut mode_down_up_maps = HashMap::new(); + mode_down_up_maps.insert( + vec![HeadphoneButton::Down], + MapAction { + action: "j".to_owned(), + kind: MapKind::Command, + } + ); + + let expected = vec![ + Definition::Mode(Mode { + trigger: vec![HeadphoneButton::Up], + maps: mode_up_maps, + }), + Definition::Map(Map { + trigger: vec![HeadphoneButton::Play], + action: "m".to_owned(), + kind: MapKind::Map, + }), + Definition::Mode(Mode { + trigger: vec![HeadphoneButton::Down, HeadphoneButton::Up], + maps: mode_down_up_maps, + }), + Definition::Map(Map { + trigger: vec![HeadphoneButton::Down], + action: "k".to_owned(), + kind: MapKind::Map, + }), + ]; + + assert_eq!(result, Ok(expected)); + } + + #[test] + fn map_group_parses_a_whole_map_file_string() { + let text = "map some text + +# The following does nothing +cmd /bin/echo nothing + +mode { + map p +} + +cmd /usr/bin/say hello +"; + let result = map_group().easy_parse(text).map(|t| t.0); + + let mut maps: MapCollection = HashMap::new(); + let mut modes: HashMap = HashMap::new(); + let mut mode_maps: MapCollection = HashMap::new(); + + maps.insert( + vec![HeadphoneButton::Down], + MapAction { + action: "/bin/echo nothing".to_owned(), + kind: MapKind::Command, + }, + ); + maps.insert( + vec![HeadphoneButton::Play], + MapAction { + action: "/usr/bin/say hello".to_owned(), + kind: MapKind::Command, + }, + ); + + mode_maps.insert( + vec![HeadphoneButton::Play], + MapAction { + action: "p".to_owned(), + kind: MapKind::Map, + }, + ); + modes.insert( + vec![HeadphoneButton::Down, HeadphoneButton::Up], + mode_maps, + ); + + let expected = MapGroup { + maps: maps, + modes: modes, + }; + + assert_eq!(result, Ok(expected)); + } +} -- cgit v1.2.3