根据特定条件从文件中检索每个令牌

时间:2018-07-22 21:07:02

标签: c++ lexer

我正在尝试为一种功能语言创建一个词法分析器,其中一种方法应允许在每次调用时返回文件的下一个标记。 例如:

func main() {
    var MyVar : integer = 3+2;
}

因此,我希望每次调用next方法时,都返回该序列中的下一个标记;在这种情况下,它看起来像这样:

func
main
(
)
{
var
MyVar
:
integer
=
3
+
2
;
}

除了我得到的结果不是我期望的那样:

func
main(
)

{





var
MyVar
:

integer
=

3+
2

}

这是我的方法:

token_t Lexer::next() {
    token_t ret;
    std::string token_tmp;
    bool IsSimpleQuote = false; // check string   --> "..."
    bool IsDoubleQuote = false; // check char     --> '...'
    bool IsComment = false;     // check comments --> `...`
    bool IterWhile = true;
    while (IterWhile) {
        bool IsInStc = (IsDoubleQuote || IsSimpleQuote || IsComment);
        std::ifstream file_tmp(this->CurrentFilename);
        if (this->eof) break;
        char chr = this->File.get();
        char next = file_tmp.seekg(this->CurrentCharIndex + 1).get();
        ++this->CurrentCharInCurrentLineIndex;
        ++this->CurrentCharIndex;
        {
            if (!IsInStc && !IsComment && chr == '`') IsComment = true; else if (!IsInStc && IsComment && chr == '`') { IsComment = false; continue; }
            if (IsComment) continue;
            if (!IsInStc && chr == '"') IsDoubleQuote = true;
            else if (!IsInStc && chr == '\'') IsSimpleQuote = true;
            else if (IsDoubleQuote && chr == '"') IsDoubleQuote = false;
            else if (IsSimpleQuote && chr == '\'') IsSimpleQuote = false;
        }
        if (chr == '\n') {
            ++this->CurrentLineIndex;
            this->CurrentCharInCurrentLineIndex = -1;
        }
        token_tmp += chr;
        if (!IsInStc && IsLangDelim(chr)) IterWhile = false;
    }
    if (token_tmp.size() > 1 && System::Text::EndsWith(token_tmp, ";") || System::Text::EndsWith(token_tmp, " ")) token_tmp.pop_back();
    ++this->NbrOfTokens;
    location_t pos;
    pos.char_pos = this->CurrentCharInCurrentLineIndex;
    pos.filename = this->CurrentFilename;
    pos.line = this->CurrentLineIndex;
    SetToken_t(&ret, token_tmp, TokenList::ToToken(token_tmp), pos);
    return ret;
}

这是IsLangDelim函数:

bool IsLangDelim(char chr) {
    return (chr == ' ' || chr == '\t' || TokenList::IsSymbol(CharToString(chr)));
}

TokenList是一个命名空间,其中包含令牌列表以及一些函数(在本例中为IsSymbol)。

我已经尝试过该方法的其他版本,但是结果几乎总是相同的。

您是否知道如何改进此方法?

2 个答案:

答案 0 :(得分:1)

您的问题的解决方案是使用std::regex。刚开始时,了解语法会有些困难,但是了解语法之后,您将始终使用它。

而且,它旨在查找令牌。

特定的标准可以在正则表达式字符串中表示。

对于您的情况,我将使用:std::regex re(R"#((\w+|\d+|[;:\(\)\{\}\+\-\*\/\%\=]))#");

这意味着:

  • 寻找一个或多个字符(即一个字)
  • 寻找一个或多个数字(即整数)
  • 或者寻找所有有意义的运算符(例如'+','-','{'等)

您可以将正则表达式扩展为要搜索的所有其他内容。您还可以对正则表达式结果进行正则表达式。

请参见下面的示例。这将从提供​​的输入中创建显示的输出。

而且,您所描述的任务只是main中的一条语句。

#include <iostream>
#include <string>
#include <algorithm>
#include <regex>

// Our test data (raw string) .
std::string testData(
R"#(func main() {
    var MyVar : integer = 3+2;
}
)#");

std::regex re(R"#((\w+|\d+|[;:\(\)\{\}\+\-\*\/\%\=]))#");

int main(void)
{
    std::copy(
        std::sregex_token_iterator(testData.begin(), testData.end(), re, 1),
        std::sregex_token_iterator(),
        std::ostream_iterator<std::string>(std::cout, "\n")
    );

    return 0;
}

答案 1 :(得分:0)

您尝试使用单循环进行解析,这使代码非常复杂。相反,我建议是这样的:

struct token { ... };
struct lexer {
  vector<token> tokens;
  string source;
  unsigned int pos;

  bool parse_ident() {
    if (!is_alpha(source[pos])) return false;
    auto start = pos;
    while(pos < source.size() && is_alnum(source[pos])) ++pos;
    tokens.push_back({ token_type::ident, source.substr(start, pos - start) });
    return true;
  }
  bool parse_num() { ... }
  bool parse_comment() { ... }
  ...
  bool parse_whitespace() { ... }
  void parse() {
    while(pos < source.size()) {
      if (!parse_comment() && !parse_ident() && !parse_num() && ... && !parse_comment()) {
        throw error{ "unexpected character at position " + std::to_string(pos) };
      }
    } 
  }

当以我编写的任何脚本语言对文件进行词法化时,这是我使用的标准结构。 Lexing通常是贪婪的,因此您不必理会regex(这是有效的,但速度较慢,除非基于某些疯狂的基于模板的实现)。只需定义您的parse_*函数,并确保它们返回false,如果它们没有解析令牌,并确保它们以正确的顺序被调用。

订单本身通常并不重要,但是:

  1. 操作员需要从最长到最短的时间进行检查
  2. 样式.123中的数字可能被错误地识别为.运算符(因此,您需要确保在.之后没有数字。
  3. 数字和标识符非常相似,除了标识符以非数字开头。