C++实现解释器(4):求值¶
参照《Writing An Interpreter/Compiler In Go》,改用C++实现。
引言¶
本篇对应的源码位于目录: src/03/
03
|token
| |token.hpp
|evaluator
| |evaluator.hpp
|CMakeLists.txt
|test
| |lexer_test.hpp
| |parser_test.hpp
| |evaluator_test.hpp
| |ast_test.hpp
| |main.cpp
|lexer
| |lexer.hpp
|repl
| |repl.hpp
|objects
| |objects.hpp
| |environment.hpp
|parser
| |parser.hpp
| |parser_tracing.hpp
|ast
| |ast.hpp
|main
| |monkey.cpp
求值(Evaluation)
是解释器处理源代码过程的最后一步,求值过程决定了一门编程语言的解释方式。
求值策略¶
解释执行AST是最直观、最传统的方式,具体就是遍历AST访问每个节点并执行该节点语义;以这种方式工作的解释器叫做树遍历解释器
。
有时在求值步骤之前,会进行优化或转换为中间表示(IR)
,还有的会先将AST转换为字节码
以提高解释性能和移植性。
如果在执行之前,虚拟机即时将字节码编译为机器码执行,而不是直接执行字节码指定操作,就是JIT(Just in Time)解释器/编译器
。
递归求值AST的树遍历解释器可能是所有求值策略中最慢的一种,但其易于构建、扩展和归纳,并且能使用宿主语言的地方就能使用(嵌入)。
Ruby
解释器在1.8版本及之前就是一个树遍历解释器,从1.9版本切换到虚拟机架构,在虚拟机中执行字节码,性能大幅提高。
Lua
编程语言在发布12年后出现了LuaJIT
,将紧凑的字节码编译为高度优化的机器码,有时能获得接近50倍的提速。
许多解释器刚开始都很小也很简单,但有很大的改进余地;其实求值器实际上就是一个叫eval
的函数,对AST求值。
对象系统¶
对象系统
就是代码执行后的值如何在宿主语言中表示,又叫值系统
或对象表示方法
。
先不关心执行速度和内存消耗,从最简单实现出发,所有的值都使用基类Object
表示,具体类型的值就是它的子类:
namespace objects
{
enum class ObjectType
{
Null,
ERROR,
INTEGER,
BOOLEAN,
RETURN_VALUE,
FUNCTION
};
struct Object
{
virtual ~Object() {}
virtual ObjectType Type() { return ObjectType::Null; }
virtual std::string Inspect() { return ""; }
std::string TypeStr()
{
switch (Type())
{
case ObjectType::Null:
return "Null";
case ObjectType::ERROR:
return "ERROR";
case ObjectType::INTEGER:
return "INTEGER";
case ObjectType::BOOLEAN:
return "BOOLEAN";
case ObjectType::RETURN_VALUE:
return "RETURN_VALUE";
case ObjectType::FUNCTION:
return "FUNCTION";
default:
return "BadType";
}
}
};
}
比如整数的值:
struct Integer : Object
{
long long int Value;
Integer() {}
Integer(long long int val) : Value(val) {}
virtual ~Integer() {}
virtual ObjectType Type() { return ObjectType::INTEGER; }
virtual std::string Inspect()
{
std::stringstream oss;
oss << Value;
return oss.str();
}
};
这个Integer对象里面包含一个宿主语言真实的整数值,等于加了一层包裹,具体使用的时候进行解包。
其中空值null
是这样表示的:
struct Null : Object
{
Null() {}
virtual ~Null() {}
virtual ObjectType Type() { return ObjectType::Null; }
virtual std::string Inspect() { return "null"; }
};
符号表或作用域环境¶
对于let
语句定义的标志符,实际上是用一个将字符串和对象关联起来的哈希映射来表示的:
struct Environment
{
std::map<std::string, std::shared_ptr<Object>> store;
std::shared_ptr<Environment> outer;
~Environment(){
store.clear();
outer.reset();
}
std::shared_ptr<Object> Get(std::string name)
{
auto fit = store.find(name);
if (fit != store.end())
{
return fit->second;
}
else if (outer != nullptr)
{
return outer->Get(name);
}
else
{
return nullptr;
}
}
std::shared_ptr<Object> Set(std::string name, std::shared_ptr<Object> val)
{
auto fit = store.find(name);
if (fit != store.end())
{
fit->second.reset();
fit->second = val;
}
else
{
store.insert(std::make_pair(name, val));
}
return val;
}
};
值得注意的每个环境里面包含一个小环境outer
,这个也被叫做外部作用域环境
,层层嵌套,就是先局部作用域环境查找,如果找不到就从外层作用域环境查找,一层层向上查找,起到一个作用域优先级的效果。
遍历AST¶
最核心的是这个叫Eval
的函数,它根据每个AST节点的类型执行具体的解释方法:
std::shared_ptr<objects::Object> Eval(std::shared_ptr<ast::Node> node, std::shared_ptr<objects::Environment> env)
{
// Statements
if (node->GetNodeType() == ast::NodeType::Program)
{
std::shared_ptr<ast::Program> program = std::dynamic_pointer_cast<ast::Program>(node);
return evalProgram(program, env);
}
// else if
// ...
return nullptr;
}
它总是从根节点开始, 执行时将当前的符号表环境带入:
std::shared_ptr<objects::Object> evalProgram(std::shared_ptr<ast::Program> program,
std::shared_ptr<objects::Environment> env)
{
std::shared_ptr<objects::Object> result = std::make_shared<objects::Object>();
for (auto &stmt : program->v_pStatements)
{
result = Eval(stmt, env);
if (result == nullptr)
{
continue;
}
if (result->Type() == objects::ObjectType::RETURN_VALUE)
{
return std::dynamic_pointer_cast<objects::ReturnValue>(result)->Value;
}
else if (result->Type() == objects::ObjectType::ERROR)
{
return result;
}
}
return result;
}
它做的事情就是对AST节点遍历执行,执行过程中遇到ERROR
或Return
就中止遍历且返回。
Return值¶
//...
else if (node->GetNodeType() == ast::NodeType::ReturnStatement)
{
std::shared_ptr<ast::ReturnStatement> returnStmt = std::dynamic_pointer_cast<ast::ReturnStatement>(node);
std::shared_ptr<objects::Object> val = Eval(returnStmt->pReturnValue, env);
if (isError(val))
{
return val;
}
return std::make_shared<objects::ReturnValue>(val);
}
//...
对于返回值直接返回即可,不需要额外的处理。
整数与布尔值¶
//...
else if (node->GetNodeType() == ast::NodeType::IntegerLiteral)
{
std::shared_ptr<ast::IntegerLiteral> integerLiteral = std::dynamic_pointer_cast<ast::IntegerLiteral>(node);
return std::make_shared<objects::Integer>(integerLiteral->Value);
}
else if (node->GetNodeType() == ast::NodeType::Boolean)
{
return nativeBoolToBooleanObject(std::dynamic_pointer_cast<ast::Boolean>(node)->Value);
}
//...
其中整数就是直接返回,而布尔值为了不需要每次都创建新对象,可以共享一个全局布尔值变量:
std::shared_ptr<objects::Null> NULL_OBJ = std::make_shared<objects::Null>();
std::shared_ptr<objects::Boolean> TRUE_OBJ = std::make_shared<objects::Boolean>(true);
std::shared_ptr<objects::Boolean> FALSE_OBJ = std::make_shared<objects::Boolean>(false);
std::shared_ptr<objects::Boolean> nativeBoolToBooleanObject(bool input)
{
if (input)
{
return TRUE_OBJ;
}
else
{
return FALSE_OBJ;
}
}
Let语句¶
else if (node->GetNodeType() == ast::NodeType::LetStatement)
{
std::shared_ptr<ast::LetStatement> lit = std::dynamic_pointer_cast<ast::LetStatement>(node);
std::shared_ptr<objects::Object> val = Eval(lit->pValue, env);
if (isError(val))
{
return val;
}
env->Set(lit->pName->Value, val);
}
let
语句执行表达式获取到值后,不管值是什么类型,都存入符号表环境中。
标志符¶
//...
else if (node->GetNodeType() == ast::NodeType::Identifier)
{
std::shared_ptr<ast::Identifier> ident = std::dynamic_pointer_cast<ast::Identifier>(node);
return evalIdentifier(ident, env);
}
//...
std::shared_ptr<objects::Object> evalIdentifier(std::shared_ptr<ast::Identifier> node,
std::shared_ptr<objects::Environment> env)
{
std::shared_ptr<objects::Object> val = env->Get(node->Value);
if (val == nullptr)
{
return newError("identifier not found: " + node->Value);
}
return val;
}
标志符直接就是符号表环境取出来。
If条件语句¶
else if (node->GetNodeType() == ast::NodeType::IfExpression)
{
std::shared_ptr<ast::IfExpression> x = std::dynamic_pointer_cast<ast::IfExpression>(node);
return evalIfExpression(x, env);
}
std::shared_ptr<objects::Object> evalIfExpression(std::shared_ptr<ast::IfExpression> ie,
std::shared_ptr<objects::Environment> env)
{
std::shared_ptr<objects::Object> condition = Eval(ie->pCondition, env);
if (isError(condition))
{
return condition;
}
if (isTruthy(condition))
{
return Eval(ie->pConsequence, env);
}
else if (ie->pAlternative != nullptr)
{
return Eval(ie->pAlternative, env);
}
else
{
return NULL_OBJ;
}
}
这里值得注意的是判断条件是否为真时直接判断是否和全局变量是同一个对象:
bool isTruthy(std::shared_ptr<objects::Object> obj)
{
if (obj == NULL_OBJ)
{
return false;
}
else if (obj == TRUE_OBJ)
{
return true;
}
else if (obj == FALSE_OBJ)
{
return false;
}
else
{
return true;
}
}
表达式求值¶
else if (node->GetNodeType() == ast::NodeType::ExpressionStatement)
{
std::shared_ptr<ast::ExpressionStatement> exprStmt = std::dynamic_pointer_cast<ast::ExpressionStatement>(node);
return Eval(exprStmt->pExpression, env);
}
else if (node->GetNodeType() == ast::NodeType::PrefixExpression)
{
std::shared_ptr<ast::PrefixExpression> infixObj = std::dynamic_pointer_cast<ast::PrefixExpression>(node);
std::shared_ptr<objects::Object> right = Eval(infixObj->pRight, env);
if (isError(right))
{
return right;
}
return evalPrefixExpression(infixObj->Operator, right);
}
else if (node->GetNodeType() == ast::NodeType::InfixExpression)
{
std::shared_ptr<ast::InfixExpression> infixObj = std::dynamic_pointer_cast<ast::InfixExpression>(node);
std::shared_ptr<objects::Object> left = Eval(infixObj->pLeft, env);
if (isError(left))
{
return left;
}
std::shared_ptr<objects::Object> right = Eval(infixObj->pRight, env);
if (isError(right))
{
return right;
}
return evalInfixExpression(infixObj->Operator, left, right);
}
目前前缀表达式只支持!
和-
:
std::shared_ptr<objects::Object> evalPrefixExpression(std::string ops,
std::shared_ptr<objects::Object> right)
{
if (ops == "!")
{
return evalBangOperatorExpression(right);
}
else if (ops == "-")
{
return evalMinusPrefixOperatorExpression(right);
}
else
{
return newError("unknown operator: " + ops + right->TypeStr());
}
}
而中缀表达式目前只对整数有所支持:
std::shared_ptr<objects::Object> evalIntegerInfixExpression(std::string ops,
std::shared_ptr<objects::Object> left,
std::shared_ptr<objects::Object> right)
{
long long int leftValue = std::dynamic_pointer_cast<objects::Integer>(left)->Value;
long long int rightValue = std::dynamic_pointer_cast<objects::Integer>(right)->Value;
std::shared_ptr<objects::Integer> result = std::make_shared<objects::Integer>();
if (ops == "+")
{
result->Value = leftValue + rightValue;
return result;
}
else if (ops == "-")
{
result->Value = leftValue - rightValue;
return result;
}
else if (ops == "*")
{
result->Value = leftValue * rightValue;
return result;
}
else if (ops == "/")
{
result->Value = leftValue / rightValue;
return result;
}
else if (ops == "<")
{
return nativeBoolToBooleanObject(leftValue < rightValue);
}
else if (ops == ">")
{
return nativeBoolToBooleanObject(leftValue > rightValue);
}
else if (ops == "==")
{
return nativeBoolToBooleanObject(leftValue == rightValue);
}
else if (ops == "!=")
{
return nativeBoolToBooleanObject(leftValue != rightValue);
}
else
{
return newError("unknown operator: " + left->TypeStr() + " " + ops + " " + right->TypeStr());
}
}
std::shared_ptr<objects::Object> evalInfixExpression(std::string ops,
std::shared_ptr<objects::Object> left,
std::shared_ptr<objects::Object> right)
{
if (left->Type() == objects::ObjectType::INTEGER && right->Type() == objects::ObjectType::INTEGER)
{
return evalIntegerInfixExpression(ops, left, right);
}
else if (ops == "==")
{
return nativeBoolToBooleanObject(left == right);
}
else if (ops == "!=")
{
return nativeBoolToBooleanObject(left != right);
}
else if (left->Type() != right->Type())
{
return newError("type mismatch: " + left->TypeStr() + " " + ops + " " + right->TypeStr());
}
else
{
return newError("unknown operator: " + left->TypeStr() + " " + ops + " " + right->TypeStr());
}
}
代码块求值¶
else if (node->GetNodeType() == ast::NodeType::BlockStatement)
{
std::shared_ptr<ast::BlockStatement> blockStmt = std::dynamic_pointer_cast<ast::BlockStatement>(node);
return evalBlockStatement(blockStmt, env);
}
代码块里面的每条语句挨个调用:
std::shared_ptr<objects::Object> evalBlockStatement(std::shared_ptr<ast::BlockStatement> block,
std::shared_ptr<objects::Environment> env)
{
std::shared_ptr<objects::Object> result;
for (auto &stmt : block->v_pStatements)
{
result = Eval(stmt, env);
if (result != nullptr)
{
objects::ObjectType rt = result->Type();
if (rt == objects::ObjectType::RETURN_VALUE || rt == objects::ObjectType::ERROR)
{
return result;
}
}
}
return result;
}
如果代码块中间有错误或者返回就立即中止遍历代码块。
函数的定义与调用¶
else if (node->GetNodeType() == ast::NodeType::FunctionLiteral)
{
std::shared_ptr<ast::FunctionLiteral> funcObj = std::dynamic_pointer_cast<ast::FunctionLiteral>(node);
std::shared_ptr<objects::Function> function = std::make_shared<objects::Function>();
std::for_each(funcObj->v_pParameters.begin(), funcObj->v_pParameters.end(), [&](std::shared_ptr<ast::Identifier> &x)
{ function->Parameters.push_back(x); });
function->Env = env;
function->Body = funcObj->pBody;
return function;
}
else if (node->GetNodeType() == ast::NodeType::CallExpression)
{
std::shared_ptr<ast::CallExpression> callObj = std::dynamic_pointer_cast<ast::CallExpression>(node);
std::shared_ptr<objects::Object> function = Eval(callObj->pFunction, env);
if (isError(function))
{
return function;
}
std::vector<std::shared_ptr<objects::Object>> args = evalExpressions(callObj->pArguments, env);
if (args.size() == 1 && isError(args[0]))
{
return args[0];
}
return applyFunction(function, args);
}
函数为了支持闭包,因此定义函数的时候要把当时的符号表环境保存起来,然后调用的时候用外部符号表环境里面的值作为实参,而那些外部符号表环境没有的值则使用闭包环境里面的值:
std::shared_ptr<objects::Environment> extendFunctionEnv(std::shared_ptr<objects::Function> fn,
std::vector<std::shared_ptr<objects::Object>> &args)
{
std::shared_ptr<objects::Environment> env = objects::NewEnclosedEnvironment(fn->Env);
for (unsigned long i = 0; i < fn->Parameters.size(); i++)
{
env->Set(fn->Parameters[i]->Value, args[i]);
}
return env;
}
std::shared_ptr<objects::Object> applyFunction(std::shared_ptr<objects::Object> fn,
std::vector<std::shared_ptr<objects::Object>> &args)
{
std::shared_ptr<objects::Function> function = std::dynamic_pointer_cast<objects::Function>(fn);
if (function == nullptr)
{
return newError("not a function: " + fn->TypeStr());
}
std::shared_ptr<objects::Environment> extendedEnv = extendFunctionEnv(function, args);
std::shared_ptr<objects::Object> evaluated = Eval(function->Body, extendedEnv);
return unwrapReturnValue(evaluated);
}
其实参替换就是挨个进行值的替换,然后执行代码块。
测试用例¶
$ ./test_monkey
[==========] Running 26 tests from 26 test suites.
[----------] Global test environment set-up.
[----------] 1 test from TestNextToken
[ RUN ] TestNextToken.BasicAssertions
[ OK ] TestNextToken.BasicAssertions (0 ms)
[----------] 1 test from TestNextToken (0 ms total)
[----------] 1 test from TestString
[ RUN ] TestString.BasicAssertions
[ OK ] TestString.BasicAssertions (0 ms)
[----------] 1 test from TestString (0 ms total)
[----------] 1 test from TestLetStatements
[ RUN ] TestLetStatements.BasicAssertions
[ OK ] TestLetStatements.BasicAssertions (0 ms)
[----------] 1 test from TestLetStatements (0 ms total)
[----------] 1 test from TestReturnStatements
[ RUN ] TestReturnStatements.BasicAssertions
[ OK ] TestReturnStatements.BasicAssertions (0 ms)
[----------] 1 test from TestReturnStatements (0 ms total)
[----------] 1 test from TestIdentifierExpression
[ RUN ] TestIdentifierExpression.BasicAssertions
[ OK ] TestIdentifierExpression.BasicAssertions (0 ms)
[----------] 1 test from TestIdentifierExpression (0 ms total)
[----------] 1 test from TestIntegerLiteralExpression
[ RUN ] TestIntegerLiteralExpression.BasicAssertions
[ OK ] TestIntegerLiteralExpression.BasicAssertions (0 ms)
[----------] 1 test from TestIntegerLiteralExpression (0 ms total)
[----------] 1 test from TestParsingPrefixExpressions
[ RUN ] TestParsingPrefixExpressions.BasicAssertions
[ OK ] TestParsingPrefixExpressions.BasicAssertions (0 ms)
[----------] 1 test from TestParsingPrefixExpressions (0 ms total)
[----------] 1 test from TestParsingInfixExpressions
[ RUN ] TestParsingInfixExpressions.BasicAssertions
[ OK ] TestParsingInfixExpressions.BasicAssertions (0 ms)
[----------] 1 test from TestParsingInfixExpressions (0 ms total)
[----------] 1 test from TestOperatorPrecedenceParsing
[ RUN ] TestOperatorPrecedenceParsing.BasicAssertions
[ OK ] TestOperatorPrecedenceParsing.BasicAssertions (0 ms)
[----------] 1 test from TestOperatorPrecedenceParsing (0 ms total)
[----------] 1 test from TestBooleanExpression
[ RUN ] TestBooleanExpression.BasicAssertions
[ OK ] TestBooleanExpression.BasicAssertions (0 ms)
[----------] 1 test from TestBooleanExpression (0 ms total)
[----------] 1 test from TestIfExpression
[ RUN ] TestIfExpression.BasicAssertions
[ OK ] TestIfExpression.BasicAssertions (0 ms)
[----------] 1 test from TestIfExpression (0 ms total)
[----------] 1 test from TestIfElseExpression
[ RUN ] TestIfElseExpression.BasicAssertions
[ OK ] TestIfElseExpression.BasicAssertions (0 ms)
[----------] 1 test from TestIfElseExpression (0 ms total)
[----------] 1 test from TestFunctionLiteralParsing
[ RUN ] TestFunctionLiteralParsing.BasicAssertions
[ OK ] TestFunctionLiteralParsing.BasicAssertions (0 ms)
[----------] 1 test from TestFunctionLiteralParsing (0 ms total)
[----------] 1 test from TestFunctionParameterParsing
[ RUN ] TestFunctionParameterParsing.BasicAssertions
[ OK ] TestFunctionParameterParsing.BasicAssertions (0 ms)
[----------] 1 test from TestFunctionParameterParsing (0 ms total)
[----------] 1 test from TestCallExpressionParsing
[ RUN ] TestCallExpressionParsing.BasicAssertions
[ OK ] TestCallExpressionParsing.BasicAssertions (0 ms)
[----------] 1 test from TestCallExpressionParsing (0 ms total)
[----------] 1 test from TestCallExpressionParameterParsing
[ RUN ] TestCallExpressionParameterParsing.BasicAssertions
[ OK ] TestCallExpressionParameterParsing.BasicAssertions (0 ms)
[----------] 1 test from TestCallExpressionParameterParsing (0 ms total)
[----------] 1 test from TestEvalIntegerExpression
[ RUN ] TestEvalIntegerExpression.BasicAssertions
[ OK ] TestEvalIntegerExpression.BasicAssertions (0 ms)
[----------] 1 test from TestEvalIntegerExpression (0 ms total)
[----------] 1 test from TestEvalBooleanExpression
[ RUN ] TestEvalBooleanExpression.BasicAssertions
[ OK ] TestEvalBooleanExpression.BasicAssertions (0 ms)
[----------] 1 test from TestEvalBooleanExpression (0 ms total)
[----------] 1 test from TestEvalBangOperator
[ RUN ] TestEvalBangOperator.BasicAssertions
[ OK ] TestEvalBangOperator.BasicAssertions (0 ms)
[----------] 1 test from TestEvalBangOperator (0 ms total)
[----------] 1 test from TestEvalIfElseExpressions
[ RUN ] TestEvalIfElseExpressions.BasicAssertions
[ OK ] TestEvalIfElseExpressions.BasicAssertions (0 ms)
[----------] 1 test from TestEvalIfElseExpressions (0 ms total)
[----------] 1 test from TestEvalReturnStatements
[ RUN ] TestEvalReturnStatements.BasicAssertions
[ OK ] TestEvalReturnStatements.BasicAssertions (0 ms)
[----------] 1 test from TestEvalReturnStatements (0 ms total)
[----------] 1 test from TestEvalErrorHandling
[ RUN ] TestEvalErrorHandling.BasicAssertions
[ OK ] TestEvalErrorHandling.BasicAssertions (0 ms)
[----------] 1 test from TestEvalErrorHandling (0 ms total)
[----------] 1 test from TestEvalLetStatements
[ RUN ] TestEvalLetStatements.BasicAssertions
[ OK ] TestEvalLetStatements.BasicAssertions (0 ms)
[----------] 1 test from TestEvalLetStatements (0 ms total)
[----------] 1 test from TestEvalFunctionObject
[ RUN ] TestEvalFunctionObject.BasicAssertions
[ OK ] TestEvalFunctionObject.BasicAssertions (0 ms)
[----------] 1 test from TestEvalFunctionObject (0 ms total)
[----------] 1 test from TestEvalFunctionApplication
[ RUN ] TestEvalFunctionApplication.BasicAssertions
[ OK ] TestEvalFunctionApplication.BasicAssertions (0 ms)
[----------] 1 test from TestEvalFunctionApplication (0 ms total)
[----------] 1 test from TestEvalEnclosingEnvironments
[ RUN ] TestEvalEnclosingEnvironments.BasicAssertions
[ OK ] TestEvalEnclosingEnvironments.BasicAssertions (0 ms)
[----------] 1 test from TestEvalEnclosingEnvironments (0 ms total)
[----------] Global test environment tear-down
[==========] 26 tests from 26 test suites ran. (3 ms total)
[ PASSED ] 26 tests.
REPL¶
$ ./monkey
Hello ! This is the Monkey-CPP programming language!
Feel free to type in commands
>> 5 + 5 + 5 + 5 - 10
10
>> -5
-5
>> 1 > 2
false
>> 1 < 2
true
>> 1 == 1
true
>> !false
true
>> !!false
false
>> if (false) { 10 }
null
>> if (10 > 1) { return 10; }
10
>> let a = 5; let b = a; let c = a + b + 5; c;
15
>> let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));
20
一个闭包的例子:
>> let first = 10;
>> let second = 10;
>> let third = 10;
>>
>> first
10
>> second
10
>> third
10
>>
>> let ourFunction = fn(first) { let second = 20; return first + second + third;}
>>
>> first
10
>> second
10
>> third
10
>>
>> ourFunction(20) + first + second;
70
>>
>> first
10
>> second
10
>> third
10
在定义函数的时候,为函数内部闭包环境定义了一个second
变量,调用的时候它起作用,并不会影响外部的符号表环境。
总结¶
- 用
shared_ptr
代替了大部分的unique_ptr
,为的是符号表环境里面的对象是共享访问的 - 使用智能指针,不知道具体是否能够达到自动
GC
的效果 - 目前的执行速度很慢,尤其是递归调用计算
斐波那契数列
的时候,后面字节码或作为内置函数应可以提高效率
- 微信搜索: 「 MinYiLife 」, 关注公众号!
- 本文链接: https://www.lesliezhu.com/blog/2022/11/24/writing_an_interpreter_in_cpp_4/
- 版权声明: 原创文章,如需转载请注明文章作者和出处。谢谢!