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}