opslang_syn/
lib.rs

1use peg::str::LineCol;
2use thiserror::Error;
3use typedef::Row;
4
5pub mod typedef;
6pub mod parser {
7    pub use crate::ops_parser::row as parse_row;
8}
9
10peg::parser! {
11    grammar ops_parser() for str {
12        use typedef::*;
13        rule file_path() -> FilePath<'input>
14            = full_name:ident()
15            { FilePath { full_name } }
16
17        rule variable_path() -> VariablePath<'input>
18            = raw:ident()
19            { VariablePath { raw } }
20
21        rule ident() -> &'input str
22            = $(
23                [c if c.is_ascii_alphabetic()]
24                [c if c.is_ascii_alphanumeric() || c == '_' || c == '/' || c == '.' || c == '-']*
25            )
26
27        rule destination() -> Destination<'input>
28            = component:$([c if c.is_ascii_alphanumeric()]+) destination_sep()
29            exec_method:$([c if c.is_ascii_alphanumeric() || c == '_']+)
30            { Destination { component, exec_method } }
31
32        rule destination_sep() = "_"
33
34        pub(crate) rule command() -> Command<'input>
35            = destinations:(d:destination() _ "." {d})* _ name:cmd_name() _ args:(e:expr() _ {e})*
36            { Command { destinations, name, args } }
37
38        rule cmd_name() -> &'input str
39            = $(
40                [c if c.is_ascii_alphabetic()]
41                [c if c.is_ascii_alphanumeric() || c == '_']*
42            )
43
44        pub(crate) rule expr() -> Expr<'input>
45            = precedence!{
46                x:@ _ "||" _ y:(@) { Expr::BinOp(BinOpKind::Or, Box::new(x), Box::new(y)) }
47                x:@ _ "if" __ y:(@) { Expr::BinOp(BinOpKind::If, Box::new(x), Box::new(y)) }
48                --
49                x:@ _ "and" __ y:(@) { Expr::BinOp(BinOpKind::And, Box::new(x), Box::new(y)) }
50                --
51                x:(@) _ e:eq_binop_kind() _ y:@ {Expr::BinOp(BinOpKind::Compare(e), Box::new(x), Box::new(y)) }
52                --
53                x:(@) _ "in" __ y:@ { Expr::BinOp(BinOpKind::In, Box::new(x), Box::new(y)) }
54                x:(@) _ c:compare_binop_kind() _ y:@ { Expr::BinOp(BinOpKind::Compare(c), Box::new(x), Box::new(y)) }
55                --
56                x:(@) _ "+" _ y:@ { Expr::BinOp(BinOpKind::Add, Box::new(x), Box::new(y)) }
57                x:(@) _ "-" _ y:@ { Expr::BinOp(BinOpKind::Sub, Box::new(x), Box::new(y)) }
58                        "-" _ v:@ { Expr::UnOp(UnOpKind::Neg, Box::new(v)) }
59                --
60                x:(@) _ "*" _ y:@ { Expr::BinOp(BinOpKind::Mul, Box::new(x), Box::new(y)) }
61                x:(@) _ "/" _ y:@ { Expr::BinOp(BinOpKind::Div, Box::new(x), Box::new(y)) }
62                x:(@) _ "%" _ y:@ { Expr::BinOp(BinOpKind::Mod, Box::new(x), Box::new(y)) }
63                --
64                x:@ _ "(" _ v:(expr() ** (_ "," _)) _ ")" { Expr::FunCall(Box::new(x), v) }
65                --
66                "(" _ v:expr() _ ")" { v }
67                n:literal() { Expr::Literal(n) }
68                v:variable_path() { Expr::Variable(v) }
69            }
70
71        pub(crate) rule numeric() -> Numeric<'input>
72            = "0x" i:$(['a'..='f' | 'A'..='F' | '0'..='9' | '_']+)
73            { Numeric::Integer(i, IntegerPrefix::Hexadecimal) }
74            / "0o" i:$(['0'..='7' | '_']+)
75            { Numeric::Integer(i, IntegerPrefix::Octal) }
76            / "0b" i:$(['0' | '1' | '_']+)
77            { Numeric::Integer(i, IntegerPrefix::Binary) }
78            / i:$(['0'..='9']['0'..='9' | '_']*) !['.' | 'e' | 'E']
79            { Numeric::Integer(i, IntegerPrefix::Decimal) }
80            / f:$(['0'..='9']['0'..='9' | '_']*
81                "."? (['0'..='9']['0'..='9' | '_']*)?
82                (['e' | 'E']['+' | '-']['0'..='9' | '_']*)?
83            ) { Numeric::Float(f) }
84
85        rule numeric_suffix() -> NumericSuffix
86            = "s" ![c if c.is_alphanumeric()] { NumericSuffix::Second }
87
88        rule literal() -> Literal<'input>
89            = "[" _ v:(expr() ** (_ "," _)) _ "]" { Literal::Array(v) }
90            / "\"" s:$([c if c != '"']*) "\"" { Literal::String(s) }
91            / n:numeric() s:numeric_suffix()? { Literal::Numeric(n, s) }
92
93        rule compare_binop_kind() -> CompareBinOpKind
94            = ">=" { CompareBinOpKind::GreaterEq }
95            / "<=" { CompareBinOpKind::LessEq }
96            / ">" { CompareBinOpKind::Greater }
97            / "<" { CompareBinOpKind::Less }
98
99        rule eq_binop_kind() -> CompareBinOpKind
100            = "!=" { CompareBinOpKind::NotEqual }
101            / "==" { CompareBinOpKind::Equal }
102
103        pub(crate) rule call() -> Call<'input>
104            = "call" __ path:file_path()
105            { Call { path } }
106
107        pub(crate) rule wait_sec() -> WaitSec<'input>
108            = "wait_sec" __ sec:expr()
109            { WaitSec { sec } }
110
111        pub(crate) rule wait_until() -> WaitUntil<'input>
112            = "wait_until" __ condition:expr()
113            { WaitUntil { condition } }
114
115        pub(crate) rule wait_inc() -> WaitInc<'input>
116            = "wait_inc" __ condition:expr()
117            { WaitInc { condition } }
118
119        pub(crate) rule check_value() -> CheckValue<'input>
120            = "check_value" __ condition:expr()
121            { CheckValue { condition } }
122
123        pub(crate) rule let_bind() -> Let<'input>
124            = "let" __ raw:ident() _ "=" _ rhs:expr()
125            { Let { variable: Ident { raw }, rhs } }
126
127        pub(crate) rule get() -> Get<'input>
128            = "get" __ variable:variable_path()
129            { Get { variable } }
130
131        rule reserved_control() -> ReservedControl<'input>
132            = call:call() { ReservedControl::Call(call) }
133            / wait_sec:wait_sec() { ReservedControl::WaitSec(wait_sec) }
134            / wait_until:wait_until() { ReservedControl::WaitUntil(wait_until) }
135            / check_value:check_value() { ReservedControl::CheckValue(check_value) }
136            / let_bind:let_bind() { ReservedControl::Let(let_bind) }
137            / get:get() { ReservedControl::Get(get) }
138            / command:command() { ReservedControl::Command(command) }
139
140        rule comment() -> Comment<'input>
141            = "#" _ s:$([_]*) { Comment(s) }
142
143        pub rule row() -> Row<'input>
144            = breaks:"."? _ r:(
145                  content:reserved_control() _ comment_trailing:comment()?
146                    { Row { breaks, content: Some(content), comment_trailing } }
147                / comment_trailing:comment()?
148                    { Row { breaks, content: None, comment_trailing } }
149            ) { r }
150
151        rule _() = ws()*
152        rule __() = ![c if c.is_alphanumeric()] _
153
154        rule ws() = quiet!{[c if c.is_whitespace()]}
155    }
156}
157
158#[derive(Debug, Error)]
159pub enum Error {
160    #[error("parse failed: {0}")]
161    ParseError(#[from] peg::error::ParseError<LineCol>),
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_numeric() {
170        let r = ops_parser::numeric("5e-3");
171        dbg!(r.unwrap());
172        let r = ops_parser::numeric("0");
173        dbg!(r.unwrap());
174        let r = ops_parser::numeric("10");
175        dbg!(r.unwrap());
176        let r = ops_parser::numeric("0.1");
177        dbg!(r.unwrap());
178    }
179    #[test]
180    fn test_expr() {
181        let r = ops_parser::expr("a + b * c");
182        dbg!(r.unwrap());
183        let r = ops_parser::expr("a * b + c");
184        dbg!(r.unwrap());
185        let r = ops_parser::expr("1 + (a.a * (0xB_C))");
186        dbg!(r.unwrap());
187        let r = ops_parser::expr("a ((c), d) + b");
188        dbg!(r.unwrap());
189        let r = ops_parser::expr("ABC.DEF == 0xabcdef || 5s");
190        dbg!(r.unwrap());
191    }
192    #[test]
193    fn test_space() {
194        fn differ<T: std::cmp::PartialEq + std::fmt::Debug, E: std::fmt::Debug>(
195            f: impl Fn(&'static str) -> Result<T, E>,
196            l: &'static str,
197            r: &'static str,
198        ) {
199            let l = f(l).unwrap();
200            let r = f(r).unwrap();
201            assert_ne!(l, r);
202        }
203        fn not_differ<T: std::cmp::PartialEq + std::fmt::Debug, E: std::fmt::Debug>(
204            f: impl Fn(&'static str) -> Result<T, E>,
205            l: &'static str,
206            r: &'static str,
207        ) {
208            let l = f(l).unwrap();
209            let r = f(r).unwrap();
210            assert_eq!(l, r);
211        }
212        differ(
213            ops_parser::expr,
214            "EXAMPLE.VARIABLE.NAME/s",
215            "EXAMPLE.VARIBLAE.NAME / s",
216        );
217        not_differ(
218            ops_parser::check_value,
219            "check_value x in [ 100, 200 ]",
220            "check_value x in[ 100,200]",
221        );
222        not_differ(ops_parser::let_bind, "let variable=0", "let variable = 0");
223    }
224    #[test]
225    fn test_fun_call() {
226        let r = ops_parser::let_bind(
227            "let result_of_test_fun = Test.fun2(Test.fun1([P.VEC.X, P.VEC.Y, P.VEC.Z]))",
228        );
229        dbg!(r.unwrap());
230    }
231    #[test]
232    fn test_call() {
233        let r = ops_parser::call("call OTHER_FILE.ops");
234        dbg!(r.unwrap());
235    }
236    #[test]
237    fn test_wait_sec() {
238        let r = ops_parser::wait_sec("wait_sec 12");
239        dbg!(r.unwrap());
240        let r = ops_parser::wait_sec("wait_sec 0.1");
241        dbg!(r.unwrap());
242    }
243    #[test]
244    fn test_wait_until() {
245        let r = ops_parser::wait_until("wait_until HEX.VALUE == 0x0123cdef || 5s");
246        dbg!(r.unwrap());
247        let r = ops_parser::wait_until("wait_until SOME.CONDITION.TO.BE.TESTED == FALSE");
248        dbg!(r.unwrap());
249    }
250    #[test]
251    fn test_check_value() {
252        let r = ops_parser::check_value("check_value TEST.VAR1 < 0.05");
253        dbg!(r.unwrap());
254        let r = ops_parser::check_value("check_value TEST.VAR2.X  in [ 0.01, 0.04 ]");
255        dbg!(r.unwrap());
256        let r = ops_parser::check_value(r#"check_value TEST.VAR_3 == "OFF""#);
257        dbg!(r.unwrap());
258        let r = ops_parser::check_value(
259            r#"check_value TEST.VAR_4.X in [ 0, 0.07 ] if TEST.Var5 == "OFF""#,
260        );
261        dbg!(r.unwrap());
262    }
263    #[test]
264    fn test_let() {
265        let r = ops_parser::let_bind("let relative_x = relative_x + 9");
266        dbg!(r.unwrap());
267        let r = ops_parser::let_bind("let HYPHEN-IS_VALID = 1");
268        dbg!(r.unwrap());
269    }
270    #[test]
271    fn test_get() {
272        let r = ops_parser::get("get abc_xyz");
273        dbg!(r.unwrap());
274    }
275    #[test]
276    fn test_command() {
277        let r = ops_parser::command("Cmd_DO_IT");
278        dbg!(r.unwrap());
279        let r = ops_parser::command("ABC_DEF.Cmd_DO_IT");
280        dbg!(r.unwrap());
281        let r = ops_parser::command("ABC_DEF.Cmd_WITH_ARGS some_value 0xaa 0xbb 2");
282        dbg!(r.unwrap());
283        let r = ops_parser::command("ABC_DEF.G_H.Cmd_WITH_ARGS relative_x 0xcc 0xdd 0xee 0xff");
284        dbg!(r.unwrap());
285    }
286    #[test]
287    fn test_rows() {
288        let s = r#".# ****** #
289.let CURRENT_EXAMPLE_TLM_VALUE = FOO.BAR.EXAMPLE_TLM.VALUE
290.ABC_DEF.Cmd_DO_IT
291 wait_until FOO.BAR.EXAMPLE_TLM.VALUE > CURRENT_EXAMPLE_TLM_VALUE || 5s"#;
292        for l in s.lines() {
293            let r = ops_parser::row(l);
294            dbg!(r.unwrap());
295        }
296    }
297}