我们见过pure standard library approach。
这个executed 立即指示。
让我们创建一个解析器来构建一个 AST(抽象语法树)。对于我们简单的堆栈机器,它只是一个指令列表。我们称之为Tape。
使用 Boost Spirit
我仍然建议不要使用词法分析器。 Spirit v2 支持词法分析器(X3 还不支持?)。但在实践中,它们会使事情复杂化,Spirit 知道如何在不匹配的情况下回溯输入。因此,如果它不是正确的“令牌”,您可以试探性地匹配产品并尝试下一个。
使用 Spirit 语法应该是这样的:
Tape program;
boost::spirit::istream_iterator f(std::cin >> std::noskipws), l; // Note the noskipws!
if (parse(f, l, Parser::program, program)) {
std::cout << "Parsed " << program.size() << " instructions\n";
} else {
std::cout << "Parse failed\n";
}
现在,AST 类型是:
struct Add {};
struct Sub {};
struct Mul {};
struct Div {};
struct Pop {};
using Value = int;
using Instr = boost::variant<Add, Sub, Mul, Div, Pop, Value>;
using Tape = std::vector<Instr>;
简单,对。
语法
在 X3 中,编写语法非常轻量级。自上而下:
auto instr = opcode_ | int_;
auto program = skip(space) [*instr];
现在,我们要做的就是教它识别操作码。开始是:
struct opcodes : symbols<Instr> {
opcodes() {
this->add("add", Add{})("sub", Sub{})("mul", Mul{})("div", Div{})("pop", Pop{});
}
} opcode_;
经验丰富的 Spirit 大师会在这里发现一个问题:opcode_ 不是词位,也不能保证“独特标识符”解析。例如。 "a dd" 将匹配 Add。 "additional" 也会匹配。
幸运的是,X3 让动态编写指令变得非常容易:
auto opcode_ = [] {
struct opcodes : symbols<Instr> {
opcodes() { this->add("add", Add{})("sub", Sub{})("mul", Mul{})("div", Div{})("pop", Pop{}); }
} codes_;
return lexeme[codes_ >> !graph];
}();
所以,现在两个洞都修好了。
完整演示
Live On Coliru
#include <iostream>
#include <deque>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
struct Add {};
struct Sub {};
struct Mul {};
struct Div {};
struct Pop {};
using Value = int;
using Instr = boost::variant<Add, Sub, Mul, Div, Pop, Value>;
struct Machine {
using result_type = void;
std::deque<Value> stack_;
void operator()(Instr instr) {
boost::apply_visitor(*this, instr);
}
void operator()(Add) {
assert(stack_.size()>=2);
auto op2 = stack_.back(); stack_.pop_back();
auto op1 = stack_.back(); stack_.pop_back();
stack_.push_back(op1 + op2);
}
void operator()(Sub) {
assert(stack_.size()>=2);
auto op2 = stack_.back(); stack_.pop_back();
auto op1 = stack_.back(); stack_.pop_back();
stack_.push_back(op1 - op2);
}
void operator()(Mul) {
assert(stack_.size()>=2);
auto op2 = stack_.back(); stack_.pop_back();
auto op1 = stack_.back(); stack_.pop_back();
stack_.push_back(op1 * op2);
}
void operator()(Div) {
assert(stack_.size()>=2);
auto op2 = stack_.back(); stack_.pop_back();
auto op1 = stack_.back(); stack_.pop_back();
assert(op2 != 0);
stack_.push_back(op1 / op2);
}
void operator()(Value v) {
stack_.push_back(v);
}
void operator()(Pop) {
assert(stack_.size()>=1);
stack_.pop_back();
}
void trace() const {
using namespace std;
// debug trace
copy(stack_.begin(), stack_.end(), ostream_iterator<Value>(cout, " "));
cout << "\n";
}
};
using Tape = std::vector<Instr>;
namespace Parser {
using namespace boost::spirit::x3;
auto opcode_ = [] {
struct opcodes : symbols<Instr> {
opcodes() { this->add("add", Add{})("sub", Sub{})("mul", Mul{})("div", Div{})("pop", Pop{}); }
} codes_;
return lexeme[codes_ >> !graph];
}();
auto instr = opcode_ | int_; // TODO
auto program = skip(space) [*instr];
}
int main() {
Tape program;
boost::spirit::istream_iterator f(std::cin >> std::noskipws), l; // Note the noskipws!
if (parse(f, l, Parser::program, program)) {
std::cout << "Parsed " << program.size() << " instructions\n";
} else {
std::cout << "Parse failed\n";
}
if (f != l)
std::cout << "Unparsed program data: '" << std::string(f,l) << "'\n";
Machine machine;
for (auto instr : program)
{
machine(instr);
machine.trace();
}
}
打印:
Parsed 7 instructions
1
1 2
3
3 3
3 3 1
3 2
6
总结
这里的主要收获是: