解释器模式通过将语法规则映射为类,实现语言解析器的可扩展性与直观性,核心组件包括抽象表达式、终结符、非终结符和上下文,支持递归解释执行;其优势在于易于扩展和维护,适合简单DSL,但类数量随语法复杂度增长,性能较低,不适用于高性能场景。

C++解释器模式在构建简单语言解析器时,本质上是将语言的每个语法规则映射到一个类结构,从而允许我们用面向对象的方式来表示和解释这些规则。这就像是为我们的迷你语言创建了一个微型的“大脑”,它知道如何理解并执行每个指令,让语言本身变得可执行。在我看来,这种模式的魅力在于它的扩展性和直观性,你几乎可以“看到”语法树在代码中生长。
要用C++解释器模式实现一个简单的语言解析器,我们通常会围绕几个核心组件来构建:抽象表达式(AbstractExpression)、终结符表达式(TerminalExpression)、非终结符表达式(NonterminalExpression)和上下文(Context)。
首先,我们需要定义一个抽象基类
AbstractExpression
interpret
Context
Context
接着,我们会为语言中的每一个终结符(比如数字、变量名、布尔值)创建一个
TerminalExpression
interpret
NumberExpression
VariableExpression
Context
立即学习“C++免费学习笔记(深入)”;
然后,对于语言中的非终结符(比如加法、减法、赋值、条件语句),我们创建
NonterminalExpression
AbstractExpression
interpret
interpret
AddExpression
interpret
最后,
Context
std::map<std::string, int>
Context
整个解析过程大致分为两步:
AbstractExpression
interpret
以一个简单的算术表达式语言为例:
Expression ::= Term | Expression '+' Term | Expression '-' Term
Term ::= Factor | Term '*' Factor | Term '/' Factor
Factor ::= Number | Identifier | '(' Expression ')'我们可以有
NumberExpression
VariableExpression
AddExpression
SubtractExpression
MultiplyExpression
DivideExpression
#include <iostream>
#include <map>
#include <string>
#include <stdexcept>
#include <memory> // For std::unique_ptr
// 前置声明
class Context;
// 抽象表达式
class AbstractExpression {
public:
virtual int interpret(Context& context) = 0;
virtual ~AbstractExpression() = default;
};
// 上下文:存储变量等运行时信息
class Context {
public:
std::map<std::string, int> variables;
void assign(const std::string& varName, int value) {
variables[varName] = value;
}
int lookup(const std::string& varName) const {
auto it = variables.find(varName);
if (it != variables.end()) {
return it->second;
}
throw std::runtime_error("Undefined variable: " + varName);
}
};
// 终结符表达式:数字
class NumberExpression : public AbstractExpression {
int number;
public:
NumberExpression(int num) : number(num) {}
int interpret(Context& context) override {
return number;
}
};
// 终结符表达式:变量
class VariableExpression : public AbstractExpression {
std::string name;
public:
VariableExpression(const std::string& varName) : name(varName) {}
int interpret(Context& context) override {
return context.lookup(name);
}
const std::string& getName() const { return name; } // 用于赋值操作
};
// 非终结符表达式:加法
class AddExpression : public AbstractExpression {
std::unique_ptr<AbstractExpression> left;
std::unique_ptr<AbstractExpression> right;
public:
AddExpression(std::unique_ptr<AbstractExpression> l, std::unique_ptr<AbstractExpression> r)
: left(std::move(l)), right(std::move(r)) {}
int interpret(Context& context) override {
return left->interpret(context) + right->interpret(context);
}
};
// 非终结符表达式:减法
class SubtractExpression : public AbstractExpression {
std::unique_ptr<AbstractExpression> left;
std::unique_ptr<AbstractExpression> right;
public:
SubtractExpression(std::unique_ptr<AbstractExpression> l, std::unique_ptr<AbstractExpression> r)
: left(std::move(l)), right(std::move(r)) {}
int interpret(Context& context) override {
return left->interpret(context) - right->interpret(context);
}
};
// 非终结符表达式:赋值
class AssignmentExpression : public AbstractExpression {
std::unique_ptr<VariableExpression> var;
std::unique_ptr<AbstractExpression> expr;
public:
AssignmentExpression(std::unique_ptr<VariableExpression> v, std::unique_ptr<AbstractExpression> e)
: var(std::move(v)), expr(std::move(e)) {}
int interpret(Context& context) override {
int value = expr->interpret(context);
context.assign(var->getName(), value);
return value;
}
};
// 客户端代码示例(构建AST并解释)
/*
int main() {
Context context;
// 构建表达式:a = 10
// std::unique_ptr<AbstractExpression> assign_a =
// std::make_unique<AssignmentExpression>(
// std::make_unique<VariableExpression>("a"),
// std::make_unique<NumberExpression>(10)
// );
// assign_a->interpret(context); // 执行赋值
// 构建表达式:b = 5
// std::unique_ptr<AbstractExpression> assign_b =
// std::make_unique<AssignmentExpression>(
// std::make_unique<VariableExpression>("b"),
// std::make_unique<NumberExpression>(5)
// );
// assign_b->interpret(context); // 执行赋值
// 构建表达式:a + b
// std::unique_ptr<AbstractExpression> expression =
// std::make_unique<AddExpression>(
// std::make_unique<VariableExpression>("a"),
// std::make_unique<VariableExpression>("b")
// );
// int result = expression->interpret(context);
// std::cout << "Result: " << result << std::endl; // 应该输出 15
return 0;
}
*/上述代码片段展示了核心的类结构,但缺少了从字符串到AST的构建过程。在实际应用中,你需要一个解析器(parser)来读取输入字符串,并根据语法规则创建这些
Expression
在我看来,解释器模式在构建语言解析器时,最直观的优势在于它的可扩展性。当你的语言需要增加新的语法规则或操作时,你通常只需要添加新的
TerminalExpression
NonterminalExpression
然而,凡事都有两面性。解释器模式的局限性也同样明显。对于复杂的语法,表达式类的数量会呈爆炸式增长,维护起来会变得非常困难,甚至让人望而却步。想象一下,如果你的语言包含几十种操作符、复杂的控制流和数据结构,那么对应的类文件可能会多到让你头晕。此外,由于其高度的面向对象特性和递归调用,解释器模式在性能上可能不如直接编译或更优化的解析器。每次解释都需要遍历AST,这在处理大量或高性能要求的代码时可能会成为瓶颈。坦白说,如果你的目标是构建一个高性能的通用编程语言解析器,解释器模式可能不是首选,它更适合那些语法相对简单、需要高扩展性的场景。
设计一个有效的语法结构是构建任何语言解析器的基石,C++解释器模式也不例外。在我看来,最开始的一步,也是最重要的一步,是明确你的语言能做什么,它的核心功能是什么。你希望它能处理算术运算?变量赋值?条件判断?还是循环?一旦这些核心功能确定了,就可以着手定义形式化的语法规则了。
我个人比较喜欢从BNF(巴科斯范式)或EBNF(扩展巴科斯范式)开始。这两种形式能清晰地描述语言的句法结构。例如,一个极简的算术和赋值语言可能包含以下规则:
// 语句:可以是赋值,也可以是表达式
Statement ::= Assignment | Expression
// 赋值:变量名 = 表达式
Assignment ::= Identifier '=' Expression
// 表达式:可以是一个项,也可以是项之间通过加减连接
Expression ::= Term (('+' | '-') Term)*
// 项:可以是一个因子,也可以是因子之间通过乘除连接
Term ::= Factor (('*' | '/') Factor)*
// 因子:可以是数字、变量名,或者括号括起来的表达式
Factor ::= Number | Identifier | '(' Expression ')'
// 终结符定义
Number ::= [0-9]+
Identifier ::= [a-zA-Z_][a-zA-Z0-9_]*这里需要注意几个关键点:
Term
Factor
a - b - c
(a - b) - c
Expression ::= Expression '+' Term
一旦有了这样的语法定义,将其映射到解释器模式的类结构就变得相对直接了。每个非终结符通常对应一个
NonterminalExpression
TerminalExpression
AddExpression
AbstractExpression
NumberExpression
在C++中实现解释器模式时,
Context
Context
对于最简单的语言,比如只有全局变量的算术表达式,
Context
std::map<std::string, int>
std::map<std::string, ValueObject>
interpret
VariableExpression
map
AssignmentExpression
map
// 简单的全局上下文示例
class Context {
public:
std::map<std::string, int> variables;
void assign(const std::string& varName, int value) {
variables[varName] = value;
// std::cout << "Assigned " << varName << " = " << value << std::endl; // 调试输出
}
int lookup(const std::string& varName) const {
auto it = variables.find(varName);
if (it != variables.end()) {
return it->second;
}
// 在实际应用中,这里应该抛出更具体的错误或返回一个默认值
throw std::runtime_error("Runtime Error: Undefined variable '" + varName + "'");
}
};然而,如果你的语言支持函数、代码块(如
if
for
{}Context
map
map
// 支持作用域的上下文示例
class Context {
public:
// 栈中的每个map代表一个作用域
std::vector<std::map<std::string, int>> scopes;
Context() {
scopes.push_back({}); // 初始时有一个全局作用域
}
void enterScope() {
scopes.push_back({}); // 压入新的局部作用域
// std::cout << "Entered new scope. Total scopes: " << scopes.size() << std::endl;
}
void exitScope() {
if (scopes.size() > 1) { // 至少保留一个全局作用域
scopes.pop_back();
// std::cout << "Exited scope. Total scopes: " << scopes.size() << std::endl;
} else {
throw std::runtime_error("Cannot exit global scope.");
}
}
void assign(const std::string& varName, int value) {
// 优先在当前作用域(栈顶)赋值
if (!scopes.empty()) {
scopes.back()[varName] = value;
} else {
throw std::runtime_error("No active scope to assign variable.");
}
}
int lookup(const std::string& varName) const {
// 从当前作用域开始,向上查找
for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) {
auto var_it = it->find(varName);
if (var_it != it->end()) {
return var_it->second;
}
}
throw std::runtime_error("Runtime Error: Undefined variable '" + varName + "'");
}
};
// 假设我们有一个BlockExpression,它会管理作用域
/*
class BlockExpression : public AbstractExpression {
std::vector<std::unique_ptr<AbstractExpression>> statements;
public:
BlockExpression(std::vector<std::unique_ptr<AbstractExpression>> s)
: statements(std::move(s)) {}
int interpret(Context& context) override {
context.enterScope(); // 进入新的作用域
int last_result = 0;
for (const auto& stmt : statements) {
last_result = stmt->interpret(context); // 执行语句
}
context.exitScope(); // 退出作用域
return last_result;
}
};
*/这种栈式的
Context
Context
以上就是C++解释器模式实现简单语言解析器的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号