# MicroPython VM
MicroPython VM (Virtual Machine) 是一个运行 MicroPython 代码的虚拟机,可以将 Python 代码编译成字节码并在 MicroPython VM 的内部环境中运行。MicroPython VM 通常用于在嵌入式系统中运行 Python 代码,因为它可以在较小的存储空间和处理器速度下运行。
使用 MicroPython VM,可以在 C 语言程序中运行 Python 代码。以下是一个简单的示例,展示了如何使用 MicroPython VM 执行 Python 程序:
#include <stdio.h>
#include "py/mphal.h"
#include "py/mpstate.h"
#include "py/gc.h"
#include "py/mppy.h"
#include "py/stackctrl.h"
// 将 Python 代码编译成字节码并返回字节码对象
mp_obj_t compile_code(const char *source_code) {
// 将代码字符串转换为字节码对象
mp_lexer_t *lex = mp_lexer_new_from_str_len("", strlen(source_code), false);
mp_parse_tree_t pt = mp_parse_compile(lex, MP_PARSE_FILE_INPUT);
// 编译字节码
mp_obj_t module_fun = mp_compile(&pt, "", MP_EMIT_OPT_NONE, false);
// 返回字节码对象
return module_fun;
}
int main(void) {
// 初始化 MicroPython VM
mp_init();
mp_obj_t module_fun;
char source_code[] = "print('Hello World')"; // Python 代码
// 编译 Python 代码
module_fun = compile_code(source_code);
// 在 VM 中执行函数
mp_call_function_0(module_fun);
return 0;
}
上述代码中,`mp_init()`函数用于初始化 MicroPython VM,并创建一些必要的对象,例如全局对象和垃圾回收堆栈。
`compile_code()`函数将 Python 代码字符串编译为字节码对象。在这个函数中,我们使用 `mp_lexer_new_from_str_len()` 函数将字符串转换为 lexer 对象并通过 `mp_parse_compile()` 函数将其编译为字节码。然后我们调用 `mp_compile()` 函数创建字节码对象,该对象便可以被 MicroPython VM 运行。
最后,我们使用 `mp_call_function_0()` 函数在 VM 中执行 `module_fun` 函数。在此示例中,Python 函数输出一个"Hello World"消息。
# compile_code和mp_call_function_0函数
# compile_code源码
py/compile.h
中 compile_code()
的函数签名和注释:
mp_obj_t compile_code(const char *data, mp_uint_t len, bool is_repl, bool is_repl_cont, mp_parse_input_kind_t input_kind);
编译一段 Python 代码并返回一个函数对象。
参数:
-
data
- 包含 Python 代码的字符串。 -
len
- 字符串data
的长度。 -
is_repl
- 表示这个代码是不是在 REPL 中输入的。 -
is_repl_cont
- 如果是 REPL 中输入且需要续行,为true
。 -
input_kind
- 输入的类型,可以是MP_PARSE_SINGLE_INPUT
、MP_PARSE_FILE_INPUT
或MP_PARSE_EVAL_INPUT
。
返回值:
- 如果编译成功,则返回一个函数对象,否则返回
MP_OBJ_NULL
并且输出错误信息到标准错误输出。
compile_code()
函数的实现在文件 py/compile.c
中,具体实现细节如下:
1,首先构建编译器环境。
mp_parse_tree_t parse_tree = {0};
emit_glue_t emit_glue = {0};
mp_lexer_t *lex = NULL;
mp_parse_node_t pn;
2,然后,根据给定的输入类型,调用不同的输入函数,返回一个解析器。
if (input_kind == MP_PARSE_SINGLE_INPUT) {
lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, data, len, 0);
pn = mp_parse(lex, MP_PARSE_SINGLE_INPUT);
} else if (input_kind == MP_PARSE_FILE_INPUT) {
...
} else { // input_kind == MP_PARSE_EVAL_INPUT
...
}
这里我们只展开 MP_PARSE_SINGLE_INPUT
的情况。
3,对解析得到的语法树进行语法分析和代码生成,把生成的字节码放到一个新的字节码对象中。
if (pn != MP_PARSE_NODE_NULL) {
// make sure last expression statement is a result
PN_set_flag(pn, PN_CAN_BE_NONE);
// compile it
qstr source_name = lex ? lex->source_name : MP_QSTR_eval;
if (is_repl) {
if (is_repl_cont) {
source_name = MP_QSTR__lt_stdin_gt_;
} else {
// add prefix " "