opslang_parser/
lib.rs

1use opslang_ast::Row;
2use peg::str::LineCol;
3use thiserror::Error;
4
5pub mod parser {
6    pub use crate::ops_parser::{row as parse_row, statements as parse_statements};
7}
8
9peg::parser! {
10    grammar ops_parser() for str {
11        use opslang_ast::*;
12        rule file_path() -> FilePath
13            = full_name:(file_path_section() ** (_ "/" _))
14            { FilePath { full_name: full_name.join("/") } }
15
16        rule file_path_section_ident() -> &'input str
17            = quiet!{
18                e:$( [c if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.']+)
19                {? if e.chars().all(|c| c == '.') { Err("") } else { Ok(e) } }
20            }
21
22        rule file_path_section() -> &'input str
23            = file_path_section_ident()
24            / ".." { ".." }
25            / "." { "." }
26
27        rule variable_path() -> VariablePath
28            = raw:ident()
29            { VariablePath { raw: raw.to_owned() } }
30
31        rule tlm_ref() -> VariablePath
32            = "$" raw:ident()
33            { VariablePath { raw: raw.to_owned() } }
34
35        rule ident() -> &'input str
36            = quiet!{$(
37                [c if c.is_ascii_alphabetic()]
38                [c if c.is_ascii_alphanumeric() || c == '_' || c == '/' || c == '.' || c == '-']*
39            )} / expected!("ident")
40
41        rule alphanum_underscore() -> &'input str
42            = quiet!{$([c if c.is_ascii_alphanumeric() || c == '_']*)} / expected!("alphanum_underscore")
43
44        rule receiver() -> ReceiverComponent
45            = exec_method:alphanum_underscore() "." name:alphanum_underscore()
46            { ReceiverComponent { name: name.to_owned(), exec_method: exec_method.to_owned() } }
47        rule executor() -> ExecutorComponent
48            = name:alphanum_underscore()
49            { ExecutorComponent { name: name.to_owned() } }
50
51        rule time_indicator() -> Expr
52            = e:expr() ":" { e } // TODO: これで大丈夫か検討
53
54        rule destination_spec() -> DestinationSpec
55           = receiver_component:("@" r:receiver() { r })? _
56             time_indicator:time_indicator()? _
57             executor_component:("@@" e:executor() { e })?
58           { DestinationSpec { receiver_component, time_indicator, executor_component } }
59
60        pub(crate) rule command() -> Command
61            = destination:destination_spec() _ name:command_name() _ args:(e:expr() _ {e})*
62            { Command { destination, name: name.to_owned(), args } }
63
64        rule command_name() -> &'input str
65            = quiet!{$(
66                [c if c.is_ascii_alphabetic()]
67                [c if c.is_ascii_alphanumeric() || c == '_']*
68            )} / expected!("command name")
69
70        pub(crate) rule expr() -> Expr
71            = precedence!{
72                x:@ _ "||" _ y:(@) { Expr::BinOp(BinOpKind::Or, Box::new(x), Box::new(y)) }
73                x:@ _ "if" __ y:(@) { Expr::BinOp(BinOpKind::If, Box::new(x), Box::new(y)) }
74                --
75                x:@ _ "&&" __ y:(@) { Expr::BinOp(BinOpKind::And, Box::new(x), Box::new(y)) }
76                --
77                x:(@) _ e:eq_binop_kind() _ y:@ {Expr::BinOp(BinOpKind::Compare(e), Box::new(x), Box::new(y)) }
78                --
79                x:(@) _ "in" __ y:@ { Expr::BinOp(BinOpKind::In, Box::new(x), Box::new(y)) }
80                x:(@) _ c:compare_binop_kind() _ y:@ { Expr::BinOp(BinOpKind::Compare(c), Box::new(x), Box::new(y)) }
81                --
82                x:(@) _ "+" _ y:@ { Expr::BinOp(BinOpKind::Add, Box::new(x), Box::new(y)) }
83                x:(@) _ "-" _ y:@ { Expr::BinOp(BinOpKind::Sub, Box::new(x), Box::new(y)) }
84                        "-" _ v:@ { Expr::UnOp(UnOpKind::Neg, Box::new(v)) }
85                --
86                x:(@) _ "*" _ y:@ { Expr::BinOp(BinOpKind::Mul, Box::new(x), Box::new(y)) }
87                x:(@) _ "/" _ y:@ { Expr::BinOp(BinOpKind::Div, Box::new(x), Box::new(y)) }
88                x:(@) _ "%" _ y:@ { Expr::BinOp(BinOpKind::Mod, Box::new(x), Box::new(y)) }
89                --
90                x:@ "(" _ v:(expr() ** (_ "," _)) _ ")" { Expr::FunCall(Box::new(x), v) }
91                --
92                "(" _ v:expr() _ ")" { v }
93                n:literal() { Expr::Literal(n) }
94                v:variable_path() { Expr::Variable(v) }
95                v:tlm_ref() { Expr::TlmRef(v) }
96            }
97
98        pub(crate) rule numeric() -> Numeric
99            = quiet!{"0x" i:$(['a'..='f' | 'A'..='F' | '0'..='9' | '_']+)
100            { Numeric::Integer(i.to_owned(), IntegerPrefix::Hexadecimal) }
101            / "0o" i:$(['0'..='7' | '_']+)
102            { Numeric::Integer(i.to_owned(), IntegerPrefix::Octal) }
103            / "0b" i:$(['0' | '1' | '_']+)
104            { Numeric::Integer(i.to_owned(), IntegerPrefix::Binary) }
105            / i:$(['0'..='9']['0'..='9' | '_']*) !['.' | 'e' | 'E']
106            { Numeric::Integer(i.to_owned(), IntegerPrefix::Decimal) }
107            / f:$(['0'..='9']['0'..='9' | '_']*
108                "."? (['0'..='9']['0'..='9' | '_']*)?
109                (['e' | 'E']['+' | '-']['0'..='9' | '_']*)?
110            ) { Numeric::Float(f.to_owned()) }} / expected!("numeric")
111
112        rule hex_digit() -> u8
113            = quiet!{
114                s:$(['a'..='f' | 'A'..='F' | '0'..='9']) {?
115                    let c = s.chars().next().unwrap();
116                    match c {
117                        '0'..='9' => Ok(c as u8 - '0' as u8),
118                        'a'..='f' => Ok(c as u8 - 'a' as u8 + 10),
119                        'A'..='F' => Ok(c as u8 - 'A' as u8 + 10),
120                        _ => Err("invalid hex digit")
121                    }
122                }
123            } / expected!("hexadecimal digit")
124
125        rule hex_byte() -> u8
126            = quiet!{
127                high:(hex_digit()) low:(hex_digit()) {?
128                    Ok(high << 4 | low)
129                }
130            } / expected!("hexadecimal byte")
131
132        rule hex_bytes() -> Vec<u8>
133            = quiet!{ s:(hex_byte()*) }
134
135        rule numeric_suffix() -> NumericSuffix
136            = "s" ![c if c.is_alphanumeric()] { NumericSuffix::Second }
137
138        rule literal() -> Literal
139            = "[" _ v:(expr() ** (_ "," _)) _ "]" { Literal::Array(v) }
140            / "\"" s:$([c if c != '"']*) "\"" { Literal::String(s.to_owned()) }
141            / n:numeric() s:numeric_suffix()? { Literal::Numeric(n, s) }
142            / "time!" _ "(" _ content:$([c if c != ')']*) _ ")" {?
143                let datetime = chrono::DateTime::parse_from_rfc3339(content).map_err(|_| "valid datetime")?;
144                Ok(Literal::DateTime(datetime.into()))
145            }
146            / "tlmid!" _ "(" _ content:$([c if c != ')']*) _ ")" {?
147                Ok(Literal::TlmId(content.to_owned()))
148            }
149            / "hex_bytes!" _ "(" _ content:hex_bytes() _ ")" {?
150                Ok(Literal::Bytes(content))
151            }
152
153        rule compare_binop_kind() -> CompareBinOpKind
154            = ">=" { CompareBinOpKind::GreaterEq }
155            / "<=" { CompareBinOpKind::LessEq }
156            / ">" { CompareBinOpKind::Greater }
157            / "<" { CompareBinOpKind::Less }
158
159        rule eq_binop_kind() -> CompareBinOpKind
160            = "!=" { CompareBinOpKind::NotEqual }
161            / "==" { CompareBinOpKind::Equal }
162
163        pub(crate) rule call() -> Call
164            = "call" __ path:file_path()
165            { Call { path } }
166
167        pub(crate) rule wait() -> Wait
168            = "wait" __ condition:expr()
169            { Wait { condition } }
170
171        pub(crate) rule wait_inc() -> WaitInc
172            = "wait_inc" __ condition:expr()
173            { WaitInc { condition } }
174
175        pub(crate) rule assert() -> Assert
176            = "assert" __ condition:expr()
177            { Assert { condition } }
178
179        pub(crate) rule assert_eq() -> AssertEq
180            = "assert_eq" __ left:expr() _ right:expr()
181            { AssertEq { left, right, tolerance: None } }
182
183        pub(crate) rule assert_approx_eq() -> AssertEq
184            = "assert_approx_eq" __ left:expr() _ right:expr() _ tolerance:expr()
185            { AssertEq { left, right, tolerance: Some(tolerance) } }
186
187        pub(crate) rule let_bind() -> Let
188            = "let" __ raw:ident() _ "=" _ rhs:expr()
189            { Let { variable: Ident { raw: raw.to_owned()}, rhs } }
190
191        pub(crate) rule print() -> Print
192            = "print" __ arg:expr()
193            { Print { arg } }
194
195        pub(crate) rule return_() -> ()
196            = "return" { }
197
198        pub(crate) rule set() -> Set
199            = "set" __ name:variable_path() _ "=" _ expr:expr()
200            { Set { name, expr } }
201
202        rule reserved_control() -> SingleStatement
203            = call:call() { SingleStatement::Call(call) }
204            / wait:wait() { SingleStatement::Wait(wait) }
205            / assert:assert() { SingleStatement::Assert(assert) }
206            / assert_eq:assert_eq() { SingleStatement::AssertEq(assert_eq) }
207            / assert_eq:assert_approx_eq() { SingleStatement::AssertEq(assert_eq) }
208            / let_bind:let_bind() { SingleStatement::Let(let_bind) }
209            / print:print() { SingleStatement::Print(print) }
210            / _:return_() { SingleStatement::Return }
211            / set:set() { SingleStatement::Set(set) }
212            / command:command() { SingleStatement::Command(command) }
213
214        rule comment() -> Comment
215            = "#" _ s:$([c if c != '\n']*) { Comment(s.to_owned()) }
216
217        pub rule row_() -> Row
218            = _ breaks:"."? _ r:(
219                  content:reserved_control() _ comment_trailing:comment()?
220                    { Row { breaks, content: Some(content), comment_trailing } }
221                / comment_trailing:comment()?
222                    { Row { breaks, content: None, comment_trailing } }
223            ) { r }
224
225        pub rule row() -> SRow
226            = spanned(<row_()>)
227
228        rule block_delay() -> Expr
229            = "delay" _ "=" _ e:expr() { e }
230
231
232        rule row_line() -> SRow
233            = row:row() _ newline() { row }
234
235        pub rule block_() -> Block
236            = _ default_receiver_component:("@" r:receiver(){r})? _ delay:block_delay()?
237            _ "{" _ comment_first:comment()? newline()
238              rows:(row_line()*)
239            _ "}" _ comment_last:comment()?
240            { Block { default_receiver_component, delay, rows, comment_first, comment_last } }
241
242        pub rule block() -> SBlock
243            = spanned(<block_()>)
244
245        pub rule statement() -> Statement
246            = block:block() { Statement::Block(block) }
247            / row:row() { Statement::Single(row) }
248
249        pub rule statements() -> Vec<Statement>
250            = s:statement() ** (_ newline()) { s }
251
252        rule ws() = quiet!{[c if c.is_whitespace() && c != '\n']}
253        rule _() = ws()*
254        rule __() = ![c if c.is_alphanumeric()] _
255        rule newline() = "\n"
256
257
258        // cf. https://github.com/kevinmehall/rust-peg/issues/283#issuecomment-1014858352
259        rule spanned<T>(inner : rule<T>) -> Spanned<T>
260            = b:position!() value:inner() e:position!() { Spanned { span: b..e, value } }
261    }
262}
263
264#[derive(Debug, Error)]
265pub enum Error {
266    #[error("parse failed: {0}")]
267    ParseError(#[from] peg::error::ParseError<LineCol>),
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_numeric() {
276        let r = ops_parser::numeric("5e-3");
277        dbg!(r.unwrap());
278        let r = ops_parser::numeric("0");
279        dbg!(r.unwrap());
280        let r = ops_parser::numeric("10");
281        dbg!(r.unwrap());
282        let r = ops_parser::numeric("0.1");
283        dbg!(r.unwrap());
284    }
285    #[test]
286    fn test_expr() {
287        let r = ops_parser::expr("a + b * c");
288        dbg!(r.unwrap());
289        let r = ops_parser::expr("a * b + c");
290        dbg!(r.unwrap());
291        let r = ops_parser::expr("1 + (a.a * (0xB_C))");
292        dbg!(r.unwrap());
293        let r = ops_parser::expr("a((c), d) + b");
294        dbg!(r.unwrap());
295        let r = ops_parser::expr("ABC.DEF == 0xabcdef || 5s");
296        dbg!(r.unwrap());
297        let r = ops_parser::expr(r#"time!(2021-01-01T00:00:00.00Z)"#);
298        dbg!(r.unwrap());
299        let r = ops_parser::expr(r#"time!(2021-01-01T00:00:00.00+09:00)"#);
300        dbg!(r.unwrap());
301        let r = ops_parser::expr(r#"tlmid!(AB.CD.EF)"#);
302        dbg!(r.unwrap());
303        let r = ops_parser::expr(r#"hex_bytes!(0123456789abcdefABCDEF)"#);
304        dbg!(r.unwrap());
305    }
306
307    #[test]
308    fn test_space() {
309        fn differ<T: std::cmp::PartialEq + std::fmt::Debug, E: std::fmt::Debug>(
310            f: impl Fn(&'static str) -> Result<T, E>,
311            l: &'static str,
312            r: &'static str,
313        ) {
314            let l = f(l).unwrap();
315            let r = f(r).unwrap();
316            assert_ne!(l, r);
317        }
318        fn not_differ<T: std::cmp::PartialEq + std::fmt::Debug, E: std::fmt::Debug>(
319            f: impl Fn(&'static str) -> Result<T, E>,
320            l: &'static str,
321            r: &'static str,
322        ) {
323            let l = f(l).unwrap();
324            let r = f(r).unwrap();
325            assert_eq!(l, r);
326        }
327        differ(
328            ops_parser::expr,
329            "EXAMPLE.VARIABLE.NAME/s",
330            "EXAMPLE.VARIBLAE.NAME / s",
331        );
332        not_differ(
333            ops_parser::assert,
334            "assert x in [ 100, 200 ]",
335            "assert x in[ 100,200]",
336        );
337        not_differ(ops_parser::let_bind, "let variable=0", "let variable = 0");
338    }
339    #[test]
340    fn test_error_msg() {
341        let r = ops_parser::let_bind("let !");
342        assert!(r.unwrap_err().to_string().contains("ident"));
343    }
344    #[test]
345    fn test_fun_call() {
346        let r = ops_parser::let_bind(
347            "let result_of_test_fun = Test.fun2(Test.fun1([P.VEC.X, P.VEC.Y, P.VEC.Z]))",
348        );
349        dbg!(r.unwrap());
350    }
351    #[test]
352    fn test_call() {
353        let r = ops_parser::call("call OTHER_FILE.ops");
354        dbg!(r.unwrap());
355        let r = ops_parser::call("call ./OTHER_FILE.ops");
356        dbg!(r.unwrap());
357        let r = ops_parser::call("call ../.another_dir/../x.y.z");
358        dbg!(r.unwrap());
359        let r = ops_parser::call("call x/.../fail");
360        assert!(r.is_err());
361    }
362    #[test]
363    fn test_wait() {
364        let r = ops_parser::wait("wait 12s");
365        dbg!(r.unwrap());
366        let r = ops_parser::wait("wait 0.1s");
367        dbg!(r.unwrap());
368        let r = ops_parser::wait("wait 1 == 1");
369        dbg!(r.unwrap());
370        let r = ops_parser::wait("wait HEX.VALUE == 0x0123cdef || 5s");
371        dbg!(r.unwrap());
372        let r = ops_parser::wait("wait SOME.CONDITION.TO.BE.TESTED == FALSE");
373        dbg!(r.unwrap());
374    }
375    #[test]
376    fn test_assert() {
377        let r = ops_parser::assert("assert TEST.VAR1 < 0.05");
378        dbg!(r.unwrap());
379        let r = ops_parser::assert("assert TEST.VAR2.X  in [ 0.01, 0.04 ]");
380        dbg!(r.unwrap());
381        let r = ops_parser::assert(r#"assert TEST.VAR_3 == "OFF""#);
382        dbg!(r.unwrap());
383        let r = ops_parser::assert(r#"assert TEST.VAR_4.X in [ 0, 0.07 ] if TEST.Var5 == "OFF""#);
384        dbg!(r.unwrap());
385    }
386
387    #[test]
388    fn test_return() {
389        let r = ops_parser::return_("return");
390        dbg!(r.unwrap());
391        let r = ops_parser::return_("return 0");
392        assert!(r.is_err());
393    }
394    #[test]
395    fn test_let() {
396        let r = ops_parser::let_bind("let relative_x = relative_x + 9");
397        dbg!(r.unwrap());
398        let r = ops_parser::let_bind("let HYPHEN-IS_VALID = 1");
399        dbg!(r.unwrap());
400    }
401    #[test]
402    fn test_print() {
403        let r = ops_parser::print("print abc_xyz");
404        dbg!(r.unwrap());
405        let r = ops_parser::print("print $A.B");
406        dbg!(r.unwrap());
407    }
408    #[test]
409    fn test_set() {
410        let r = ops_parser::set("set A.B = 1");
411        dbg!(r.unwrap());
412    }
413    #[test]
414    fn test_command() {
415        let r = ops_parser::command("DO_IT");
416        dbg!(r.unwrap());
417        let r = ops_parser::command("@ABC.DEF DO_IT");
418        dbg!(r.unwrap());
419        let r = ops_parser::command("@ABC.DEF CMD_WITH_ARGS some_value 0xaa 0xbb 2");
420        dbg!(r.unwrap());
421        let r = ops_parser::command("@ABC.DEF @@G CMD_WITH_ARGS relative_x 0xcc 0xdd 0xee 0xff");
422        dbg!(r.unwrap());
423    }
424    #[test]
425    fn test_block() {
426        let r = ops_parser::block(
427            r#"{ #comment
428  let x = 2
429  . call OTHER_FILE.ops
430  . #hello
431}"#,
432        );
433        dbg!(r.unwrap());
434
435        let r = ops_parser::block(
436            r#"delay=1s {
437  let x = 2
438  @AB.CD @@GH DO_IT
439} #comment"#,
440        );
441        dbg!(r.unwrap());
442        let r = ops_parser::block(
443            r#"  @AB.CD delay=1s {
444    0: DO_IT
445    0 + 1: @@EF DO_IT 1 2 3
446    2: DO_IT x y z
447    @XY.ZW FOO_BAR tlmid!(AB.CD)
448    wait 1 == 1
449}"#,
450        );
451        dbg!(r.unwrap());
452    }
453
454    #[test]
455    fn test_file() {
456        let s = include_str!("../tests/test.ops");
457        let r = ops_parser::statements(s);
458        dbg!(r.unwrap());
459    }
460    #[test]
461    fn test_rows() {
462        let s = r#".# ****** #
463    .set X.Y=2
464    .let CURRENT_EXAMPLE_TLM_VALUE = FOO.BAR.EXAMPLE_TLM.VALUE
465    .@@DEF DO_IT
466     wait $FOO.BAR.EXAMPLE_TLM.VALUE > CURRENT_EXAMPLE_TLM_VALUE || 5s
467     let foobar = $FOO.BAR
468     wait foobar.EXAMPLE_TLM.VALUE > CURRENT_EXAMPLE_TLM_VALUE || 5s
469     return
470    "#;
471        for l in s.lines() {
472            let r = ops_parser::row(l);
473            dbg!(r.unwrap());
474        }
475    }
476}