init
This commit is contained in:
182
docs/workflow_details.md
Normal file
182
docs/workflow_details.md
Normal file
@ -0,0 +1,182 @@
|
||||
# 解释器作业的完成流程
|
||||
|
||||
## Step 1. 配置环境
|
||||
|
||||
见文档 [antlr_guide.md](antlr_guide.md)。
|
||||
|
||||
## Step 2. 阅读 "./resources/Python3Parser.g4"
|
||||
|
||||
**阅读 `.g4` 文件需要一定的正则表达式基础。**
|
||||
如果你不会正则表达式,可以参考 [正则表达式 - 维基百科](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)。
|
||||
|
||||
我们假设有这样的语法规则(并不是我们这次作业的一部分):
|
||||
|
||||
```
|
||||
plus: NUMBER '+' NUMBER;
|
||||
NUMBER:[0-9]+;
|
||||
ADD:'+';
|
||||
```
|
||||
|
||||
## Step 3. 阅读 "./generated/Python3Parser.h"
|
||||
|
||||
你可以看到以下代码(对应着上面的语法规则):
|
||||
|
||||
```c++
|
||||
//...............
|
||||
class PlusContext : public antlr4::ParserRuleContext {
|
||||
public:
|
||||
PlusContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||
virtual size_t getRuleIndex() const override;
|
||||
std::vector<antlr4::tree::TerminalNode *> NUMBER();
|
||||
antlr4::tree::TerminalNode* NUMBER(size_t i);
|
||||
antlr4::tree::TerminalNode* ADD();
|
||||
|
||||
virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
|
||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||
}
|
||||
//...............
|
||||
```
|
||||
|
||||
因为在上述的 `plus` 语法中,`NUMBER` 一定要出现至少一次,所以 `PlusContext` 有以下两个函数:
|
||||
|
||||
```c++
|
||||
std::vector<antlr4::tree::TerminalNode *> NUMBER();
|
||||
antlr4::tree::TerminalNode* NUMBER(size_t i);
|
||||
```
|
||||
|
||||
第一个函数返回一个 `vector`,包含了指向所有 `NUMBER` 的指针,第二个函数返回指向第 i 个 `NUMBER` 的指针,从 0 开始。
|
||||
|
||||
因为 `ADD` 只在 `plus` 中出现了一次,所以它只有以下函数,返回指向唯一的 `ADD` 的指针:
|
||||
|
||||
```c++
|
||||
antlr4::tree::TerminalNode* ADD()
|
||||
```
|
||||
|
||||
对于一个 `temrinal node`,有以下方法:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
std::string toString()
|
||||
Token* TerminalNodeImpl::getSymbol()
|
||||
/*
|
||||
* for example, consider:
|
||||
* antlr4::tree::TerminalNode *it;
|
||||
* it->toString() returns the string, for example, "123456" or "a"
|
||||
* (so you need to converse (std::string)"123456" to (int)123456)
|
||||
* it->getSymbol()->getTokenIndex() returns where this word is in the whole input.
|
||||
*/
|
||||
//...............
|
||||
```
|
||||
|
||||
## Step 4. 完成 "./src/Evalvisitor.h"
|
||||
|
||||
在这一步中,所要做的就是补全相关代码:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
std::any visitPlus(Python3Parser::PlusContext *ctx)
|
||||
{
|
||||
/*
|
||||
* TODO
|
||||
* the pseudo-code is:
|
||||
* return visit(ctx->NUMBER(0))+visit(ctx->NUMBER(1));
|
||||
*/
|
||||
}
|
||||
//...............
|
||||
```
|
||||
|
||||
当写:
|
||||
|
||||
```c++
|
||||
visit(ctx->NUMBER(0))
|
||||
```
|
||||
|
||||
等价于写:
|
||||
|
||||
```c++
|
||||
visitAtom(ctx->NUMBER(0))
|
||||
```
|
||||
|
||||
所以我们只需要用 `visit` 函数来访问各种结点,而不是用 `visitBalabala`,想想为什么?
|
||||
|
||||
## Step 5. 编译程序
|
||||
|
||||
输入以下代码即可:
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
如果你不会使用 `cmake`,你可以借助于 `Clion` 来实现,如果你不知道这步如何操作,请询问助教。
|
||||
|
||||
## 神奇的 std::any
|
||||
|
||||
关于这个类,你只需要会一个方法:`std::any_cast`。
|
||||
|
||||
譬如说,如果有以下的语法规则:
|
||||
|
||||
```
|
||||
plus: atom '+' atom;
|
||||
atom: NUMBER | STRING+;
|
||||
NUMBER:[0-9]+;
|
||||
STRING:[A-Z]+;
|
||||
ADD:'+';
|
||||
```
|
||||
|
||||
在 Parser.h 中的 `Context` 长这样:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
class PlusContext : public antlr4::ParserRuleContext {
|
||||
public:
|
||||
PlusContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||
virtual size_t getRuleIndex() const override;
|
||||
std::vector<AtomContext*> atom();
|
||||
AtomContext* atom(size_t i);
|
||||
antlr4::tree::TerminalNode* ADD()
|
||||
|
||||
virtual void enterRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
virtual void exitRule(antlr4::tree::ParseTreeListener *listener) override;
|
||||
|
||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||
}
|
||||
//notice that the atom is AtomContext* type rather than a TerminalNode* type.
|
||||
//an easy way to tell the difference is: capital letter-TerminalNode; xxxContext otherwise
|
||||
//...............
|
||||
```
|
||||
|
||||
那么相应的代码就长这样:
|
||||
|
||||
```c++
|
||||
//...............
|
||||
std::any visitPlus(Python3Parser::PlusContext *ctx)
|
||||
{
|
||||
auto ret1 = visit(ctx->NUMBER());
|
||||
auto ret2 = visit(ctx->NUMBER());
|
||||
if (auto v1int = std::any_cast<int*>(ret1), v2int = std::any_cast<int*>(ret2);
|
||||
v1int && v2int)
|
||||
return *v1int + *v2int;
|
||||
else if (auto v1str = std::any_cast<string*>(ret1), v2str = std::any_cast<string*>(ret2);
|
||||
v1str && v2str)
|
||||
return *v1str + *v2str;
|
||||
else
|
||||
throw(std::string("unsupported operand type(s) for +: ") + ret1.type().name() + " + " + ret2.type().name());//no need
|
||||
}
|
||||
//...............
|
||||
```
|
||||
|
||||
我们保证测试文件的语法都是正确的,所以后两行实则是不需要的。
|
||||
|
||||
`any_cast` 模板函数可以直接将 `any` 类型转换为你想要的类型,但是如果转换失败,它会直接抛出异常。
|
||||
而在上文的代码中,我们向模板填入的类型是目标类型的引用,所以如果类型不匹配、转换失败,它会返回 `nullptr`。
|
||||
|
||||
在 OOP 课程中,你们将会学习构造函数和析构函数。所以你们最好是在理解 `std::any` 是如何构造与析构的基础上,进行编程。通过阅读[cppreference](https://zh.cppreference.com/w/cpp/utility/any)来理解。如果这对于你们来说太过困难,请求助助教。
|
||||
|
||||
https://www.cnblogs.com/mangoyuan/p/6446046.html
|
||||
|
||||
https://www.cnblogs.com/xiaoshiwang/p/9590029.html
|
||||
|
||||
搜索 "C++11 traits" 来获得更多信息。
|
Reference in New Issue
Block a user