fsl/parser/
fsl_parser.rs

1// The MIT License (MIT)
2//
3// Copyright (c) 2024 Aliaksei Bialiauski
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included
13// in all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22use pest_derive::Parser;
23
24/// FSL Syntax Parser.
25#[derive(Parser)]
26#[grammar = "program.pest"]
27pub struct FslParser {}
28
29#[cfg(test)]
30mod tests {
31    use crate::parser::fsl_parser::{FslParser, Rule};
32    use crate::sample_program::sample_program;
33    use anyhow::Result;
34    use hamcrest::{equal_to, is, HamcrestMatcher};
35    use parameterized::parameterized;
36    use pest::Parser;
37
38    #[parameterized(
39        program = {
40            &sample_program("me.fsl"),
41            &sample_program("plusfoo-plusbar.fsl")
42        }
43      )
44    ]
45    fn parses_program(program: &str) -> Result<()> {
46        let parsed = FslParser::parse(Rule::program, program)
47            .expect("Failed to parse FSL syntax")
48            .next()
49            .expect("Failed to get pair");
50        let pairs = parsed.into_inner().as_str();
51        let mut trimmed = program.to_string();
52        trimmed.pop().expect("Failed to remove last character");
53        assert_that!(String::from(pairs), is(equal_to(trimmed)));
54        Ok(())
55    }
56
57    #[test]
58    fn parses_me() -> Result<()> {
59        let parse = FslParser::parse(Rule::me, "me: @jeff")
60            .expect("Failed to parse FSL syntax");
61        assert_that!(parse.as_str(), is(equal_to("me: @jeff")));
62        Ok(())
63    }
64
65    #[parameterized(input = {"@jeff", "@x"})]
66    fn parses_login(input: &str) -> Result<()> {
67        let parsed = FslParser::parse(Rule::login, input)
68            .expect("Failed to parse login");
69        assert_that!(parsed.as_str(), is(equal_to(input)));
70        Ok(())
71    }
72
73    #[should_panic(expected = "Failed to parse login")]
74    #[parameterized(
75        input = {
76            "@_f",
77            "abc",
78            "@",
79            "testing@",
80            "@."
81        }
82    )]
83    fn panics_on_invalid_login(input: &str) {
84        FslParser::parse(Rule::login, input).expect("Failed to parse login");
85    }
86
87    #[test]
88    fn parses_command() -> Result<()> {
89        let parsed = FslParser::parse(Rule::command, "+repo me/foo > foo")
90            .expect("Failed to parse FSL syntax");
91        assert_that!(parsed.as_str(), is(equal_to("+repo me/foo > foo")));
92        Ok(())
93    }
94
95    #[test]
96    fn parses_object() -> Result<()> {
97        let parsed = FslParser::parse(Rule::object, "repo me/foo > foo")
98            .expect("Failed to parse FSL syntax");
99        assert_that!(parsed.as_str(), is(equal_to("repo me/foo > foo")));
100        Ok(())
101    }
102
103    #[test]
104    fn parses_object_without_attributes() -> Result<()> {
105        let parsed = FslParser::parse(Rule::object, "repo > foo")
106            .expect("Failed to parse FSL syntax");
107        assert_that!(parsed.as_str(), is(equal_to("repo > foo")));
108        Ok(())
109    }
110
111    #[test]
112    fn parses_new() -> Result<()> {
113        let parsed = FslParser::parse(Rule::new, "> x")
114            .expect("Failed to parse FSL syntax");
115        assert_that!(parsed.as_str(), is(equal_to("> x")));
116        Ok(())
117    }
118
119    #[parameterized(input = {"x", "xy", "foo", "test"})]
120    fn parses_ref(input: &str) -> Result<()> {
121        let parsed = FslParser::parse(Rule::reference, input)
122            .expect("Failed to parse FSL syntax");
123        assert_that!(parsed.as_str(), is(equal_to(input)));
124        Ok(())
125    }
126
127    #[test]
128    fn parses_application() -> Result<()> {
129        let parsed = FslParser::parse(Rule::application, "-> x")
130            .expect("Failed to parse application syntax");
131        assert_that!(parsed.as_str(), is(equal_to("-> x")));
132        Ok(())
133    }
134
135    #[test]
136    fn parses_object_with_application() -> Result<()> {
137        let parsed = FslParser::parse(Rule::object, "issue testing -> x")
138            .expect("Failed to parse object syntax");
139        assert_that!(parsed.as_str(), is(equal_to("issue testing -> x")));
140        Ok(())
141    }
142
143    #[should_panic(expected = "Failed to parse reference")]
144    #[parameterized(
145        input = {
146            "_",
147            "_test",
148            "@",
149            "!",
150            "!bar",
151            ".",
152            "/t",
153            "X",
154            "XYZ"
155        }
156    )]
157    fn panics_on_invalid_ref(input: &str) {
158        FslParser::parse(Rule::reference, input)
159            .expect("Failed to parse reference");
160    }
161
162    #[parameterized(
163        input = {
164            "# test",
165            "# UPPER CASE",
166            "#  extra space",
167            "# with extra words",
168            "# with dot in the end.",
169            "# test, and another test.",
170            "#sticky works too.",
171            "# it works THIS way TOO.",
172            "# parses with tag inside #",
173            "# parses with text after tag # this way",
174            "# `special` 'characters' ```test```"
175        }
176    )]
177    fn parses_comment(input: &str) -> Result<()> {
178        let parsed = FslParser::parse(Rule::comment, input)
179            .expect("failed to parse comment");
180        assert_that!(parsed.as_str(), is(equal_to(input)));
181        Ok(())
182    }
183
184    #[test]
185    fn parses_program_with_comments() -> Result<()> {
186        let program = &sample_program("with-comments.fsl");
187        let pairs = FslParser::parse(Rule::program, program)
188            .expect("failed to parse program with comments");
189        assert_that!(pairs.as_str().len(), is(equal_to(143)));
190        Ok(())
191    }
192
193    #[test]
194    fn parses_program_with_license() -> Result<()> {
195        let program = &sample_program("with-license.fsl");
196        let pairs = FslParser::parse(Rule::program, program)
197            .expect("failed to parse program with license");
198        assert_that!(pairs.as_str().len(), is(equal_to(211)));
199        Ok(())
200    }
201}