由于我从未学习过计算机科学,所以我从未参加过一个编译器类,因此我希望获得一些反馈,以了解我是否正在使用会导致“可用”语言的技术。
该语言当前确实非常小。它具有一个“打印”语句,变量,表达式求值,注释和
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个令牌。
#1 楼
C风格好吧,我首先要说的是您的代码非常类似于C。这里的第一步是将所有这些松散的函数和全局变量移入两个C ++类
Lexer
和Parser
中。 。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::string
和std::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
评论
如果您有和语言一样复杂的东西,那么就该出发使用适当的工具了。 Lex / Yacc(或后代)。