在解释来自结构整齐的用户命令的数据时使用的最干净的数据结构(在c++中)

Cleanest data structure to use when interpreting data from neatly-structured user commands (in C++)

本文关键字:数据结构 c++ 数据 解释 结构 命令 用户      更新时间:2023-10-16

我想写一个简单的内部程序,解析用我们团队自己发明的语言编写的用户命令(但密切基于我们已经熟悉的另一个程序)。我现在正在处理的命令解析器只是一个UI,用户可以通过它运行我已经编写的其他算法。(顺便说一下,这些其他算法是用来为一个名为LAMMPS的分子动力学模拟包生成输入文件的。)我剩下唯一要做的事情就是编写这个UI,但事实证明,编写自己的脚本语言对于一个非软件工程师来说几乎是一个棘手的挑战。

根据我收到的答案,我试图做的将被认为是一种领域特定语言,并且不建议尝试制作自己的DSL,因为需要大量的工作使其有用和无bug。

最好的选择实际上是使用现有的脚本语言,如Lua或Python,并将其嵌入到程序中。要做到这一点,我将最有可能使用Lua,因为它似乎最适合我们的需要。所以在这一点上,这个问题的其余部分不再相关,因为答案将是:"不要自己做。"但我仍然会保留其中的一部分,以便其他用户能够阅读并从下面的精彩答案中学习。

再次感谢所有回复的人!


老问题:

我想写一个程序,解析用户文本输入,然后运行与该输入相对应的函数。要做到这一点,我需要解析字符串以查找相关关键字。我相信会有的当我完成时少于15个关键字,所以理想情况下我喜欢这个代码要简洁明了。

问题是,我目前正在使用if语句来解析字符串。这是一种非常不方便的解析命令的方式因为即使是一个简短的3字命令,代码也会爆炸成嵌套的if3层深。所以长8个以上单词的句子将成为嵌套的——这超过了8层深

这种编程方法很快变得难以管理,特别是当我需要对某个命令进行重大修改时,


我的问题是c++中是否存在这样的数据结构能帮我更好地管理我的巨大的嵌套假设吗,或者有人能给点建议吗为许多不同的数据类型解析字符串的更好方法(例如:并在预期的情况下输出错误消息没有找到类型?


下面是一个简短的用户会话示例,用于显示各种命令我想解释一下:

load "Basis.Silicon" as material 1
add material 1 to layer 1
rotate layer 1 about x-axis by 45 degrees
translate layer 1 in x-axis by 10 nm
generate crystal

这些命令是基于我们团队已经存在的程序使用,但不幸的是这个程序的源代码从来没有公开发布了,所以我只能猜测它到底是怎么回事实施。

最后一点,与自然语言处理器不同,我确切地知道每一行的格式将是。所以我的问题不是如何解释文本,而是如何以一种简洁和可管理的方式编码逻辑。

谢谢大家!

你的问题不清楚。你的目标比你想象的要难。

或者你认为你想以某种方式处理人类语言句子(例如英语)。如果你想学习自然语言处理,你可以找到一些与该领域相关的库。

或者你认为你想要解释一些正式的编程或脚本语言。然后你想学习解释器和编译器。顺便说一句,在这种情况下,你可以在你的程序中嵌入一个现有的解释器(如Lua, Guile, Python等....)。

你也可以考虑使用由规则组成的知识库的专家系统(这种方法可以被视为介于NLP和脚本语言之间),然后你将需要一些推理引擎(可能是CLIPS)。参见J.Pitrat的博客。

请注意,即使编写一个简单的解释器也比你想象的要困难得多。您绝对需要表示抽象语法树,这是通过解析阶段从文本输入构建的。

顺便说一句,所有的NLP、专家系统和解释器的设计和实现都是困难的领域。你可以在这三个领域都获得博士学位(但你必须选择其中一个)。

如果你选择嵌入式解释器的方式:研究我提到的解释器(Guile, Lua, Python, Neko等),然后选择你想要嵌入的。

如果出于某种原因,你想从头开始制作解释器:首先学习几种编程语言(包括脚本语言,如Ruby, Python, Ocaml, Scheme, Lua, Neko等)。阅读有关编程语言语用学(M.Scott)和Lisp In Small Pieces (Queinnec)的书籍。还要阅读有关编译和解析、垃圾收集和形式化(例如指义)语义的教科书。这一切可能需要十几年的工作。

请注意,根据经验,在解释器中嵌入软件是一种非常结构化的设计。如果您一开始没有想到这一点,那么您可能需要重新设计和重构大量现有的应用程序。例如,在解释器中嵌入软件时,您不能承受错误输入导致程序崩溃的后果。因此,错误处理和内存管理(与解释器的GC接口)是具有挑战性的,并给出了新的约束。因此,您需要重新考虑您的应用程序。

如果所有这些都是新的(即使你没有选择Guile作为嵌入解释器):学习和实践一点Scheme-例如使用Guile或PltScheme-(例如阅读SICP),阅读一点关于λ微积分和闭包的知识,然后阅读Queinnec的Lisp In Small Pieces书。请记住暂停问题(这也是为什么解释器很难编码的部分原因)。

顺便说一句,你提出的语法(例如rotate mat 1 by x 90)不是很好读,看起来像cobol。如果可能的话,使用一种看起来与现有语言相似的语言。 !

从阅读我在这里引用的所有维基开始

顺便说一句,我是MELT的主要作者,这是一种扩展GCC编译器的特定领域语言(受Scheme的启发很大)。我写的一些论文/文档可能会给你启发(并包含有价值的参考资料)。

补遗(问题修改后)

你似乎发明了一些形式语法,比如

add material 1 to layer 1
rotate layer 1 about x-axis by 90 degrees
translate layer 1 in x-axis by 10 inches

我猜不出这是什么语言?你正在实现3D打印机吗?如果是,您应该坚持使用该领域中现有的标准形式语言。

我认为这种类似于cobol的语法是错误的。关键是它太啰嗦了,而且您希望实现一些特定于领域的语言。我觉得你的例子很难看。

这种语法是你自己发明的,还是有一些文档指定了你的领域特定语言(成千上万已经存在的行代码)。如果你只是在发明它,请重新考虑语法和语义。首先,您需要在纸上指定DSL的完整语法和语义。

你的DSL图灵完备吗?(我想是的,因为图灵完备性是很快达到的——例如变量和循环....)。如果是,你正在发明一种脚本语言。请不要在不懂一些编程的情况下发明脚本语言。脚本语言(然后阅读编程语言语用学…)。关键是,如果您的脚本语言变得成功,高级用户迟早会用它编写重要的程序(例如数千行)。然后,这些高级用户将成为程序员。在这种情况下,它对社会是非常重要的。经济上的原因)有一个建立良好的DSL,看起来很熟悉(如果可能的话,扩展一些现有的脚本语言)。

如果您的DSL已经存在,请遵循其书面规范。如果该规范不够好,可以通过形式化来改进它(例如,为它编写一些BNF语法和一些形式化(例如,指意性)语义)。发布并与现有用户讨论该形式化。

一些行业得到了一些特别的dsl,这些dsl被广泛使用,但设计得很差(例如,在法国核工业中,GibianeDSL是由核物理学家而不是计算机科学家在20世纪70年代设计的;据传美国波音公司也犯过类似的错误)。然后,维护和改进数十万行DSL脚本将成为一场噩梦(可能意味着损失数百万美元或欧元)。因此,您最好坚持使用一些现有的脚本语言。优点是它存在一些文化(例如,你可以找到许多关于Python或Lua的书,并且许多训练有素的工程师熟悉它们),解释器被广泛使用和测试,致力于它们的社区正在改进解释器,因此它有相当少的未纠正的错误。

如果你不是一个训练有素的计算机科学家,你不应该尝试设计和实现你自己的DSL。坚持使用一些现有的脚本语言(当然它们的语法不像你想要的那样),并利用现有的实现和实验。

作为一个反例,J.Ousterhout发明了广泛使用的Tcl脚本语言,他声称脚本总是很小(例如只有几百行),不会增长到很大的代码库;不幸的是,他们中的一些人确实这样做了,Tcl被认为是一种糟糕的语言,无法编写成千上万行代码(即使Tcl对于小脚本来说是一种简单方便的语言)。这个故事的寓意是,如果一种(图灵完备的)脚本语言变得成功,一些"疯狂的"高级用户将会编写成千上万的脚本代码。所以你需要从一开始就设计好脚本语言。因此,您应该采用和适应一种好的现有的脚本语言(并且避免在没有几种现有脚本语言的良好知识的情况下发明不熟悉的语法)

之后添加

PS:我对Tcl的批评并不完全是主观的:关键是Tcl是为小脚本设计的(阅读j.s ousterhout关于Tcl的第一篇论文),但我的观点是,当你提供一种图灵完备的脚本语言时,一些"疯狂"的用户最终会为它编写大脚本。因此,您需要通过提供一种脚本语言来预测这种"疯狂"的使用,这种脚本语言可以"扩展"到大型脚本,因此是根据大型软件代码库的软件工程实践构建的。

NB。作为一种嵌入语言,Lua可能是一个不错的选择。它很小,有一个很好的实现,有很好的文档,并且有很好的性能。但是要小心内存管理问题(这个建议适用于任何脚本语言)。

编辑:为了更清楚,我希望有一个简短的关键词列表(& lt; 15)。它们的顺序/存在将决定哪个函数将运行。

你可以构建一个小的规则集引擎(例如处理单词列表的东西)。你只需要写一次引擎/函数,然后把数据结构传递给它。

作为一种替代方案,使用正则表达式的解决方案可能是最快的编码(引擎已经为您准备好了),假设您熟悉regexp语法(如果不熟悉,它仍然是一个很好的投资)。

您可以构建一个关键字和函数指针表:

typedef void (*Function_Pointer)(void);
struct table_entry
{
const char * keyword;
Function_Pointer p_function;
};
table_entry function_table[] =
{
{"car", Process_Car},
{"bike", Process_Bike},
};

在表中搜索关键字。如果找到关键字,则解除对函数指针的引用。
下面的代码片段将执行处理单词"car"的函数:

(function_table[0].p_function)();

有一个著名的程序,叫做Eliza,它可以解析句子中的关键词。
示例可以在:Eliza c++ Examples

找到