在过去的几个月中,我一直在研究自己的编程语言,这是一种叫做Reedoo的爱好。它在纯C ++ 11中实现,并且仅使用C ++随附的库。没有外部的。我从未学习过计算机科学,但是到目前为止,我在学校已经完成了大约2周的软件开发工作,仅此而已。

由于我从未学习过计算机科学,所以我从未参加过一个编译器类,因此我希望获得一些反馈,以了解我是否正在使用会导致“可用”语言的技术。

该语言当前确实非常小。它具有一个“打印”语句,变量,表达式求值,注释和if语句。

这是GitHub上代码的链接。

这是一个演示所有内容的简单程序到目前为止的功能:

if "Hello World" == "Hello World" and "Reedoo" == "reedoo" {
    print "This part of the block shouldn't run"
} else {
    print "This should be displayed in the console."
    print 10 + 2 * (5 + 4)    # Example of expression evaluation and a comment
}


它如何工作,当前:文件并标识令牌。这是上面程序中的令牌: “ sc”令牌代表分号,词法分析器插入分号代替新行。当词法分析器找到条件时(例如在if语句中),它将条件的各个部分加在一起,直到找到打开的大括号标记为止。然后获取令牌,然后将令牌一一添加在一起,直到它与解析器中的模式之一匹配为止。解析器也是手写的。

解析器也会调用我编写的其他函数,例如,在评估表达式时,解析器会调用我编写的返回表达式结果的函数。

评估条件时,解析器执行另一个返回true或false的函数。

变量

变量存储在C ++映射中。

我还在词法分析器/解析器和相关功能的所有方面广泛使用向量。这对性能有害吗?

最后,解析器只执行所看到的代码。我对树的实现还不了解。

我只想对我的项目提供一些反馈。我想知道我是否按照正确的方式行事。

这是词法分析器代码:

if
cond:string:"Hello World" eqeq string:"Hello World" and string:"Reedoo" eqeq string:"reedoo" 
opencb
print
string:"This part of the block shouldn't run"
sc
closecb
else
cond:string:"Hello World" eqeq string:"Hello World" and string:"Reedoo" eqeq string:"reedoo" 
opencb
print
string:"This should be displayed in the console."
sc
print
expr:(10+2*(5+4))
sc
closecb
sc


很长,但我会解释它是如何工作的。有一个名为tok的变量。每次循环运行时,tok的长度都会增加1个字符,直到等于reserved数组中的关键字之一。

字符串是相似的。变量s用于在识别字符串时将其保存,当识别出整个字符串时,将令牌推入令牌向量。

数字的识别方法与字符串相同。唯一的区别是使用了变量n

条件的识别与此类似。找到一个if关键字后,词法分析器会假定跟随它的所有内容都是条件的一部分,直到看到一个closecb令牌为止。
解析器实际上非常简单,它只是遍历令牌,每次添加下一个令牌,直到它与if语句条件之一匹配为止(如果匹配),解析器会将艰苦的工作交给其他功能在单独的文件中。

然后解析器将迭代器压入,例如,如果解析器识别的语句长3个令牌,则解析器将迭代器压入3个令牌。

评论

如果您有和语言一样复杂的东西,那么就该出发使用适当的工具了。 Lex / Yacc(或后代)。

#1 楼

C风格

好吧,我首先要说的是您的代码非常类似于C。这里的第一步是将所有这些松散的函数和全局变量移入两个C ++类LexerParser中。 。
lex()是冠军,在函数顶部堆积了18个变量。
绝对不要在C ++中这样做。在使用点声明一个变量,将范围限制在需要var的位置。

for循环中的计数器尤其应在循环内声明:

int i;
for (i = 0; i < prog.size(); ++i) // poor practice
----
for (int i = 0; i < prog.size(); ++i) // much better



还有一个古老的C习惯是在各处使用/**/(多行注释)。为什么不对单衬板使用更实用的//?单行注释现在甚至在C语言中也有效。


#define IO_ERROR "[IO ERROR] "
#define SYNTAX_ERROR "[SYNTAX ERROR] "
#define ASSIGN_ERROR "[ASSIGN ERROR] "


这些宏也不是很好。您应该首选const std::string
const char[]。对于以文件为作用域的变量/常量,在一个未命名的命名空间中声明它们是一个好习惯,有效地使这些常量在文件范围内生效: >您拥有的其他全局变量应成为类的成员,但如果要保留为全局变量,则还应使用未命名的命名空间将它们包装起来。

>
已经提到过了,我第二点。不要使用命名空间std
,因为这违反了命名空间的目的,即允许相同的名称共存而不会冲突。

可能的错误?


namespace 
{
    const std::string IO_ERROR     { "[IO ERROR] "     };
    const std::string SYNTAX_ERROR { "[SYNTAX ERROR] " };
    const std::string ASSIGN_ERROR { "[ASSIGN ERROR] " };
}



此函数从0迭代到8,但是using namespace std数组
有14个元素。这是故意还是错误?但是无论如何该功能都没有用,因为标准库提供了reserved,这是用于这种线性搜索并更清楚地传达意图的。

剩余的注释行

bool rdo_is_reserved(string tok) {
  int i;
  for (i = 0; i < 9;i++) {
    if (tok == reserved[i])
      return true;
    else
      return false;
  }
  return false;
}


请清理注释掉的行。这仅可能导致分心并使读者困惑。此外,由于
您不知道注释掉的代码是否正确,因此它不传递任何重要信息。巨大的野兽。我不想成为可怜的程序员,负责修改或修复由它引起的错误。此功能需要紧急重构。应该将其分解为几种辅助方法。这有点苛刻,你不觉得吗?像这样终止程序,没有清除或错误消息。相反,它应该引发异常。这是C ++,我们有更现代的错误处理方式,此外,调用者有可能从错误中恢复。


std::find()和丢失的标签

lex()函数exit()中有一​​个标签,我希望它在某处与lex()语句配对。但是,我在函数内找不到单个exit(1)。那么,那个标签在那儿干什么?我真的不认为goto在现代C ++中仍然占有一席之地,就像我之前说过的那样,该语言具有异常和自动资源管理功能,因此parse()相当过时。 s

您的代码也被TOP:调用打乱了,大多数都被注释掉了。
您不应该像在函数内部那样直接使用它。大多数用户都不希望在每次执行程序时都输出冗长的内容。如果需要调试
,更好的方法是将这些调用包装到可以在编译时禁用的宏中,例如:

if (n != "") {
  //is_expr = 1;
  //lex_tokens.push_back(reserved[7] + ":" + n);
}


或使用更好,更成熟的日志库。进行搜索,您会发现很多不错的选择。

评论


\ $ \ begingroup \ $
#define DEBUG_LOG(msg)do {std :: cout << msg << std :: end; } while(0)会更好。
\ $ \ endgroup \ $
–user52292
2014年9月16日下午6:32

#2 楼

看来这将是一个很大的项目。我会尝试给你一些提示(我已经编写了类似ECMA-262 / JavaScript编译器和带有自定义字节码的iterpreter作为学校项目的东西)。可能了解LEX,YACC和BISON,但您必须了解解析器的形式语法。建议开始编写一些自定义类和枚举而不是所有那些字符串:

std::string reserved[14] = { "print", "string", "sc", "variable", "eq", "undefined",
    "nl", "num", "expr", "eof", "if", "else", "and", "or" };


您应该使用一些常量使代码可读和可维护: br />
enum LaxarWords {
    lxPrint, lxString, lxSemiColon, ...


然后,您可以立即使用它们产生一些字节码,而不是文本表示形式。这不是必需条件,而是个人提示-实际上,我从小型计算器,从后缀到后缀和从前缀到前缀的转换算法,表达式评估开始,最后以ECMA-262结尾。我创建的字节码使用了前缀表示法,如下所示:指针)。不需要字节码,应该具有指针的良好类/结构。

当我阅读您的代码时,我注意到using namespace std;,但是std::stringstd::vector。您可能应该选择是否要使用std::前缀或放置一些using std::string等。使用using namespace std可能会遇到意想不到的麻烦。 br />首先,如果不需要在解析器中更改令牌,则必须复制整个vector,使用const vector<string>&会更好。请在单独的行上贴上TOP:标签,这看起来很难看。

该IF确实需要一些工作,一些解析助手(例如get_word)和单独的if(current_token == "print") { ...(首先将字符串提取到某个变量中)。

评论


\ $ \ begingroup \ $
感谢您的反馈!我将带上它并修理我的翻译。 :D
\ $ \ endgroup \ $
–弗朗西斯
2014年9月15日20:07

\ $ \ begingroup \ $
您是否考虑过嵌套if con {如果cond2 {...?您将需要递归,并因此需要更改parser()签名-它应该返回解析结束的位置(或size_t&作为参数)。
\ $ \ endgroup \ $
–user52292
2014年9月16日下午6:24

\ $ \ begingroup \ $
我不确定为什么,但是可以。我会尽力不破坏它。 :D
\ $ \ endgroup \ $
–弗朗西斯
2014年9月16日17:11