From 4dfadc2ee515826dd7ea82705dda2b3105bc5b44 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Mon, 31 Jul 2023 15:41:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9D=A1=E4=BB=B6=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F(=E4=B8=8E=E6=88=96=E9=9D=9E)=E7=9A=84?= =?UTF-8?q?=E7=BC=96=E5=86=99,=20=E5=B9=B6=E4=B8=94=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E8=BF=87=E7=A8=8B=E4=B8=AD=E7=94=B3=E8=AF=B7?= =?UTF-8?q?=E7=9A=84=E4=B8=B4=E6=97=B6=E6=A0=87=E8=AE=B0=E4=B8=8E=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E8=BF=87=E7=A8=8B=E4=B8=AD=E7=94=B3=E8=AF=B7=E7=9A=84?= =?UTF-8?q?=E4=B8=B4=E6=97=B6=E6=A0=87=E8=AE=B0=E9=87=8D=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/cmps.mdtlbl | 24 +++ src/syntax.rs | 462 ++++++++++++++++++++++++++++++++++++----- src/syntax_def.lalrpop | 28 ++- 5 files changed, 462 insertions(+), 56 deletions(-) create mode 100644 examples/cmps.mdtlbl diff --git a/Cargo.lock b/Cargo.lock index 8656bd0..69b2910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mindustry_logic_bang_lang" -version = "0.6.2" +version = "0.7.0" dependencies = [ "lalrpop", "lalrpop-util", diff --git a/Cargo.toml b/Cargo.toml index 8e691b9..fddc6d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mindustry_logic_bang_lang" -version = "0.6.2" +version = "0.7.0" edition = "2021" authors = ["A4-Tacks "] diff --git a/examples/cmps.mdtlbl b/examples/cmps.mdtlbl new file mode 100644 index 0000000..b4e991b --- /dev/null +++ b/examples/cmps.mdtlbl @@ -0,0 +1,24 @@ +#* 这里是介绍0.7.0版本加入的重要语法 + cmps, 这个语法内部依照以前的命名JumpCmp, 然后被连接到CmpTree, + 是可以通过短路与, 短路或, 单目非将多个条件(JumpCmpBody)组织起来的语法 + 这可以使得我们写多条件运算变得很容易, + 无需像以前一样插入DExp并且不满足条件跳走来实现 + 短路与的符号为`&&`, 短路或的符号为`||`, 单目非的符号为`!` + 注意, 优先级严格递增为`||` < `&&` < `!` + 也就是说`a && b || !c && d`被加上等价的括号后为`(a && b) || ((!c) && d)` +*# + +do { + op i i + 1; +} while i < 10 && (read $ cell1 i;) != 0; +end; + +#* >>> +op add i i 1 +jump 4 greaterThanEq i 10 +read __0 cell1 i +jump 0 notEqual __0 0 +end +*# +# 可以看到, 我们的多条件正确的生成了. +# 我们可以在while skip goto do_while 等地方编写它 diff --git a/src/syntax.rs b/src/syntax.rs index d3ca780..c1969b6 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -226,8 +226,6 @@ impl DExp { #[derive(Debug)] pub struct Meta { tag_number: usize, - id: usize, - tmp_var: usize, /// 被跳转的label defined_labels: Vec>, } @@ -235,8 +233,6 @@ impl Default for Meta { fn default() -> Self { Self { tag_number: 0, - id: 0, - tmp_var: 0, defined_labels: vec![Vec::new()], } } @@ -251,21 +247,7 @@ impl Meta { pub fn get_tag(&mut self) -> String { let tag = self.tag_number; self.tag_number += 1; - format!("__{}", tag) - } - - /// 获取一个临时变量, 不会重复获取 - pub fn get_tmp_var(&mut self) -> Var { - let id = self.tmp_var; - self.tmp_var += 1; - format!("__{}", id) - } - - /// 获取一个始终不会重复获取的id - pub fn get_id(&mut self) -> usize { - let id = self.id; - self.id += 1; - id + format!("___{}", tag) } /// 添加一个被跳转的label到当前作用域 @@ -427,6 +409,73 @@ impl JumpCmp { } } +/// 一颗比较树, +/// 用于多条件判断. +/// 例如: `a < b && c < d || e == f` +#[derive(Debug, PartialEq, Clone)] +pub enum CmpTree { + And(Box, Box), + Or(Box, Box), + Atom(JumpCmp), +} +impl CmpTree { + /// 反转自身条件, + /// 使用`德•摩根定律`进行表达式变换. + /// + /// 即`!(a&&b) == (!a)||(!b)`和`!(a||b) == (!a)&&(!b)` + /// + /// 例如表达式`(a && b) || c`进行反转变换 + /// 1. `!((a && b) || c)` + /// 2. `!(a && b) && !c` + /// 3. `(!a || !b) && !c` + pub fn reverse(self) -> Self { + match self { + Self::Or(a, b) + => Self::And(a.reverse().into(), b.reverse().into()), + Self::And(a, b) + => Self::Or(a.reverse().into(), b.reverse().into()), + Self::Atom(cmp) + => Self::Atom(cmp.reverse()), + } + } + + /// TODO + /// 构建条件树为goto + pub fn build(self, meta: &mut CompileMeta, mut do_tag: Var) { + use CmpTree::*; + + // 获取如果在常量展开内则被重命名后的标签 + do_tag = meta.get_in_const_label(do_tag); + + match self { + Or(a, b) => { + a.build(meta, do_tag.clone()); + b.build(meta, do_tag); + }, + And(a, b) => { + let end = meta.get_tmp_tag(); + a.reverse().build(meta, end.clone()); + b.build(meta, do_tag); + let tag_id = meta.get_tag(end); + meta.push(TagLine::TagDown(tag_id)); + }, + Atom(cmp) => { + let cmp_str = cmp.cmp_str(); + let (a, b) = cmp.build_value(meta); + + let jump = Jump( + meta.get_tag(do_tag).into(), + format!("{} {} {}", cmp_str, a, b) + ); + meta.push(jump.into()) + }, + } + } +} +impl_enum_froms!(impl From for CmpTree { + Atom => JumpCmp; +}); + #[derive(Debug, PartialEq, Clone)] pub enum Op { Add(Value, Value, Value), @@ -578,18 +627,10 @@ impl Compile for Op { } #[derive(Debug, PartialEq, Clone)] -pub struct Goto(pub Var, pub JumpCmp); +pub struct Goto(pub Var, pub CmpTree); impl Compile for Goto { - fn compile(mut self, meta: &mut CompileMeta) { - // 如果在常量替换内, 这将被重命名 - self.0 = meta.get_in_const_label(self.0); - let cmp_str = self.1.cmp_str(); - let (a, b) = self.1.build_value(meta); - let jump = Jump( - meta.get_tag(self.0).into(), - format!("{} {} {}", cmp_str, a, b) - ); - meta.push(TagLine::Jump(jump.into())) + fn compile(self, meta: &mut CompileMeta) { + self.1.build(meta, self.0) } } @@ -1248,7 +1289,7 @@ mod tests { fn goto_test() { let parser = ExpandParser::new(); assert_eq!(parse!(parser, "goto :a 1 <= 2; :a").unwrap(), vec![ - Goto("a".into(), JumpCmp::LessThanEq("1".into(), "2".into())).into(), + Goto("a".into(), JumpCmp::LessThanEq("1".into(), "2".into()).into()).into(), LogicLine::Label("a".into()), ].into()); } @@ -1259,9 +1300,9 @@ mod tests { assert_eq!( parse!(parser, r#"skip 1 < 2 print "hello";"#).unwrap(), Expand(vec![ - Goto("__0".into(), JumpCmp::LessThan("1".into(), "2".into())).into(), + Goto("___0".into(), JumpCmp::LessThan("1".into(), "2".into()).into()).into(), LogicLine::Other(vec!["print".into(), r#""hello""#.into()]), - LogicLine::Label("__0".into()), + LogicLine::Label("___0".into()), ]).into() ); @@ -1277,23 +1318,23 @@ mod tests { "#).unwrap(), parse!(parser, r#" { - goto :__1 2 < 3; - goto :__2 3 < 4; - goto :__3 4 < 5; + goto :___1 2 < 3; + goto :___2 3 < 4; + goto :___3 4 < 5; print 4; - goto :__0 _; - :__2 { + goto :___0 _; + :___2 { print 2; } - goto :__0 _; - :__3 { + goto :___0 _; + :___3 { print 3; } - goto :__0 _; - :__1 { + goto :___0 _; + :___1 { print 1; } - :__0 + :___0 } "#).unwrap(), ); @@ -1305,11 +1346,11 @@ mod tests { "#).unwrap(), parse!(parser, r#" { - goto :__0 a >= b; - :__1 + goto :___0 a >= b; + :___1 print 3; - goto :__1 a < b; - :__0 + goto :___1 a < b; + :___0 } "#).unwrap(), ); @@ -1322,10 +1363,10 @@ mod tests { "#).unwrap(), parse!(parser, r#" { - :__0 { + :___0 { print 1; } - goto :__0 a < b; + goto :___0 a < b; } "#).unwrap(), ); @@ -2180,4 +2221,327 @@ mod tests { "print C", ]); } + + #[test] + fn cmptree_test() { + let parser = ExpandParser::new(); + + let ast = parse!(parser, r#" + goto :end a && b && c; + foo; + :end + end; + "#).unwrap(); + assert_eq!( + ast[0].as_goto().unwrap().1, + CmpTree::And( + CmpTree::And( + Box::new(JumpCmp::NotEqual("a".into(), "false".into()).into()), + Box::new(JumpCmp::NotEqual("b".into(), "false".into()).into()), + ).into(), + Box::new(JumpCmp::NotEqual("c".into(), "false".into()).into()), + ).into() + ); + + let ast = parse!(parser, r#" + goto :end a || b || c; + foo; + :end + end; + "#).unwrap(); + assert_eq!( + ast[0].as_goto().unwrap().1, + CmpTree::Or( + CmpTree::Or( + Box::new(JumpCmp::NotEqual("a".into(), "false".into()).into()), + Box::new(JumpCmp::NotEqual("b".into(), "false".into()).into()), + ).into(), + Box::new(JumpCmp::NotEqual("c".into(), "false".into()).into()), + ).into() + ); + + let ast = parse!(parser, r#" + goto :end a && b || c && d; + foo; + :end + end; + "#).unwrap(); + assert_eq!( + ast[0].as_goto().unwrap().1, + CmpTree::Or( + CmpTree::And( + Box::new(JumpCmp::NotEqual("a".into(), "false".into()).into()), + Box::new(JumpCmp::NotEqual("b".into(), "false".into()).into()), + ).into(), + CmpTree::And( + Box::new(JumpCmp::NotEqual("c".into(), "false".into()).into()), + Box::new(JumpCmp::NotEqual("d".into(), "false".into()).into()), + ).into(), + ).into() + ); + + let ast = parse!(parser, r#" + goto :end a && (b || c) && d; + foo; + :end + end; + "#).unwrap(); + assert_eq!( + ast[0].as_goto().unwrap().1, + CmpTree::And( + CmpTree::And( + Box::new(JumpCmp::NotEqual("a".into(), "false".into()).into()), + CmpTree::Or( + Box::new(JumpCmp::NotEqual("b".into(), "false".into()).into()), + Box::new(JumpCmp::NotEqual("c".into(), "false".into()).into()), + ).into(), + ).into(), + Box::new(JumpCmp::NotEqual("d".into(), "false".into()).into()), + ).into() + ); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end a && b; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 equal a false", + "jump 3 notEqual b false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a || b) && c; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 notEqual a false", + "jump 3 equal b false", + "jump 4 notEqual c false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a || b) && (c || d); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 notEqual a false", + "jump 4 equal b false", + "jump 5 notEqual c false", + "jump 5 notEqual d false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end a || b || c || d || e; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 6 notEqual a false", + "jump 6 notEqual b false", + "jump 6 notEqual c false", + "jump 6 notEqual d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end a && b && c && d && e; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 5 equal a false", + "jump 5 equal b false", + "jump 5 equal c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a && b && c) && d && e; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 5 equal a false", + "jump 5 equal b false", + "jump 5 equal c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end a && b && (c && d && e); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 5 equal a false", + "jump 5 equal b false", + "jump 5 equal c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end a && (op $ b && c;); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 3 equal a false", + "op land __0 b c", + "jump 4 notEqual __0 false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end a && b || c && d; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 equal a false", + "jump 5 notEqual b false", + "jump 4 equal c false", + "jump 5 notEqual d false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end !a && b || c && d; + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 notEqual a false", + "jump 5 notEqual b false", + "jump 4 equal c false", + "jump 5 notEqual d false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a && b) || !(c && d); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 equal a false", + "jump 5 notEqual b false", + "jump 5 equal c false", + "jump 5 equal d false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a && b && c) || (d && e); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 3 equal a false", + "jump 3 equal b false", + "jump 6 notEqual c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a && b || c) || (d && e); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 equal a false", + "jump 6 notEqual b false", + "jump 6 notEqual c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end ((a && b) || c) || (d && e); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 2 equal a false", + "jump 6 notEqual b false", + "jump 6 notEqual c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (a && (b || c)) || (d && e); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "jump 3 equal a false", + "jump 6 notEqual b false", + "jump 6 notEqual c false", + "jump 5 equal d false", + "jump 6 notEqual e false", + "foo", + "end", + ]); + + let logic_lines = CompileMeta::new().compile(parse!(parser, r#" + goto :end (op $ a + 2;) && (op $ b + 2;); + foo; + :end + end; + "#).unwrap()).compile().unwrap(); + assert_eq!(logic_lines, vec![ + "op add __0 a 2", + "jump 4 equal __0 false", + "op add __1 b 2", + "jump 5 notEqual __1 false", + "foo", + "end", + ]); + } } diff --git a/src/syntax_def.lalrpop b/src/syntax_def.lalrpop index 3c6fb88..8aa5000 100644 --- a/src/syntax_def.lalrpop +++ b/src/syntax_def.lalrpop @@ -11,6 +11,7 @@ use crate::syntax::{ Var, DExp, JumpCmp, + CmpTree, Goto, Expand, Select, @@ -110,9 +111,26 @@ JumpCmpBody: JumpCmp = { => JumpCmp::bool(<>), } -pub JumpCmp: JumpCmp = { - JumpCmpBody, - Or<"lnot", "!"> => <>.reverse(), +pub JumpCmp: CmpTree = CmpTree1; + +pub CmpTree1: CmpTree = { + "||" => CmpTree::Or(Box::new(a.into()), b.into()), + CmpTree2, +} + +pub CmpTree2: CmpTree = { + "&&" => CmpTree::And(Box::new(a.into()), b.into()), + CmpTree3, +} + +pub CmpTree3: CmpTree = { + Or<"lnot", "!"> => <>.reverse(), + CmpTree, +} + +pub CmpTree: CmpTree = { + JumpCmpBody => <>.into(), + MTuple, } pub Op: Op = { @@ -355,13 +373,13 @@ pub Control: LogicLine = { res.push(Goto(tag.clone(), cmp).into()); // true jump } else_body.map(|body| res.push(body)); - res.push(Goto(end.clone(), JumpCmp::Always).into()); // jump always to end + res.push(Goto(end.clone(), JumpCmp::Always.into()).into()); // jump always to end // elif body for (tag, body) in elif_tags.into_iter().zip(elif_bodys) { res.push(LogicLine::new_label(tag, meta)); res.push(body); - res.push(Goto(end.clone(), JumpCmp::Always).into()); // jump always to end + res.push(Goto(end.clone(), JumpCmp::Always.into()).into()); // jump always to end } // true body