Lua中实现Python的eval()

Preface

在Python中呢,有一个非常便利的一个函数,可以针对一个字符串(类似于1+3)的运算而不需做任何的解析工作:

eval()

因无太多文章关于此点的介绍,故本篇文章即为介绍如何实现在Lua下的eval()的功能(其实很简单)。

正文

问题来源

问题具体描述

其实,这个问题是出现在一个例子中,在这个例子中,我们需要对特定(或非特定)数字进行动态性的运算。

何为动态性地运算呢?

即,有以下数字:8、7、6、5、4,对这5个数字(顺序可以不变或可变,此处规定为不变)依次进行与运算符的运算。

譬如,我需要下面这个运算:

8 + 7 + 6 + 5 + 4

但是因为特殊的需求,又需要下面这个运算:

8 - 7 - 6 - 5 - 4

和下面这个运算:

8 x 7 + 6 / 5 - 4

其实运算的情况有很多,但是需要对其运算符进行动态地“注入”进去。

又譬如,运算符不止有“+ - * /”这四种,还可能包括了“^ % += -+ ……”等等情况,
如果需求异常特殊,可能还会出现类似于“i = i + k(k ≥ 1)”的等式,
因此使用“静态”的方法是不现实的。

问题总结

  1. 输入

    8 7 6 5 4

  2. 输出

    一个数值

  3. 要求

    • 数字顺序不变
    • 运算符可“动态注入”

解决方案

完美解决

此文只是使用loadstring的方法,当然你可以使用在Lua中的Python库Lunatic Python

你可以使用python.eval()直接实现不费任何力气。

但是,本着钻研精神和深究到底的精神(其实是我不热爱Python),故使用

loadstring()

点击我查看loadstring文档

其实实现方式非常简单,但是中间有坑。

请看以下的示例:

1
2
3
4
5
6
Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio
> a = '3'
> b = '7'
> operator = '+'
> loadstring("return ".. a .. operator .. b)()
10 -- 请注意结果值没有小数点

通过以上的运算即可实现动态性地运算。

具体如何做思路:

  1. 将所需要的数字存入table中
  2. 将所需要的运算符存入table中
  3. 用for循环遍历相应的字符便可计算

测试

这个地方其实也不算是坑,只能说你一不小心地时候会弄出不一样的东西来。下面介绍坑是什么、为什么以及怎么解决。

在得知正确地写法前,以下是测试代码

1
2
3
4
5
6
7
8
9
10
Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio
> a = '3'
> b = '7'
> operator = '+'
> loadstring("return a" .. operator .. "b")()
10.0 -- 请注意结果有小数点
> loadstring("return 3" .. operator .. "7")()
10 -- 请注意无小数点
> loadstring("return ".. a .. operator .. b)()
10 -- 请注意结果值没有小数点

有无小数点不同的原因在于:"return a""return " .. a的位置(当然也包括b)。
为什么会这样呢?

我去stackoverflow上问了这个问题,两个人的说法有点含糊,让我来测试验证一下:
以下是测结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
> a = '3'
> b = '7'
> operator = '+'
-- 第一组:a在引号中的情况
> loadstring("return a" .. operator .. "b")()
10.0 -- 有小数点,a、b都有双引号
> loadstring("return a" .. operator .. b)()
10.0 -- 有小数点,a有引号,b没有引号
> loadstring("return a" .. operator .. 7)()
10.0 -- 有小数点,a有引号,7无引号
> loadstring("return a" .. operator .. "7")()
10.0 -- 有小数点,a有引号,7有引号
-- 第二组:a在引号外的情况
> loadstring("return " .. a .. operator.."7")()
10 -- 无小数点,a无引号,7有引号
> loadstring("return " .. a .. operator.. 7)()
10 -- 无小数点,a、7都没有引号
> loadstring("return " .. a .. operator .. "b")()
10.0 -- 有小数点,a无引号,b有引号
> loadstring("return ".. a .. operator .. b)()
10 -- 无小数点,a、b无引号
-- 第三组:没有a和b的情况
> loadstring("return 3" .. operator .. "7")()
10 -- 无小数点,3、7均有引号
> loadstring("return 3" .. operator .. 7)()
10 -- 无小数点,3有引号,7无引号
> loadstring("return " .. 3 .. operator .. 7)()
10 -- 无小数点,3、7均无引号
> loadstring("return " .. 3 .. operator .. "7")()
10 -- 无小数点,3无引号,7有引号

测试总结

  1. 第一组:当a在引号中的时候,不管后面的怎么变结果都存在小数点,即为float型数据。因为a在引号中又在loadstring中,此过程为对string类型进行运算,
    那么即会转换为float类型,后面的操作对象和“+”运算符(+也起到着影响因素,只是此处结果和运算结果相同)遇到了float类型,其结果会转换为float类型。(点我查看具体的转换关系

  2. 第二组:a在引号外的情况的时候,只有当后面的操作对象type类型是number时才不会有小数点存在,其余情况会有小数点存在。
    这里需要注意的是,虽然..是连接的作用,连接后的结果都是string类型,但是连接前,b是一个字符(串)(string类型),而7是一个数字(number型)
    虽然结果相同,但是运算出来的结果却是完全不同的

  3. 第三组:没有a和b的情况,可以看出不管如何运算,其结果均没有小数点。

结论:

a或者b没有引号包括,即作为string对象参与运算;
若有引号包括,即作为string类型参与运算,即return stringA + number,其结果必然会被转换成float类型

由此结论可知“正确的”写法是:

1
2
3
loadstring("return " .. a .. operator .. b)()
-- 或
loadstring("return tonumber(a)"..operator.."tonumber(b)")()

总结

其实原理非常地简单,如果你弄不清你可能需要去看下Lua的基本知识或者重新看一遍分析过程。

有任何不懂的可以留言。

End

0%