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