diff --git a/Cargo.lock b/Cargo.lock index 14317d3..5dc472e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,7 +288,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mindustry_logic_bang_lang" -version = "0.13.5" +version = "0.13.6" dependencies = [ "display_source", "lalrpop", @@ -563,7 +563,7 @@ version = "0.1.2" [[package]] name = "var_utils" -version = "0.2.0" +version = "0.3.0" dependencies = [ "lazy-regex", ] diff --git a/Cargo.toml b/Cargo.toml index cca8e63..b0e7fc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mindustry_logic_bang_lang" -version = "0.13.5" +version = "0.13.6" edition = "2021" authors = ["A4-Tacks "] diff --git a/examples/README.md b/examples/README.md index 999635f..e2c9c26 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,6 +19,7 @@ > [`const.mdtlbl`](./const.mdtlbl)
> [`inline_block.mdtlbl`](./inline_block.mdtlbl)
> [`take.mdtlbl`](./take.mdtlbl)
+> [`compiling_eval`](./compiling_eval.mdtlbl)
> [`cmp_deps.mdtlbl`](./cmp_deps.mdtlbl)
> [`switch_append.mdtlbl`](./switch_append.mdtlbl)
> [`shell_sort.mdtlbl`](./shell_sort.mdtlbl)
diff --git a/examples/compiling_eval.mdtlbl b/examples/compiling_eval.mdtlbl new file mode 100644 index 0000000..eaf8aa7 --- /dev/null +++ b/examples/compiling_eval.mdtlbl @@ -0,0 +1,44 @@ +#** +* 这是0.13.6新增的功能, 尝试在编译期进行一些计算 +* +* 目前支持的有op部分运算和set, 在数字时 +* +* 触发为一个匿名返回句柄的DExp其中仅有一行op或者set并且其返回值位为返回句柄替换符 +* 当这个DExp被take时, 将会尝试进行计算. +* +* 目前仅支持数字, 字符串和变量并不被支持 +* +* 一些运算无法支持例如rand, 一些运算不方便支持例如严格相等和len noise angle +* +* 还有需要注意的是, 逻辑和本编译器的浮点运算误差和显示位数都有所不同, +* 所以使用时需要注意 +* +* 还有一些运算细节问题, 以及逻辑浮点数的parser也有所差异, +* 比如超长小数位逻辑的行为非常玄学, +* 还有i64溢出数字逻辑可能会将其当做变量或极其玄学的溢出 +* +* 本编译器已经尽量做到更像逻辑的行为 +*# + +take Num = ($ = 1/3;); +take Foo = ($ = Num*3;); +print Num","Foo; +printflush message1; +#* >>> +print 0.3333333333333333 +print "," +print 1 +printflush message1 +*# + +take N = (set $ ($ = 1+2;);); +print N; +#* >>> +print 3 +*# + +take N = ($ = 1e3 / 10;); +print N; +#* >>> +print 100 +*# diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index c2e56e1..241caaf 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -21,6 +21,7 @@ use display_source::{ DisplaySource, DisplaySourceMeta, }; +use var_utils::AsVarType; pub use crate::tag_code::mdt_logic_split; use utils::counter::Counter; @@ -171,6 +172,15 @@ impl TakeHandle for Value { fn take_handle(self, meta: &mut CompileMeta) -> Var { // 改为使用空字符串代表空返回字符串 // 如果空的返回字符串被编译将会被编译为tmp_var + if let Some(num) = self.try_eval_const_num(meta) { + return match num.classify() { + std::num::FpCategory::Nan => "null".into(), + std::num::FpCategory::Infinite + if num.is_sign_negative() => (i64::MIN+1).to_string(), + std::num::FpCategory::Infinite => i64::MAX.to_string(), + _ => num.to_string(), + } + } match self { Self::Var(var) => var.take_handle(meta), Self::DExp(dexp) => dexp.take_handle(meta), @@ -320,6 +330,55 @@ impl Value { pub fn is_result_handle(&self) -> bool { matches!(self, Self::ResultHandle) } + + pub fn as_result_handle(&self) -> Option<()> { + if let Self::ResultHandle = self { + ().into() + } else { + None + } + } + + /// 尝试解析为一个常量数字 + pub fn try_eval_const_num(&self, meta: &CompileMeta) -> Option { + fn num(s: &str) -> Option { + s.as_var_type().as_number().copied() + } + match self { + Self::ReprVar(var) => num(var), + Self::Var(name) => { + match meta.get_const_value(name) { + Some((_, Self::Var(var))) => num(var), + Some((_, Self::ReprVar(repr_var))) => { + unreachable!("被const的reprvar {:?}", repr_var) + }, + Some((_, x @ Self::DExp(_))) => x.try_eval_const_num(meta), + Some(_) => None?, + None => num(name), + } + }, + Self::DExp(dexp) if dexp.len() == 1 && dexp.result.is_empty() => { + let logic_line = &dexp.first().unwrap(); + match logic_line { + LogicLine::Op(op) => op.try_eval_const_num(meta), + LogicLine::Other(args) => { + let Value::ReprVar(cmd) = &args[0] else { + return None; + }; + match &**cmd { + "set" + if args.len() == 3 + && args[1].is_result_handle() + => args[2].try_eval_const_num(meta), + _ => None, + } + }, + _ => None, + } + }, + _ => None, + } + } } impl Deref for Value { type Target = str; @@ -388,7 +447,8 @@ impl TakeHandle for DExp { fn take_handle(self, meta: &mut CompileMeta) -> Var { let DExp { mut result, lines } = self; - if result.is_empty() { + let dexp_res_is_alloced = result.is_empty(); + if dexp_res_is_alloced { result = meta.get_tmp_var(); /* init tmp_var */ } else if let Some((_, value)) = meta.get_const_value(&result) { @@ -411,9 +471,9 @@ impl TakeHandle for DExp { result = value.as_var().unwrap().clone() } assert!(! result.is_empty()); - meta.push_dexp_handle(result); + meta.push_dexp_handle((result, dexp_res_is_alloced)); lines.compile(meta); - let result = meta.pop_dexp_handle(); + let (result, _) = meta.pop_dexp_handle(); result } } @@ -1494,6 +1554,80 @@ impl Op { StrictEqual: StrictEqual, ] } + + /// 在输出值为返回句柄替换符时, 尝试编译期计算它 + pub fn try_eval_const_num(&self, meta: &CompileMeta) -> Option { + use std::num::FpCategory as FpC; + fn conv(n: f64) -> Option { + match n.classify() { + FpC::Nan => 0.0.into(), + FpC::Infinite => None, + _ => n.into(), + } + } + fn bool_as(x: bool) -> f64 { + if x { 1. } else { 0. } + } + let OpInfo { result, arg1, arg2, .. } = self.get_info(); + result.as_result_handle()?; + let (a, b) = ( + arg1.try_eval_const_num(meta)?, + match arg2 { + Some(value) => value.try_eval_const_num(meta)?, + None => 0.0, + }, + ); + let (a, b) = (conv(a)?, conv(b)?); + match self { + Op::Add(..) => a + b, + Op::Sub(..) => a - b, + Op::Mul(..) => a * b, + Op::Div(..) | Op::Idiv(..) | Op::Mod(..) + if matches!(b.classify(), FpC::Zero | FpC::Subnormal) => f64::NAN, + Op::Div(..) => a / b, + Op::Idiv(..) => (a / b).floor(), + Op::Mod(..) => a % b, + Op::Pow(..) => a.powf(b), + Op::Abs(..) => a.abs(), + Op::Log(..) | Op::Log10(..) if a <= 0. => f64::NAN, + Op::Log(..) => a.ln(), + Op::Log10(..) => a.log10(), + Op::Floor(..) => a.floor(), + Op::Ceil(..) => a.ceil(), + Op::Sqrt(..) => a.sqrt(), + Op::Sin(..) => a.to_radians().sin(), + Op::Cos(..) => a.to_radians().cos(), + Op::Tan(..) => a.to_radians().tan(), + Op::Asin(..) => a.asin().to_degrees(), + Op::Acos(..) => a.acos().to_degrees(), + Op::Atan(..) => a.atan().to_degrees(), + + Op::Equal(..) => bool_as(a == b), + Op::NotEqual(..) => bool_as(a != b), + Op::Land(..) => bool_as(a != 0. && b != 0.), + Op::LessThan(..) => bool_as(a < b), + Op::LessThanEq(..) => bool_as(a <= b), + Op::GreaterThan(..) => bool_as(a > b), + Op::GreaterThanEq(..) => bool_as(a >= b), + + Op::Shl(..) => ((a as i64) << b as i64) as f64, + Op::Shr(..) => ((a as i64) >> b as i64) as f64, + Op::Or(..) => ((a as i64) | b as i64) as f64, + Op::And(..) => ((a as i64) & b as i64) as f64, + Op::Xor(..) => ((a as i64) ^ b as i64) as f64, + Op::Not(..) => !(a as i64) as f64, + + Op::Max(..) => a.max(b), + Op::Min(..) => a.min(b), + + // Not Impl + | Op::StrictEqual(..) + | Op::Angle(..) + | Op::Len(..) + | Op::Noise(..) + | Op::Rand(..) => None?, + }.into() + } } impl Compile for Op { fn compile(self, meta: &mut CompileMeta) { @@ -2143,7 +2277,7 @@ impl Compile for LogicLine { }, Self::SetResultHandle(value) => { let new_dexp_handle = value.take_handle(meta); - meta.set_dexp_handle(new_dexp_handle); + meta.set_dexp_handle((new_dexp_handle, false)); }, Self::Select(select) => select.compile(meta), Self::Expand(expand) => expand.compile(meta), @@ -2405,8 +2539,8 @@ pub struct CompileMeta { /// /// `Vec<(leaks, HashMap, Value)>)>` const_var_namespace: Vec<(Vec, HashMap, Value)>)>, - /// 每层DExp所使用的句柄, 末尾为当前层 - dexp_result_handles: Vec, + /// 每层DExp所使用的句柄, 末尾为当前层, 同时有一个是否为自动分配名称的标志 + dexp_result_handles: Vec<(Var, bool)>, tmp_tag_count: Counter Var>, /// 每层const展开的标签 /// 一个标签从尾部上寻, 寻到就返回找到的, 没找到就返回原本的 @@ -2653,12 +2787,12 @@ impl CompileMeta { } /// 新增一层DExp, 并且传入它使用的返回句柄 - pub fn push_dexp_handle(&mut self, handle: Var) { + pub fn push_dexp_handle(&mut self, handle: (Var, bool)) { self.dexp_result_handles.push(handle) } /// 如果弹无可弹, 说明逻辑出现了问题 - pub fn pop_dexp_handle(&mut self) -> Var { + pub fn pop_dexp_handle(&mut self) -> (Var, bool) { self.dexp_result_handles.pop().unwrap() } @@ -2680,17 +2814,26 @@ impl CompileMeta { .collect() } + /// 尝试获取当前DExp返回句柄, 没有DExp的话返回空 + pub fn try_get_dexp_handle(&self) -> Option<&Var> { + self.dexp_result_handles.last().map(|(var, _)| var) + } + /// 获取当前DExp返回句柄 pub fn dexp_handle(&self) -> &Var { - self.dexp_result_handles - .last() + self.try_get_dexp_handle() .unwrap_or_else( || self.do_out_of_dexp_err("`DExpHandle` (`$`)")) } + /// 获取该DExp是否为自动分配的 + pub fn try_get_dexp_is_alloced(&self) -> Option { + self.dexp_result_handles.last().map(|&(_, x)| x) + } + /// 将当前DExp返回句柄替换为新的 /// 并将旧的句柄返回 - pub fn set_dexp_handle(&mut self, new_dexp_handle: Var) -> Var { + pub fn set_dexp_handle(&mut self, new_dexp_handle: (Var, bool)) -> (Var, bool) { if let Some(ref_) = self.dexp_result_handles.last_mut() { replace(ref_, new_dexp_handle) } else { diff --git a/src/syntax/tests.rs b/src/syntax/tests.rs index 1aa4a7d..9b74941 100644 --- a/src/syntax/tests.rs +++ b/src/syntax/tests.rs @@ -344,7 +344,7 @@ fn goto_compile_test() { "#).unwrap()).compile().unwrap(); assert_eq!(logic_lines, vec![ "op strictEqual __0 a b", - "jump 2 equal __0 false", + "jump 2 equal __0 0", "end", ]); @@ -357,7 +357,7 @@ fn goto_compile_test() { "#).unwrap()).compile().unwrap(); assert_eq!(logic_lines, vec![ "op strictEqual __0 a b", - "jump 2 equal __0 false", + "jump 2 equal __0 0", "end", ]); @@ -558,7 +558,7 @@ fn compile_test() { #[test] fn compile_take_test() { let parser = LogicLineParser::new(); - let ast = parse!(parser, "op x (op $ 1 + 2;) + 3;").unwrap(); + let ast = parse!(parser, "op x ({}op $ 1 + 2;) + 3;").unwrap(); let mut meta = CompileMeta::new(); meta.push(TagLine::Line("noop".to_string().into())); assert_eq!( @@ -823,7 +823,7 @@ fn const_expand_label_rename_test() { i = C; goto :next _; # 测试往外跳 ); - const C = (op $ 1 + 1;); + const C = ({}op $ 1 + 1;); take __ = B; print "skiped"; :next @@ -1051,7 +1051,7 @@ fn sets_test() { let parser = TopLevelParser::new(); let ast = parse!(parser, r#" - a b c = 1 2 (op $ 2 + 1;); + a b c = 1 2 ({}op $ 2 + 1;); "#).unwrap(); let meta = CompileMeta::new(); let mut tag_codes = meta.compile(ast); @@ -1268,8 +1268,8 @@ fn cmptree_test() { end; "#).unwrap()).compile().unwrap(); assert_eq!(logic_lines, vec![ - "jump 2 equal a false", - "jump 3 notEqual b false", + "jump 2 equal a 0", + "jump 3 notEqual b 0", "foo", "end", ]); @@ -1281,9 +1281,9 @@ fn cmptree_test() { end; "#).unwrap()).compile().unwrap(); assert_eq!(logic_lines, vec![ - "jump 2 notEqual a false", - "jump 3 equal b false", - "jump 4 notEqual c false", + "jump 2 notEqual a 0", + "jump 3 equal b 0", + "jump 4 notEqual c 0", "foo", "end", ]); @@ -1295,10 +1295,10 @@ fn cmptree_test() { 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", + "jump 2 notEqual a 0", + "jump 4 equal b 0", + "jump 5 notEqual c 0", + "jump 5 notEqual d 0", "foo", "end", ]); @@ -1310,11 +1310,11 @@ fn cmptree_test() { 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", + "jump 6 notEqual a 0", + "jump 6 notEqual b 0", + "jump 6 notEqual c 0", + "jump 6 notEqual d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1326,11 +1326,11 @@ fn cmptree_test() { 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", + "jump 5 equal a 0", + "jump 5 equal b 0", + "jump 5 equal c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1342,11 +1342,11 @@ fn cmptree_test() { 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", + "jump 5 equal a 0", + "jump 5 equal b 0", + "jump 5 equal c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1358,11 +1358,11 @@ fn cmptree_test() { 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", + "jump 5 equal a 0", + "jump 5 equal b 0", + "jump 5 equal c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1374,9 +1374,9 @@ fn cmptree_test() { end; "#).unwrap()).compile().unwrap(); assert_eq!(logic_lines, vec![ - "jump 3 equal a false", + "jump 3 equal a 0", "op land __0 b c", - "jump 4 notEqual __0 false", + "jump 4 notEqual __0 0", "foo", "end", ]); @@ -1388,10 +1388,10 @@ fn cmptree_test() { 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", + "jump 2 equal a 0", + "jump 5 notEqual b 0", + "jump 4 equal c 0", + "jump 5 notEqual d 0", "foo", "end", ]); @@ -1403,10 +1403,10 @@ fn cmptree_test() { 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", + "jump 2 notEqual a 0", + "jump 5 notEqual b 0", + "jump 4 equal c 0", + "jump 5 notEqual d 0", "foo", "end", ]); @@ -1418,10 +1418,10 @@ fn cmptree_test() { 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", + "jump 2 equal a 0", + "jump 5 notEqual b 0", + "jump 5 equal c 0", + "jump 5 equal d 0", "foo", "end", ]); @@ -1433,11 +1433,11 @@ fn cmptree_test() { 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", + "jump 3 equal a 0", + "jump 3 equal b 0", + "jump 6 notEqual c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1449,11 +1449,11 @@ fn cmptree_test() { 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", + "jump 2 equal a 0", + "jump 6 notEqual b 0", + "jump 6 notEqual c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1465,11 +1465,11 @@ fn cmptree_test() { 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", + "jump 2 equal a 0", + "jump 6 notEqual b 0", + "jump 6 notEqual c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1481,11 +1481,11 @@ fn cmptree_test() { 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", + "jump 3 equal a 0", + "jump 6 notEqual b 0", + "jump 6 notEqual c 0", + "jump 5 equal d 0", + "jump 6 notEqual e 0", "foo", "end", ]); @@ -1498,9 +1498,9 @@ fn cmptree_test() { "#).unwrap()).compile().unwrap(); assert_eq!(logic_lines, vec![ "op add __0 a 2", - "jump 4 equal __0 false", + "jump 4 equal __0 0", "op add __1 b 2", - "jump 5 notEqual __1 false", + "jump 5 notEqual __1 0", "foo", "end", ]); @@ -3716,3 +3716,137 @@ fn switch_append_tail_once_test() { "#).unwrap(), ); } + +#[test] +fn const_expr_eval_test() { + let parser = TopLevelParser::new(); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + print 1.00; + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print 1; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + take N = (op $ (op $ (op $ 1 + 2;) + 3;) + 4;); + print N; + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print `10`; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + take N = ($ = 1 + 2 + 3 + 4;); + print N; + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print `10`; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + take N = ($ = 1 << 10;); + print N; + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + take N = 1024; + print N; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + take N = ($ = 1.0 == 1;); + print N; + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + take N = 1; + print N; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + take N = (m: $ = 1.0 == 1;); + print N; + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + m = 1.0 == 1; + print m; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + print ($ = (set $ ($ = 1 + 1;););); + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print 2; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( // 非匿名句柄不优化 + CompileMeta::new().compile(parse!(parser, r#" + print (x: $ = 1 + 1;); + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + op x 1 + 1; + print x; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + print ($ = log(0);); + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print null; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + print ($ = acos(0.5);); + print ($ = cos(60);); + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print 60.00000000000001; + print 0.5000000000000001; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + print ($ = -3 // 2;); + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print -2; + "#).unwrap()).compile().unwrap(), + ); + + assert_eq!( + CompileMeta::new().compile(parse!(parser, r#" + print ($ = -2 - 3;); + print ($ = max(3, 5);); + print ($ = min(0, -2);); + print ($ = min(null, -2);); + print ($ = abs(null);); + print ($ = null;); + "#).unwrap()).compile().unwrap(), + CompileMeta::new().compile(parse!(parser, r#" + print -5; + print 5; + print -2; + print -2; + print 0; + print null; + "#).unwrap()).compile().unwrap(), + ); +} diff --git a/tools/var_utils/Cargo.lock b/tools/var_utils/Cargo.lock index ac40d8d..65de91b 100644 --- a/tools/var_utils/Cargo.lock +++ b/tools/var_utils/Cargo.lock @@ -112,7 +112,7 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "var_utils" -version = "0.2.0" +version = "0.3.0" dependencies = [ "lazy-regex", ] diff --git a/tools/var_utils/Cargo.toml b/tools/var_utils/Cargo.toml index ca7a0a4..e2f6c1f 100644 --- a/tools/var_utils/Cargo.toml +++ b/tools/var_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "var_utils" -version = "0.2.0" +version = "0.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/var_utils/src/lib.rs b/tools/var_utils/src/lib.rs index acb69d9..8d6aec9 100644 --- a/tools/var_utils/src/lib.rs +++ b/tools/var_utils/src/lib.rs @@ -2,6 +2,7 @@ use lazy_regex::{regex,Lazy,Regex}; use std::{ collections::HashSet, thread_local, + num::IntErrorKind, }; /// 判断是否是一个标识符(包括数字) @@ -28,13 +29,130 @@ pub const VAR_KEYWORDS: &[&str] = {&[ pub fn is_ident_keyword(s: &str) -> bool { thread_local! { static VAR_KEYWORDS_SET: HashSet<&'static str> - = HashSet::from_iter(VAR_KEYWORDS.into_iter().copied()); + = HashSet::from_iter(VAR_KEYWORDS.iter().copied()); } VAR_KEYWORDS_SET.with(|var_keywords| { var_keywords.get(s).is_some() }) } +#[derive(Debug, PartialEq, Clone)] +pub enum VarType<'a> { + Var(&'a str), + String(&'a str), + Number(f64), +} + +impl<'a> VarType<'a> { + /// Returns `true` if the var type is [`Number`]. + /// + /// [`Number`]: VarType::Number + #[must_use] + pub fn is_number(&self) -> bool { + matches!(self, Self::Number(..)) + } + + pub fn as_number(&self) -> Option<&f64> { + if let Self::Number(v) = self { + Some(v) + } else { + None + } + } + + /// Returns `true` if the var type is [`String`]. + /// + /// [`String`]: VarType::String + #[must_use] + pub fn is_string(&self) -> bool { + matches!(self, Self::String(..)) + } + + pub fn as_string(&self) -> Option<&&'a str> { + if let Self::String(v) = self { + Some(v) + } else { + None + } + } + + /// Returns `true` if the var type is [`Var`]. + /// + /// [`Var`]: VarType::Var + #[must_use] + pub fn is_var(&self) -> bool { + matches!(self, Self::Var(..)) + } + + pub fn as_var(&self) -> Option<&&'a str> { + if let Self::Var(v) = self { + Some(v) + } else { + None + } + } +} + +pub trait AsVarType { + fn as_var_type(&self) -> VarType<'_>; +} +impl AsVarType for str { + fn as_var_type(&self) -> VarType<'_> { + fn as_string(s: &str) -> Option<&str> { + if s.len() < 2 { return None; } + if !(s.starts_with('"') && s.ends_with('"')) { return None; } + Some(&s[1..s.len()-1]) + } + fn as_number(s: &str) -> Option { + static NUM_REGEX: &Lazy = regex!( + r"^-?(?:\d+(?:e[+\-]?\d+|\.\d*)?|\d*(?:\.\d+)?)$" + ); + static HEX_REGEX: &Lazy = regex!( + r"^0x-?[0-9A-Fa-f]+$" + ); + static BIN_REGEX: &Lazy = regex!( + r"^0b-?[01]+$" + ); + fn parse_radix(src: &str, radix: u32) -> Option { + match i64::from_str_radix(src, radix) { + Ok(x) => Some(x as f64), + Err(e) => { + match e.kind() { + | IntErrorKind::PosOverflow + | IntErrorKind::NegOverflow + => None, + _ => unreachable!("parse hex err: {e}, ({src:?})"), + } + }, + } + } + if NUM_REGEX.is_match(s) { + Some(s.parse().unwrap()) + } else if HEX_REGEX.is_match(s) { + parse_radix(&s[2..], 16) + } else if BIN_REGEX.is_match(s) { + parse_radix(&s[2..], 2) + } else { + None + } + } + match self { + "null" => return VarType::Number(f64::NAN.into()), + "true" => return VarType::Number(1.0.into()), + "false" => return VarType::Number(0.0.into()), + _ => (), + } + if let Some(str) = as_string(self) { + return VarType::String(str); + } + if let Some(num) = as_number(self) { + if num <= i64::MAX as f64 && num >= (i64::MIN + 1) as f64 { + return VarType::Number(num); + } + } + VarType::Var(self) + } +} #[cfg(test)] mod tests; diff --git a/tools/var_utils/src/tests.rs b/tools/var_utils/src/tests.rs index 3a1c90d..65f6c80 100644 --- a/tools/var_utils/src/tests.rs +++ b/tools/var_utils/src/tests.rs @@ -69,3 +69,50 @@ fn test() { assert!(! is_ident(str), "err: is_ident({str:?}) == false assert failed."); } } + +#[test] +fn float_parser_test() { + let src = [ + "1.2", + "0", + "0.", + ".0", + "00.", + ".00", + ".01", + "1e3", + "1e03", + "01e+3", + "01e+03", + "1e-3", + "001e-3", + "001e-03", + "100000", + "123.456", + "000123.456", + "1234567891011", + "-2", + "-2.3", + "-234.345", + "-234.", + "-.345", + "-2e12", + "null", + "true", + "false", + ]; + for src in src { + let r#type = src.as_var_type(); + assert!(matches!(r#type, VarType::Number(_)), "{:?}", r#type); + } +} + +#[test] +fn mod_op_test() { + assert_eq!(2.0 % 2.0, 0.0); + assert_eq!(1.0 % 2.0, 1.0); + assert_eq!(3.0 % 2.0, 1.0); + + assert_eq!(-0.2 % 1.0, -0.2); // 这是必要的防御, 在python, 它为0.8 + assert_eq!(-0.2 % 2.0, -0.2); +}