From a447a56f49d6f3d07f40fa5475034fbff0e87f8c Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 28 Sep 2023 13:16:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0`break`=E5=92=8C`continue`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/README.md | 1 + examples/control_plus.mdtlbl | 92 ++++++ src/syntax.rs | 582 +++++++++++++++++++++++++++++++++-- src/syntax_def.lalrpop | 112 ++++++- syntax/vim/mdtlbl.snippets | 14 +- syntax/vim/mdtlbl.vim | 4 +- 6 files changed, 757 insertions(+), 48 deletions(-) create mode 100644 examples/control_plus.mdtlbl diff --git a/examples/README.md b/examples/README.md index 0cd874a..0625f49 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,6 +11,7 @@ > [`op.mdtlbl`](./op.mdtlbl)
> [`op_expr.mdtlbl`](./op_expr.mdtlbl)
> [`control.mdtlbl`](./control.mdtlbl)
+> [`control_plus.mdtlbl`](./control_plus.mdtlbl)
> [`cmps.mdtlbl`](./cmps.mdtlbl)
> [`insert_sort.mdtlbl`](./insert_sort.mdtlbl)
> [`switch.mdtlbl`](./switch.mdtlbl)
diff --git a/examples/control_plus.mdtlbl b/examples/control_plus.mdtlbl new file mode 100644 index 0000000..9a49947 --- /dev/null +++ b/examples/control_plus.mdtlbl @@ -0,0 +1,92 @@ +#** +* 这是0.11.7加入的功能, 可以在跳出或继续当前循环语句时不必编写标签 +* +* # 以下两种语法为 +* `"break" JumpCmp? LEnd` +* `"continue" JumpCmp? LEnd` +* +* # 示例 +* `break;` +* `break _;` +* `break a < b;` +* `continue a < b;` +* +* 目前被囊括到的语句有 +* - `while` +* - `gwhile` +* - `do_while` +* - `switch` +* - `select` +* +* 如果是在最顶层, 那么`break`与`continue`都指向末尾 +* +* `switch` & `select`的`continue`和`break`分别指向起始及末尾处 +* `while` & `gwhile` & `do_while`的`continue`和`break`指向末尾条件前后 +* +* +* 需要注意的是 +* `while` & `gwhile` & `do_while` 的条件部分, +* `switch` & `select` 的值部分 +* 都不在一个新的控制域中, +* 也就是说不能例如`while`的条件跑了一半便直接中断或重新计算此循环. +* 如果有此方面需要, 请用回`goto` +*# + + +i = 0; +while i < 64 { + read num cell1 i; + op i i + 1; + continue num == 0; + break num > 0xFF; + print i ": " num "\n"; +} +printflush message1; +#* >>> +set i 0 +jump 11 greaterThanEq i 64 +read num cell1 i +op add i i 1 +jump 10 equal num 0 +jump 11 greaterThan num 0xFF +print i +print ": " +print num +print "\n" +jump 2 lessThan i 64 +printflush message1 +*# +# 示例1 + + +i = 0; +while i < 64 { + read num cell1 i; + op i i + 1; + skip num == 0 { + goto :bk1 num > 0xFF; + print i ": " num "\n"; + } +} :bk1 +printflush message1; +#* >>> +set i 0 +jump 11 greaterThanEq i 64 +read num cell1 i +op add i i 1 +jump 10 equal num 0 +jump 11 greaterThan num 0xFF +print i +print ": " +print num +print "\n" +jump 2 lessThan i 64 +printflush message1 +*# +# 示例2 + + +# 从以上两个示例中可以看出 +# 有了break和continue的示例1相比示例2有效减少了代码嵌套与控制结构中的命名标记 +# 虽然如果乱用的话会丢失明确嵌套流控制结构使控制流混乱 +# 不过至少相较于直接编写goto, 它不用去命名标记且语义更清晰 diff --git a/src/syntax.rs b/src/syntax.rs index 417adc0..fedac16 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -441,6 +441,8 @@ pub struct Meta { tag_number: usize, /// 被跳转的label defined_labels: Vec>, + break_labels: Vec>, + continue_labels: Vec>, } impl Default for Meta { fn default() -> Self { @@ -448,6 +450,8 @@ impl Default for Meta { tmp_var_count: 0, tag_number: 0, defined_labels: vec![Vec::new()], + break_labels: Vec::new(), + continue_labels: Vec::new(), } } } @@ -493,6 +497,71 @@ impl Meta { self.defined_labels.pop().unwrap() } + /// 添加一层用于`break`和`continue`的未使用控制层 + /// + /// 需要在结构结束时将其销毁 + pub fn add_control_level( + &mut self, r#break: Option, + r#continue: Option, + ) { + self.break_labels.push(r#break); + self.continue_labels.push(r#continue); + } + + /// 将`break`和`continue`的标签返回 + /// + /// 如果未使用那么返回的会为空 + pub fn pop_control_level(&mut self) -> (Option, Option) { + ( + self.break_labels.pop().unwrap(), + self.continue_labels.pop().unwrap(), + ) + } + + /// 返回`break`的目标标签, 这会执行惰性初始化 + pub fn get_break(&mut self) -> &Var { + // 由于设计上的懒惰与所有权系统的缺陷冲突, 所以这里的代码会略繁琐 + let new_lab + = if self.break_labels.last().unwrap().is_none() { + self.get_tag().into() + } else { + None + }; + let label = self.break_labels.last_mut().unwrap(); + if let Some(new_lab) = new_lab { + assert!(label.is_none()); + *label = Some(new_lab) + } + label.as_ref().unwrap() + } + + /// 返回`continue`的目标标签, 这会执行惰性初始化 + pub fn get_continue(&mut self) -> &Var { + // 由于设计上的懒惰与所有权系统的缺陷冲突, 所以这里的代码会略繁琐 + let new_lab + = if self.continue_labels.last().unwrap().is_none() { + self.get_tag().into() + } else { + None + }; + let label = self.continue_labels.last_mut().unwrap(); + if let Some(new_lab) = new_lab { + assert!(label.is_none()); + *label = Some(new_lab) + } + label.as_ref().unwrap() + } + + pub fn push_some_label_to( + &mut self, + lines: &mut Vec, + label: Option, + ) { + if let Some(label) = label { + lines.push(LogicLine::new_label(label, self)) + } + } + /// 构建一个`sets`, 例如`a b c = 1 2 3;` /// 如果只有一个值与被赋值则与之前行为一致 /// 如果值与被赋值数量不匹配则返回错误 @@ -631,37 +700,37 @@ impl JumpCmp { /// 获取两个运算成员, 如果是没有运算成员的则返回[`Default`] pub fn get_values(self) -> (Value, Value) { match self { - Self::Equal(a, b) - | Self::NotEqual(a, b) - | Self::LessThan(a, b) - | Self::LessThanEq(a, b) - | Self::GreaterThan(a, b) - | Self::StrictEqual(a, b) - | Self::GreaterThanEq(a, b) - | Self::StrictNotEqual(a, b) - => (a, b), - Self::Always - | Self::NotAlways - // 这里使用default生成无副作用的占位值 - => Default::default(), + | Self::Equal(a, b) + | Self::NotEqual(a, b) + | Self::LessThan(a, b) + | Self::LessThanEq(a, b) + | Self::GreaterThan(a, b) + | Self::StrictEqual(a, b) + | Self::GreaterThanEq(a, b) + | Self::StrictNotEqual(a, b) + => (a, b), + | Self::Always + | Self::NotAlways + // 这里使用default生成无副作用的占位值 + => Default::default(), } } /// 获取两个运算成员, 如果是没有运算成员的则返回空 pub fn get_values_ref(&self) -> Option<(&Value, &Value)> { match self { - Self::Equal(a, b) - | Self::NotEqual(a, b) - | Self::LessThan(a, b) - | Self::LessThanEq(a, b) - | Self::GreaterThan(a, b) - | Self::StrictEqual(a, b) - | Self::GreaterThanEq(a, b) - | Self::StrictNotEqual(a, b) - => Some((a, b)), - Self::Always - | Self::NotAlways - => None, + | Self::Equal(a, b) + | Self::NotEqual(a, b) + | Self::LessThan(a, b) + | Self::LessThanEq(a, b) + | Self::GreaterThan(a, b) + | Self::StrictEqual(a, b) + | Self::GreaterThanEq(a, b) + | Self::StrictNotEqual(a, b) + => Some((a, b)), + | Self::Always + | Self::NotAlways + => None, } } @@ -4901,4 +4970,467 @@ mod tests { ); } + + #[test] + fn top_level_break_and_continue_test() { + let parser = TopLevelParser::new(); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 always 0 0", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue _; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 always 0 0", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue a < b; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 lessThan a b", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue a < b || c < d; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 lessThan a b", + "jump 0 lessThan c d", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 always 0 0", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue _; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 always 0 0", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue a < b; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 lessThan a b", + "bar", + ] + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + foo; + continue a < b || c < d; + bar; + "#).unwrap()).compile().unwrap(), + [ + "foo", + "jump 0 lessThan a b", + "jump 0 lessThan c d", + "bar", + ] + ); + + } + + #[test] + fn control_stmt_break_and_continue_test() { + let parser = TopLevelParser::new(); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + while a < b { + foo1; + while c < d { + foo2; + break; + } + bar1; + break; + } + bar; + break; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "jump 10 greaterThanEq a b", + "foo1", + "jump 7 greaterThanEq c d", + "foo2", + "jump 7 always 0 0", + "jump 4 lessThan c d", + "bar1", + "jump 10 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + gwhile a < b { + foo1; + gwhile c < d { + foo2; + break; + } + bar1; + break; + } + bar; + break; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "jump 9 always 0 0", + "foo1", + "jump 6 always 0 0", + "foo2", + "jump 7 always 0 0", + "jump 4 lessThan c d", + "bar1", + "jump 10 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + xxx; + do { + foo1; + xxx; + do { + foo2; + break; + } while c < d; + bar1; + break; + } while a < b; + bar; + break; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "xxx", + "foo1", + "xxx", + "foo2", + "jump 7 always 0 0", + "jump 4 lessThan c d", + "bar1", + "jump 10 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + switch a { + case 0: foo; + case 1: break; + case 2: bar; + } + end; + break; + "#).unwrap())).compile().unwrap(), + [ + "op add @counter @counter a", + "foo", + "jump 4 always 0 0", + "bar", + "end", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + select a { + foo; + break; + bar; + } + end; + break; + "#).unwrap())).compile().unwrap(), + [ + "op add @counter @counter a", + "foo", + "jump 4 always 0 0", + "bar", + "end", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + while a < b { + foo1; + while c < d { + foo2; + continue; + } + bar1; + continue; + } + bar; + continue; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "jump 10 greaterThanEq a b", + "foo1", + "jump 7 greaterThanEq c d", + "foo2", + "jump 6 always 0 0", + "jump 4 lessThan c d", + "bar1", + "jump 9 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + while a < b { + foo1; + while c < d { + continue; + foo2; + } + bar1; + continue; + } + bar; + continue; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "jump 10 greaterThanEq a b", + "foo1", + "jump 7 greaterThanEq c d", + "jump 6 always 0 0", + "foo2", + "jump 6 lessThan c d", // 4 -> 6 + "bar1", + "jump 9 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + gwhile a < b { + foo1; + gwhile c < d { + foo2; + continue; + } + bar1; + continue; + } + bar; + continue; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "jump 9 always 0 0", + "foo1", + "jump 6 always 0 0", + "foo2", + "jump 6 always 0 0", + "jump 4 lessThan c d", + "bar1", + "jump 9 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + foo; + xxx; + do { + foo1; + xxx; + do { + foo2; + continue; + } while c < d; + bar1; + continue; + } while a < b; + bar; + continue; + "#).unwrap())).compile().unwrap(), + [ + "foo", + "xxx", + "foo1", + "xxx", + "foo2", + "jump 6 always 0 0", + "jump 4 lessThan c d", + "bar1", + "jump 9 always 0 0", + "jump 2 lessThan a b", + "bar", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + switch a { + case 0: foo; + case 1: continue; + case 2: bar; + } + end; + continue; + "#).unwrap())).compile().unwrap(), + [ + "op add @counter @counter a", + "foo", + "jump 0 always 0 0", + "bar", + "end", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + select a { + foo; + continue; + bar; + } + end; + continue; + "#).unwrap())).compile().unwrap(), + [ + "op add @counter @counter a", + "foo", + "jump 0 always 0 0", + "bar", + "end", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + end; + switch a { + case 0: foo; + case 1: continue; + case 2: bar; + } + end; + continue; + "#).unwrap())).compile().unwrap(), + [ + "end", + "op add @counter @counter a", + "foo", + "jump 1 always 0 0", + "bar", + "end", + "jump 0 always 0 0", + ] + ); + + assert_eq!( + dbg!(CompileMeta::new().compile(parse!(parser, r#" + end; + select a { + foo; + continue; + bar; + } + end; + continue; + "#).unwrap())).compile().unwrap(), + [ + "end", + "op add @counter @counter a", + "foo", + "jump 1 always 0 0", + "bar", + "end", + "jump 0 always 0 0", + ] + ); + + } } diff --git a/src/syntax_def.lalrpop b/src/syntax_def.lalrpop index 134706a..73a2094 100644 --- a/src/syntax_def.lalrpop +++ b/src/syntax_def.lalrpop @@ -1,4 +1,5 @@ // this is a lalrpop file + #![allow(clippy::just_underscores_and_digits)] #![allow(clippy::needless_lifetimes)] #![allow(clippy::deprecated_cfg_attr)] @@ -68,7 +69,15 @@ OOCArgs = { } Span = @L T @R; -pub TopLevel: Expand = Expand; +CtrlStart: () = () => meta.add_control_level(None, None); +CtrlStop: (Option, Option) = () => meta.pop_control_level(); + +pub TopLevel: Expand = CtrlStart => { + let (break_lab, continue_lab) = ctrl; + meta.push_some_label_to(&mut lines, break_lab); + meta.push_some_label_to(&mut lines, continue_lab); + lines +}; pub String: Var = r#""[^"]*""# => <>.lines().collect::>().join("\\n"); pub Ident: Var = r"[_\p{XID_Start}]\p{XID_Continue}*" => <>.into(); @@ -515,6 +524,20 @@ SwitchCatchFlag: SwitchCatch = { pub Control: LogicLine = { "goto"