嵌套循环用于简单的逻辑解析器

时间:2017-12-08 14:50:17

标签: c++ loops parsing arduino

我正在为一种简单的编程语言编写解析器,该编程语言可能包含轴编号,双字母命令和可能的输入值。所有命令都以逗号分隔。我有一个解析器,它通过描绘符分割输入并一次运行一个有效命令。我在编写循环函数RP时遇到问题。

我可以有这样的命令

MD1,TP,RP5,TT,RP10

我希望它以

的形式运行
for (int i = 0; i < 10; i++) {
    TT();
    for (int j = 0; j < 5; j++) {
        TP();
    }
}

到目前为止,我所拥有的主解析器将看到第一个RP命令并运行该命令,然后查看第二个RP命令并运行它。 RP命令设置为从最后一个RP命令的末尾循环,给出更多类似的东西。

for (int j = 0; j < 5; j++) {
    TP();
}  
for (int i = 0; i < 10; i++) {
    TT();
}

我尝试过几种不同的方法,但到目前为止还没有运气。任何和所有的帮助表示赞赏。

1 个答案:

答案 0 :(得分:1)

实际上,我认为这个问题有点过于宽泛。另一方面,我无法抗拒&#34;尝试&#34;。

前言

首先,我想批评(一点点)问题标题。 简单逻辑解析器 对我来说就像boolean expressions的翻译一样。但是,我记得我的工程师同事经常谈论&#34;程序逻辑&#34; (我还没有实现他们摆脱这个)。因此,我的建议是:如果你(提问者)正在与计算机科学家交谈,请使用术语&#34;逻辑&#34;明智的(或者有时他们可能看起来很困惑......)

示例代码MD1,TP,RP5,TT,RP10看起来对我来说很熟悉。一个简短的谷歌/维基百科研究清除了我的想法:维基百科文章Numerical control是关于数控机床。接近文章的最后,提到了编程。 (德语&#34; sibling&#34;文章提供了更多。)恕我直言,代码看起来有点类似但似乎更简单。 (没有冒犯 - 我认为保持尽可能简单是好的。)

似乎有意的程序符号有点像Reverse Polish notation。我想至少提到这个术语是谷歌搜索&#34; rpn interpreter&#34;抛出了很多足够的点击,包括github网站。实际上,对目标语言的描述有点太短,无法确定哪个现有的S / W项目是合适的。

说完这些,我想展示我得到的......

分析器

我首先使用解析器(因为提问者不敢暴露他)。这是mci1.cc的代码:

#include <iostream>
#include <sstream>

using namespace std;

typedef unsigned char uchar;

enum Token {
  TkMD = 'M' | 'D' << 8,
  TkRP = 'R' | 'P' << 8,
  TkTP = 'T' | 'P' << 8,
  TkTT = 'T' | 'T' << 8
};

inline Token tokenize(uchar c0, uchar c1) { return (Token)(c0 | c1 << 8); }

bool parse(istream &in)
{
  for (;;) {
    // read command (2 chars)
    char cmd[2];
    if (in >> cmd[0] >> cmd[1]) {
      //cout << "DEBUG: token: " << hex << tokenize(cmd[0], cmd[1]) << endl;
      switch (tokenize(cmd[0], cmd[1])) {
        case TkMD: { // MD<num>
          int num;
          if (in >> num) {
            cout << "Received 'MD" << dec << num << "'." << endl;
          } else {
            cerr << "ERROR: Number expected after 'MD'!" << endl;
            return false;
          }
        } break;
        case TkRP:  { // RP<num>
          int num;
          if (in >> num) {
            cout << "Received 'RP" << dec << num << "'." << endl;
          } else {
            cerr << "ERROR: Number expected after 'RP'!" << endl;
            return false;
          }
        } break;
        case TkTP: // TP
          cout << "Received 'TP'." << endl;
          break;
        case TkTT: // TT
          cout << "Received 'TT'." << endl;
          break;
        default:
          cerr << "ERROR: Wrong command '" << cmd[0] << cmd[1] << "'!" << endl;
          return false;
      }
    } else {
      cerr << "ERROR: Command expected!" << endl;
      return false;
    }
    // try to read separator
    char sep;
    if (!(in >> sep)) break; // probably EOF (further checks possible)
    if (sep != ',') {
      cerr << "ERROR: ',' expected!" << endl;
      return false;
    }
  }
  return true;
}

int main()
{
  // test string
  string sample("MD1,TP,RP5,TT,RP10");
  // read test string
  istringstream in(sample);
  if (parse(in)) cout << "Done." << endl;
  else cerr << "Interpreting aborted!" << endl;
  // done
  return 0;
}

我在Windows 10上的Cygwin中使用g++bash进行了编译和测试:

$ g++ --version
g++ (GCC) 6.4.0

$ g++ -std=c++11 -o mci mci1.cc

$ ./mci
Received 'MD1'.
Received 'TP'.
Received 'RP5'.
Received 'TT'.
Received 'RP10'.
Done.

$

ideone上传了终身演示。

我介绍了函数tokenize()作为更新的一部分。 (当我刷牙和研究如何摆脱先前版本的丑陋嵌套switch时,我明白了这一点。)Tokenizing是解析中的常用技术 - 但是,实现通常是有点不同。

因此,解析器似乎有效。还不是下一个大事,但下一步还不够......

解释

为了解释已解析的命令,我开始重新编写代码。后端 - 一组可以存储和执行所需操作的类。

第一步的parse()函数成为compile()函数,其中简单的标准输出被代码构建和嵌套操作所取代。 mci2.cc

#include <cassert>
#include <iostream>
#include <stack>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

// super class of all operations
class Op {
  protected:
    Op() = default;
  public:
    virtual ~Op() = default;
    virtual void exec() const = 0;
  // disabled: (to prevent accidental usage)
    Op(const Op&) = delete;
    Op& operator=(const Op&) = delete;
};

// super class of grouping operations
class Grp: public Op {
  protected:
    vector<Op*> _pOps; // nested operations

  protected:
    Grp() = default;
    virtual ~Grp()
    {
      for (Op *pOp : _pOps) delete pOp;
    }
  public:
    void add(Op *pOp) { _pOps.push_back(pOp); }
  // disabled: (to prevent accidental usage)
    Grp(const Grp&) = delete;
    Grp& operator=(const Grp&) = delete;
};

// class for repeat op.
class RP: public Grp {
  private:
    unsigned _n; // repeat count
  public:
    RP(unsigned n): Grp(), _n(n) { }
    virtual ~RP() = default;
    virtual void exec() const
    {
      cout << "Exec. RP" << _n << endl;
      for (unsigned i = 0; i < _n; ++i) {
        for (const Op *pOp : _pOps) pOp->exec();
      }
    }
  // disabled: (to prevent accidental usage)
    RP(const RP&) = delete;
    RP& operator=(const RP&) = delete;
};

// class for TP op.
class TP: public Op {
  public:
    TP() = default;
    virtual ~TP() = default;
    virtual void exec() const
    {
      cout << "Exec. TP" << endl;
    }    
};

// class for TT op.
class TT: public Op {
  public:
    TT() = default;
    virtual ~TT() = default;
    virtual void exec() const
    {
      cout << "Exec. TT" << endl;
    }    
};

// class for MD sequence
class MD: public Grp {
  private:
    unsigned _axis;
  public:
    MD(unsigned axis): Grp(), _axis(axis) { }
    virtual ~MD() = default;
    virtual void exec() const
    {
      cout << "Exec. MD" << _axis << endl;
      for (const Op *pOp : _pOps) pOp->exec();
    }
};

typedef unsigned char uchar;

enum Token {
  TkMD = 'M' | 'D' << 8,
  TkRP = 'R' | 'P' << 8,
  TkTP = 'T' | 'P' << 8,
  TkTT = 'T' | 'T' << 8
};

inline Token tokenize(uchar c0, uchar c1) { return (Token)(c0 | c1 << 8); }

MD* compile(istream &in)
{
  MD *pMD = nullptr;
  stack<Op*> pOpsNested;
#define ERROR \
  delete pMD; \
  while (pOpsNested.size()) { delete pOpsNested.top(); pOpsNested.pop(); } \
  return nullptr
  for (;;) {
    // read command (2 chars)
    char cmd[2];
    if (in >> cmd[0] >> cmd[1]) {
      //cout << "DEBUG: token: " << hex << tokenize(cmd[0], cmd[1]) << dec << endl;
      switch (tokenize(cmd[0], cmd[1])) {
        case TkMD: { // MD<num>
          int num;
          if (in >> num) {
            if (pMD) {
              cerr << "ERROR: Unexpected command 'MD" << num << "'!" << endl;
              ERROR;
            }
            pMD = new MD(num);
          } else {
            cerr << "ERROR: Number expected after 'MD'!" << endl;
            ERROR;
          }
        } break;
        case TkRP:  { // RP<num>
          int num;
          if (in >> num) {
            if (!pMD) {
              cerr << "ERROR: Unexpected command 'RP" << num << "'!" << endl;
              ERROR;
            }
            RP *pRP = new RP(num);
            while (pOpsNested.size()) {
              pRP->add(pOpsNested.top());
              pOpsNested.pop();
            }
            pOpsNested.push(pRP);
          } else {
            cerr << "ERROR: Number expected after 'RP'!" << endl;
            ERROR;
          }
        } break;
        case TkTP: { // TP
          if (!pMD) {
            cerr << "ERROR: Unexpected command 'TP'!" << endl;
            ERROR;
          }
          pOpsNested.push(new TP());
        } break;
        case TkTT: { // TT
          if (pOpsNested.empty()) {
            cerr << "ERROR: Unexpected command 'TT'!" << endl;
            ERROR;
          }
          pOpsNested.push(new TT());
        } break;
        default:
          cerr << "ERROR: Wrong command '" << cmd[0] << cmd[1] << "'!" << endl;
          ERROR;
      }
    } else {
      cerr << "ERROR: Command expected!" << endl;
      ERROR;
    }
    // try to read separator
    char sep;
    if (!(in >> sep)) break; // probably EOF (further checks possible)
    if (sep != ',') {
      cerr << "ERROR: ',' expected!" << endl;
      ERROR;
    }
  }
#undef ERROR
  assert(pMD != nullptr);
  while (pOpsNested.size()) {
    pMD->add(pOpsNested.top());
    pOpsNested.pop();
  }
  return pMD;
}

int main()
{
  // test string
  string sample("MD1,TP,RP3,TT,RP2");
  // read test string
  istringstream in(sample);
  MD *pMD = compile(in);
  if (!pMD) {
    cerr << "Interpreting aborted!" << endl;
    return 1;
  }
  // execute sequence
  pMD->exec();
  delete pMD;
  // done
  return 0;
}

同样,我在Windows 10上的Cygwin中使用g++bash进行了编译和测试:

$ g++ -std=c++11 -o mci mci2.cc

$ ./mci
Exec. MD1
Exec. RP2
Exec. TT
Exec. RP3
Exec. TP
Exec. TP
Exec. TP
Exec. TT
Exec. RP3
Exec. TP
Exec. TP
Exec. TP

$

ideone上传了终身演示。

嵌套技巧在compile()函数中非常简单:

  • 将命令TPTT添加到临时堆栈pOpsNested

  • 对于命令RP,所有收集的操作都会添加到弹出RP堆栈的pOpsNested实例中(从而颠倒它们的顺序),
    之后,RP实例本身被推入pOpsNested堆栈而不是

  • 最后,缓冲区pOpsNested的内容被添加到序列MD(因为这些是顶级操作)。