指定值约束的通用语言

Posted

技术标签:

【中文标题】指定值约束的通用语言【英文标题】:General-purpose language to specify value constraints 【发布时间】:2014-01-06 09:57:36 【问题描述】:

我正在寻找一种定义文本表达式的通用方法,该方法允许验证值。

例如,我有一个值只能设置为 1、2、3、10、11 或 12。 它的约束可能被定义为:(value >= 1 && value <= 3) || (value >= 10 && value <= 12)

或者其他可以是 1、3、5、7、9 等的值...会有类似 value % 2 == 1IsOdd(value) 的约束。

(为了帮助用户更正无效值,我想显示约束 - 所以像 IsOdd 这样的描述性更可取。)

这些约束将在客户端(在用户输入之后)和服务器端进行评估。 因此,多平台解决方案将是理想的(特别是 Win C#/Linux C++)。

是否存在允许评估或解析类似的简单表达式的现有语言/项目?

如果没有,我可以从哪里开始创建自己的?

我意识到这个问题有点含糊,因为我不完全确定我在追求什么。搜索没有结果,所以即使是一些术语作为起点也会有所帮助。然后我可以相应地更新/标记问题。

【问题讨论】:

拥有这样一个允许验证的框架将非常有用,例如在 javascriptphp 中使用仅用一种语言编写的相同规则! 【参考方案1】:

您可能想要研究依赖类型的语言,例如 Idris 或 Agda。

此类语言的类型系统允许对类型中的值约束进行编码。不能保证约束的程序根本无法编译。通常的例子是矩阵乘法,其中维度必须匹配。但这可以说是依赖类型语言的“hello world”,类型系统可以为你做更多的事情。

【讨论】:

【参考方案2】:

如果您最终开始使用自己的语言,我会尽量保持与实现无关。寻找合适的编程语言(例如 C)的形式表达式语法,并根据需要添加特殊的关键字/函数。一旦你对你的语言有了一个正式的定义,就可以使用你最喜欢的解析器生成器来实现一个解析器。

这样,即使您的解析器不能移植到某个平台,您至少也有一个正式的标准,可以从哪里开始一个单独的解析器实现。

【讨论】:

谢谢,这是个好主意。 顺便说一句,如果您最终决定实现自己的 DSL,请考虑为此使用 Jetbrains MPS:jetbrains.com/mps【参考方案3】:

您可能还想了解在 Ruby 中创建领域特定语言 (DSL)。 (这里有一篇关于这意味着什么以及它的样子的好文章:http://jroller.com/rolsen/entry/building_a_dsl_in_ruby)

这肯定会为您提供所需的可移植性,包括在您的 C# 环境中使用 IronRuby,并且您将能够利用 Ruby 的现有逻辑和数学运算。然后,您可以拥有如下所示的约束定义文件:

constrain 'wakeup_time' do
   6 <= value && value <= 10
end

constrain 'something_else' do
   check (value % 2 == 1), MustBeOdd
end

# constrain is a method that takes one argument and a code block
# check is a function you've defined that takes a two arguments
# MustBeOdd is the name of an exception type you've created in your standard set

但实际上,DSL 的优点在于您可以很好地控制约束文件的外观。

【讨论】:

【参考方案4】:

有多种方法可以跨多种语言验证值列表。我的首选方法是列出允许的值并将它们加载到dictionary/hashmap/list/vector(取决于语言和您的偏好)并编写一个简单的isIn()isValid() 函数,它将检查提供的值是否为有效基于它在数据结构中的存在。这样做的好处是代码很简单,可以很容易地用几乎任何语言实现。对于仅奇数或仅偶数数字有效性,不同语言 isOdd() 函数的小型库就足够了:如果它不奇怪,则根据定义它必须是偶数(除了 0 但可以是一个简单的例外设置来处理它,或者您可以简单地在代码文档中指定出于逻辑目的,您的代码将 0 评估为奇数/偶数(您的选择)。

出于与您提到的类似原因,我通常会使用一组 c++ 和 c# 函数来评估 isOdd(),代码如下:

C++

bool isOdd( int integer )  return (integer%2==0)?false:true;  

您还可以根据需要或偏好将inline 和/或fastcall 添加到函数中;我倾向于将其用作inlinefastcall,除非有必要这样做(至强处理器的性能大幅提升)。

C#

如果它不属于另一个类,那么在 C# 中也可以使用相同的行,只需将静态添加到前面即可:

static bool isOdd( int integer )  return (integer%2==0)?false:true;  

希望这会有所帮助,如果您需要任何进一步的信息,请告诉我:)

【讨论】:

【参考方案5】:

不确定它是否是您要查找的内容,但从您的起始条件(Win C#/Linux C++)来看,您可能不需要它完全与语言无关。您可以自己在 C++ 中实现具有所有所需功能的此类解析器,然后只需在 C++ 和 C# 项目中使用它 - 从而也无需添加外部库。

在应用程序设计级别,它会(相对)简单 - 您创建一个可跨平台构建的库并在两个项目中使用它。界面可能很简单,例如:

bool VerifyConstraint_int(int value, const char* constraint);
bool VerifyConstraint_double(double value, const char* constraint);
// etc

这样的接口可以在 Linux C++(通过静态或动态链接)和 Windows C#(使用 P/Invoke)中使用。您可以在两个平台上编译相同的代码库。

解析器(同样,从您在问题中描述的内容来看)可能非常简单 - 一个包含 VariableExpression 类型元素的树,可以是 Evaluated 和给定的 Variable价值。

示例类定义:

class Entity public: virtual VARIANT Evaluate() = 0; // boost::variant may be used typedef'd as VARIANT
class BinaryOperation: public Entity 
    private:
        Entity& left;
        Entity& right;
        enum Operation PLUS,MINUS,EQUALS,AND,OR,GREATER_OR_EQUALS,LESS_OR_EQUALS;
    public:
        virtual VARIANT Evaluate() override; // Evaluates left and right operands and combines them

class Variable: public Entity 
    private:
        VARIANT value;
    public:
        virtual VARIANT Evaluate() override return value;;


或者,您可以只用 C++ 编写验证代码,然后在 C# 和 C++ 应用程序中使用它:)

【讨论】:

【参考方案6】:

我个人的选择是 Lua。任何 DSL 的缺点是新语言的学习曲线以及如何将代码与脚本粘合在一起,但我发现 Lua 有很多用户群的支持和几本可以帮助您学习的好书。

如果您在编写一些非程序员可以为允许输入注入规则的通用代码之后,无论您采用哪种方式,都需要进行一些前期工作。我强烈建议不要自行开发,因为您可能会发现人们想要更多功能,而现有的 DSL 将拥有。

【讨论】:

【参考方案7】:

如果您使用的是 Java,那么您可以使用 Object Graph Navigation Library。

它使您能够编写可以解析、编译和评估 OGNL 表达式的 Java 应用程序。

OGNL 表达式包括基本的 java、C、C++、C# 表达式。

您可以编译使用一些变量的表达式,然后计算该表达式 对于一些给定的变量。

【讨论】:

【参考方案8】:

实现表达式验证的一种简单方法是使用 Python 的 eval 方法。它可用于评估表达式,就像您编写的那样。 Python 的语法对于简单的表达式和类似英语的语言来说很容易学习。您的表达式示例被翻译为:

(value >= 1 and value <= 3) or (value >= 10 and value <= 12)

用户提供的代码评估可能会带来安全风险,因为某些功能可以用于在主机上执行(例如open 功能,以打开文件)。但是eval 函数需要额外的参数来限制允许的函数。因此,您可以创建一个安全的评估环境。

# Import math functions, and we'll use a few of them to create
# a list of safe functions from the math module to be used by eval.
from math import *

# A user-defined method won't be reachable in the evaluation, as long
# as we provide the list of allowed functions and vars to eval.
def dangerous_function(filename):
  print open(filename).read()

# We're building the list of safe functions to use by eval:
safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])

# Let's test the eval method with your example:
exp = "(value >= 1 and value <= 3) or (value >= 10 and value <= 12)"
safe_dict['value'] = 2
print "expression evaluation: ", eval(exp, "__builtins__":None,safe_dict)
-> expression evaluation:  True

# Test with a forbidden method, such as 'abs'
exp = raw_input("type an expression: ")
-> type an expression: (abs(-2) >= 1 and abs(-2) <= 3) or (abs(-2) >= 10 and abs(-2) <= 12)
print "expression evaluation: ", eval(exp, "__builtins__":None,safe_dict)
-> expression evaluation:
-> Traceback (most recent call last):
->   File "<stdin>", line 1, in <module>
->   File "<string>", line 1, in <module>
-> NameError: name 'abs' is not defined

# Let's test it again, without any extra parameters to the eval method
# that would prevent its execution
print "expression evaluation: ", eval(exp)
-> expression evaluation:  True 
# Works fine without the safe dict! So the restrictions were active 
# in the previous example..

# is odd?
def isodd(x): return bool(x & 1)
safe_dict['isodd'] = isodd
print "expression evaluation: ", eval("isodd(7)", "__builtins__":None,safe_dict)
-> expression evaluation:  True
print "expression evaluation: ", eval("isodd(42)", "__builtins__":None,safe_dict)
-> expression evaluation:  False

# A bit more complex this time, let's ask the user a function:
user_func = raw_input("type a function: y = ")
-> type a function: y = exp(x)

# Let's test it:
for x in range(1,10):
    # add x in the safe dict
    safe_dict['x']=x
    print "x = ", x , ", y = ", eval(user_func,"__builtins__":None,safe_dict)

-> x =  1 , y =  2.71828182846
-> x =  2 , y =  7.38905609893
-> x =  3 , y =  20.0855369232
-> x =  4 , y =  54.5981500331
-> x =  5 , y =  148.413159103
-> x =  6 , y =  403.428793493
-> x =  7 , y =  1096.63315843
-> x =  8 , y =  2980.95798704
-> x =  9 , y =  8103.08392758

因此,您可以控制eval 方法应使用的允许函数,并拥有可以评估表达式的沙盒环境。

这是我们在我之前工作的一个项目中使用的。我们在自定义 Eclipse IDE 插件中使用 Python 表达式,使用Jython 在 JVM 中运行。 您可以对 IronPython 执行相同的操作以在 CLR 中运行。

我使用的示例部分灵感/复制自 Lybniz 项目说明,说明如何运行安全的 Python eval 环境。 Read it for more details!

【讨论】:

【参考方案9】:

您可能想查看Regular-Expressions or RegEx。它已经被证明并且已经存在了很长时间。所有主要的编程/脚本语言都有一个正则表达式库。

库:

C++: what regex library should I use? C# Regex Class

用法

Regex Email validation Regex to validate date format dd/mm/yyyy

【讨论】:

以上是关于指定值约束的通用语言的主要内容,如果未能解决你的问题,请参考以下文章

具有通用联合约束的 TypeScript 函数返回值

实现具有类型约束的通用接口

C# 中是不是有带有参数约束的通用构造函数?

通用约束,其中 T : struct 和 where T : class

任何人都知道缺少枚举通用约束的好方法吗?

任何人都知道缺少枚举通用约束的好方法吗?