diff --git a/Cargo.lock b/Cargo.lock index 9b5dd92..3401960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ dependencies = [ [[package]] name = "display_source" -version = "0.3.27" +version = "0.3.28" dependencies = [ "either", "parser", @@ -285,7 +285,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mindustry_logic_bang_lang" -version = "0.17.24" +version = "0.18.0" dependencies = [ "display_source", "logic_lint", @@ -331,7 +331,7 @@ dependencies = [ [[package]] name = "parser" -version = "0.3.28" +version = "0.3.29" dependencies = [ "lalrpop", "lalrpop-util", @@ -342,7 +342,7 @@ dependencies = [ [[package]] name = "parser-tests" -version = "0.1.49" +version = "0.1.50" dependencies = [ "either", "parser", @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "syntax" -version = "0.2.53" +version = "0.2.54" dependencies = [ "either", "itermaps", diff --git a/Cargo.toml b/Cargo.toml index 8c77538..57b3a84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mindustry_logic_bang_lang" -version = "0.17.24" +version = "0.18.0" edition = "2021" authors = ["A4-Tacks "] diff --git a/examples/builtin_functions.mdtlbl b/examples/builtin_functions.mdtlbl index ac02f9c..df9c009 100644 --- a/examples/builtin_functions.mdtlbl +++ b/examples/builtin_functions.mdtlbl @@ -26,6 +26,9 @@ * * `MetaDebug[]`: 以调试形式输出编译时元数据 * * `MaxExpandDepth[]`: 获取最大展开层数限制 * * `SetMaxExpandDepth[depth]`: 设置最大展开层数限制 +* * `RepeatLimit[]`: 获取最大重复块重复次数限制 +* * `SetRepeatLimit[limit]`: 设置最大重复块重复次数限制 +* * `StopRepeat[]`: 最里层的重复块在本次重复结束后将停止 * * `ExpandStack[]`: 调试输出当前展开栈情况 * * `EvalNum[num]`: 尝试常量计算一个值, 失败则返回`__` * * `IsString[str]`: 返回给定量是否是一个字符串 diff --git a/examples/learn.md b/examples/learn.md index 7ad6bb0..9e5ff17 100644 --- a/examples/learn.md +++ b/examples/learn.md @@ -1262,6 +1262,10 @@ print 1 从编译结果可以看出它的工作原理, `@`符号前面写数量, 数量不足时**依旧会运行**, 如果数量省略不写则默认为`1` +> [!WARNING] +> 重复块的重复次数可以为 0, 那么它将无限的重复, +> 此时需要使用内建函数 StopRepeat 来终止下一轮重复 + 通常重复块也配合着 match 一起使用, 来取出参数内容, 或者仅关心参数足够时的情况 > [!TIP] diff --git a/examples/match.mdtlbl b/examples/match.mdtlbl index c8faef5..76228aa 100644 --- a/examples/match.mdtlbl +++ b/examples/match.mdtlbl @@ -411,3 +411,24 @@ inline*C@ { foo a b foo c *# + + +# 在0.18.0版本的重复块添加了可以无限重复的功能, 只要在重复次数写0(以前会报错) +# 终止的方式则是使用内建函数 StopRepeat, 调用了它后将不会再次运行当前重复块 +# 同时增加了重复次数上限, 防止卡死, 也可以使用内建函数调整上限 +take*I = 0; +inline 0@ { + # 考虑到 StopRepeat 不是立即跳出, 而是执行完当前重复不再重复 + # 所以match一下, 省得多执行一次 + match I { [5] { Builtin.StopRepeat!; } _ { + print I; + take*I = I + 1; + } } +} +#* >>> +print 0 +print 1 +print 2 +print 3 +print 4 +*# diff --git a/syntax/vim/UltiSnips/mdtlbl.snippets b/syntax/vim/UltiSnips/mdtlbl.snippets index 366e057..34fc89d 100644 --- a/syntax/vim/UltiSnips/mdtlbl.snippets +++ b/syntax/vim/UltiSnips/mdtlbl.snippets @@ -158,6 +158,9 @@ endsnippet snippet _MaxExpandDepth "Builtin.MaxExpandDepth[]" w Builtin.MaxExpandDepth[] endsnippet +snippet _RepeatLimit "Builtin.RepeatLimit[]" w +Builtin.RepeatLimit[] +endsnippet snippet _EvalNum "Builtin.EvalNum[$1]" w Builtin.EvalNum[$1] endsnippet @@ -196,6 +199,9 @@ endsnippet snippet _SetMaxExpandDepth "Builtin.SetMaxExpandDepth! $1;" w Builtin.SetMaxExpandDepth! $1; endsnippet +snippet _SetRepeatLimit "Builtin.SetRepeatLimit! $1;" w +Builtin.SetRepeatLimit! $1; +endsnippet snippet _ExpandStack "Builtin.ExpandStack!;" w Builtin.ExpandStack!; endsnippet @@ -211,6 +217,9 @@ endsnippet snippet _SetNoOp "Builtin.SetNoOp! $1;" w Builtin.SetNoOp! $1; endsnippet +snippet _StopRepeat "Builtin.StopRepeat! $1;" w +Builtin.StopRepeat!; +endsnippet # logic snippet read "read result cell1 0;" w diff --git a/tools/display_source/Cargo.toml b/tools/display_source/Cargo.toml index 76a1aa1..26ac47f 100644 --- a/tools/display_source/Cargo.toml +++ b/tools/display_source/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "display_source" -version = "0.3.27" +version = "0.3.28" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/display_source/src/impls.rs b/tools/display_source/src/impls.rs index 3d8c770..d8f7cee 100644 --- a/tools/display_source/src/impls.rs +++ b/tools/display_source/src/impls.rs @@ -553,7 +553,7 @@ impl DisplaySource for Args { impl DisplaySource for ArgsRepeat { fn display_source(&self, meta: &mut DisplaySourceMeta) { meta.push("inline"); - match self.count() { + match &**self.count() { Left(n) => { meta.add_space(); meta.push_fmt(n); diff --git a/tools/parser/Cargo.toml b/tools/parser/Cargo.toml index c9378a2..49bb256 100644 --- a/tools/parser/Cargo.toml +++ b/tools/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parser" -version = "0.3.28" +version = "0.3.29" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/parser/src/parser.lalrpop b/tools/parser/src/parser.lalrpop index 11eb587..5dcf2e3 100644 --- a/tools/parser/src/parser.lalrpop +++ b/tools/parser/src/parser.lalrpop @@ -93,7 +93,6 @@ OOCArgs: Vec = { OpenArgs, CloseArgs, } -Span = @L T @R; #[inline] Opt: bool = T? => <>.is_some(); #[inline] @@ -519,28 +518,24 @@ ParamArgs: Args = { }, } ArgsRepeatBlock: ArgsRepeat = { - - "@" =>? { - if chunk == Some(0) { - return Err(Error::from((l, Errors::ArgsRepeatChunkByZero, r)).into()) - } - Ok(ArgsRepeat::new(chunk.unwrap_or(1), block)) + > "@" =>? { + Ok(ArgsRepeat::new(chunk.map(|n| n.unwrap_or(1)), block)) }, "*" > "@" => { ArgsRepeat::new_valued(value, block) }, - > => { - let glob = l.new_value(Args::GLOB_ONLY); - ArgsRepeat::new(pats.len(), vec![ + > > => { + let glob = pats.new_value(Args::GLOB_ONLY); + ArgsRepeat::new(l.new_value(pats.len()), vec![ ConstMatch::new(glob, vec![ - (pats.into(), block), + (pats.value.into(), block), ]).into() ].into()) }, } -Print: LogicLine = "print" LEnd => { - let Some(args) = <> else { +Print: LogicLine = > LEnd => { + let Some(args) = args else { // 无参数, 啥也不做 return LogicLine::Ignore }; @@ -563,7 +558,7 @@ Print: LogicLine = "print" LEnd => { ).into() }, Args::Expanded(p, s) if p.len() == 0 && s.len() == 0 => { - ArgsRepeat::new(1, vec![ + ArgsRepeat::new(l.new_value(1), vec![ LogicLine::Other(Args::Expanded( vec![ReprVar("print".into())], vec![], @@ -578,7 +573,7 @@ Print: LogicLine = "print" LEnd => { res.push(one(arg)) } - res.push(ArgsRepeat::new(1, vec![ + res.push(ArgsRepeat::new(l.new_value(1), vec![ LogicLine::Other(Args::Expanded(vec![ ReprVar("print".into()), ], vec![])) diff --git a/tools/parser/tests/Cargo.toml b/tools/parser/tests/Cargo.toml index 9bed7bf..e5182be 100644 --- a/tools/parser/tests/Cargo.toml +++ b/tools/parser/tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "parser-tests" -version = "0.1.49" +version = "0.1.50" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/parser/tests/src/lib.rs b/tools/parser/tests/src/lib.rs index 3d7d65c..55256e0 100644 --- a/tools/parser/tests/src/lib.rs +++ b/tools/parser/tests/src/lib.rs @@ -4807,6 +4807,26 @@ fn match_test() { ], ); + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + const C = 1; + match a b c { @{} } + inline*C@{ + foo @; + Builtin.StopRepeat!; + } + print end; + print @; + "#).unwrap()).compile().unwrap(), + vec![ + "foo a", + "print end", + "print a", + "print b", + "print c", + ], + ); + assert_eq!( CompileMeta::new().compile(parse!(parser, r#" const C = 2; @@ -5319,8 +5339,9 @@ fn match_test() { assert_eq!( parse!(parser, r#" - # align............. - inline@ A B *C { + inline@ # align ..... + A B *C + { print A B C; } "#).unwrap(), @@ -6088,6 +6109,26 @@ fn const_match_test() { "print _0", ], ); + + // 无限重复块 + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + take*I = 0; + inline 0@ { + match I { [5] { Builtin.StopRepeat!; } _ { + print I; + take*I = I + 1; + } } + } + "#).unwrap()).compile().unwrap(), + vec![ + "print 0", + "print 1", + "print 2", + "print 3", + "print 4", + ], + ); } #[test] diff --git a/tools/syntax/Cargo.toml b/tools/syntax/Cargo.toml index 37e8cf4..a9eefb9 100644 --- a/tools/syntax/Cargo.toml +++ b/tools/syntax/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syntax" -version = "0.2.53" +version = "0.2.54" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/syntax/src/builtins.rs b/tools/syntax/src/builtins.rs index a1fb72e..4995dc3 100644 --- a/tools/syntax/src/builtins.rs +++ b/tools/syntax/src/builtins.rs @@ -360,6 +360,31 @@ pub fn build_builtins() -> Vec { }) } + fn stop_repeat:StopRepeat(meta) [] { + let Some(flag) = meta.args_repeat_flags.last_mut() else { + return Err((2, format!("Not in repeat block"))) + }; + *flag = false; + Ok("__".into()) + } + + fn repeat_limit:RepeatLimit(meta) [] { + Ok(meta.args_repeat_limit().to_string().into()) + } + + fn set_repeat_limit:SetRepeatLimit(meta) [d:limit] { + check_type!("var" Value::Var(limit) = limit.value() => { + let limit: usize = match limit.parse() { + Ok(n) => n, + Err(e) => { + return Err((2, e.to_string())) + }, + }; + meta.set_args_repeat_limit(limit); + Ok("__".into()) + }) + } + fn expand_stack:ExpandStack(meta) [] { meta.log_expand_stack::(); Ok("__".into()) @@ -427,7 +452,6 @@ pub fn build_builtins() -> Vec { Ok("__".into()) }) } - fn bind_sep:BindSep(meta) [v:sep] { check_type!("var" Value::Var(sep) = sep.value() => { match sep.as_var_type().as_string() { diff --git a/tools/syntax/src/lib.rs b/tools/syntax/src/lib.rs index 91f07d9..5fd2849 100644 --- a/tools/syntax/src/lib.rs +++ b/tools/syntax/src/lib.rs @@ -19,7 +19,7 @@ use std::{ use builtins::{build_builtins, BuiltinFunc}; use either::Either; -use itermaps::{fields, short_funcs::copy, MapExt, Unpack}; +use itermaps::{fields, MapExt, Unpack}; use tag_code::{ args, logic_parser::{Args as LArgs, IdxBox, ParseLine, ParseLines}, @@ -2811,21 +2811,21 @@ impl_enum_froms!(impl From for Args { /// 拿取指定个参数, 并重复块中代码 #[derive(Debug, PartialEq, Clone)] pub struct ArgsRepeat { - count: Either>, + /// 0 表示无限循环 + count: IdxBox>, block: InlineBlock, } impl ArgsRepeat { - pub fn new(count: usize, block: InlineBlock) -> Self { - assert_ne!(count, 0); - Self { count: Either::Left(count), block } + pub fn new(count: IdxBox, block: InlineBlock) -> Self { + Self { count: count.map(Either::Left), block } } pub fn new_valued(count: IdxBox, block: InlineBlock) -> Self { - Self { count: Either::Right(count), block } + Self { count: count.map(Either::Right), block } } - pub fn count(&self) -> Either> { - self.count.as_ref().map_left(copy) + pub fn count(&self) -> &IdxBox> { + &self.count } pub fn block(&self) -> &InlineBlock { @@ -2838,11 +2838,12 @@ impl ArgsRepeat { } impl Compile for ArgsRepeat { fn compile(self, meta: &mut CompileMeta) { - let count = match self.count { + let loc = self.count.new_value(()); + let count = match self.count.value { Either::Left(count) => count, Either::Right(value) => { let Some((n, _)) = value.try_eval_const_num(meta) else { - let (line, col) = value.location(&meta.source); + let (line, col) = loc.location(&meta.source); err!( "{}\n重复块次数不是数字, 位于: {}:{}\n{}", meta.err_info().join("\n"), @@ -2852,17 +2853,17 @@ impl Compile for ArgsRepeat { exit(6) }; let n = n.round(); - if n <= 0.0 || !n.is_finite() { - let (line, col) = value.location(&meta.source); + if n < 0.0 || !n.is_finite() { + let (line, col) = loc.location(&meta.source); err!( - "{}\n重复块次数必须大于0 ({}), 位于: {}:{}", + "{}\n重复块次数必须不小于0 ({}), 位于: {}:{}", meta.err_info().join("\n"), n, line, col, ); exit(6) } if n > 512.0 { - let (line, col) = value.location(&meta.source); + let (line, col) = loc.location(&meta.source); err!( "{}\n重复块次数过大 ({}), 位于: {}:{}", meta.err_info().join("\n"), @@ -2873,20 +2874,34 @@ impl Compile for ArgsRepeat { n as usize }, }; - let chunks: Vec> = meta.get_env_args() - .chunks(count) - .map(|chunks| chunks.iter() - .cloned() - .map(Into::into) - .collect()) - .collect(); - for args in chunks { - let args = Vec::from_iter(args.iter().cloned()); + let mut chunks = if count == 0 { + Either::Left(repeat_with(Vec::new)) + } else { + Either::Right(meta.get_env_args() + .chunks(count) + .map(|chunks| chunks.iter() + .cloned() + .map(Into::into) + .collect()) + .collect::>>() + .into_iter()) + }.enumerate(); + meta.args_repeat_flags.push(true); + while let Some((i, args)) = chunks.next() { meta.with_env_args_scope(|meta| { meta.set_env_args(args); self.block.clone().compile(meta) }); + if !meta.args_repeat_flags.last().unwrap() { break } + if i >= meta.args_repeat_limit { + err!( + "Maximum repeat depth exceeded ({})", + meta.args_repeat_limit, + ); + exit(6) + } } + meta.args_repeat_flags.pop().unwrap(); } } @@ -3967,6 +3982,8 @@ pub struct CompileMeta { last_builtin_exit_code: u8, enable_misses_match_log_info: bool, enable_misses_bind_info: bool, + args_repeat_limit: usize, + args_repeat_flags: Vec, noop_line: String, /// 保证不会是字符串 bind_custom_sep: Option, @@ -4033,6 +4050,8 @@ impl CompileMeta { enable_misses_match_log_info: false, enable_misses_bind_info: false, noop_line: "noop".into(), + args_repeat_limit: 10000, + args_repeat_flags: Vec::new(), bind_custom_sep: None, source, }; @@ -4628,6 +4647,14 @@ impl CompileMeta { self.const_expand_max_depth = const_expand_max_depth; } + pub fn args_repeat_limit(&self) -> usize { + self.args_repeat_limit + } + + pub fn set_args_repeat_limit(&mut self, limit: usize) { + self.args_repeat_limit = limit; + } + pub fn set_extender(&mut self, extender: Box) { self.extender = extender.into(); }