投放广告的网站,seo黑帽是什么意思,wordpress极速,安次区建设局网站MicroPython编译过程包括以下步骤#xff1a;
词法分析器将MicroPython程序文本流转换为标记。语法解释器将标记转换为抽象语法#xff08;语法树#xff09;。根据语法书输出字节码或本地代码。
本文以给MicroPython增加一个简单的语言特性为例来说明这一过程#xff1a…MicroPython编译过程包括以下步骤
词法分析器将MicroPython程序文本流转换为标记。语法解释器将标记转换为抽象语法语法树。根据语法书输出字节码或本地代码。
本文以给MicroPython增加一个简单的语言特性为例来说明这一过程 add1 3
4add1语句以整数作为参数将其加 1。
添加语法规则
MicroPython 的语法基于CPython 语法并在py/grammar.h中定义。该语法用于解析MicroPython源码文件。
要定义语法规则需要关注两个宏DEF_RULE 和 DEF_RULE_NC。DEF_RULE 允许您定义一个带有相关编译函数的规则而 DEF_RULE_NC 则没有编译 (no compile NC) 函数。
对于新增的语句 add1带有编译函数的简单语法定义如下
DEF_RULE(add1_stmt, c(add1_stmt), and(2), tok(KW_ADD1), rule(testlist))第二个参数 c(add1_stmt) 是相应的编译函数需要在 py/compile.c 中实现以便将此规则转化为可执行代码。
第三个必要参数可以是 or 或 and它指定了与语句相关的节点数。在本例中add1语句类似于汇编语言中的ADD1它需要一个数字参数因此add1_stmt有两个相关节点一个节点是语句本身即与KW_ADD1对应的字面 add1另一个节点是它的参数即作为顶层表达式规则的testlist规则。
注意
这里的add1规则只是一个示例并非MicroPython标准语法的一部分。
本例中的第四个参数是与规则KW_ADD1相关的标记可以通过编辑py/lexer.h在词典中定义该标记。
使用DEF_RULE_NC宏可省略编译函数参数即在不使用编译函数的情况下定义相同的规则
DEF_RULE_NC(add1_stmt, and(2), tok(KW_ADD1), rule(testlist))其余参数的含义相同无编译函数的规则必须由所有以该规则为节点的规则明确处理。这种 NC 规则通常用于表达复杂语法结构的子部分这些子部分无法用一条规则表达。
注意
宏DEF_RULE和DEF_RULE_NC需要其他参数要深入了解支持的参数请参阅 py/grammar.h。
添加词法标记
语法中定义的每条规则都应与py/lexer.h中定义的标记相关联通过编辑 _mp_token_kind_t 枚举来添加该标记
typedef enum _mp_token_kind_t {...MP_TOKEN_KW_OR,MP_TOKEN_KW_PASS,MP_TOKEN_KW_RAISE,MP_TOKEN_KW_RETURN,MP_TOKEN_KW_TRY,MP_TOKEN_KW_WHILE,MP_TOKEN_KW_WITH,MP_TOKEN_KW_YIELD,MP_TOKEN_KW_ADD1,...
} mp_token_kind_t;然后编辑py/lexer.c添加新关键字的字面文本
STATIC const char *const tok_kw[] {...or,pass,raise,return,try,while,with,yield,add1,...
};请注意关键字的命名可以自己定义但为了保持一致性还是要尽可能的遵守命名标准。
注意
py/lexer.c中的关键字顺序必须与py/lexer.h中定义的枚举标记顺序一致。
解析
在解析阶段解析器将词法生成器产生的标记转换为抽象语法树AST abstract syntax tree 或语法树。解析器的实现定义在py/parse.c中。
解析器还维护一个常量表用于解析的不同方面这与符号表的作用类似。
在这一阶段解析器还进行了一些优化如针对逻辑、二进制、一元等大多数操作对整数进行常量折叠对表达式周围的括号进行优化增强以及对字符串进行一些优化。
值得注意的是docstrings会被丢弃且无法访问即使像字符串互调这样的优化也不会应用于docstrings。
编译步骤
与许多编译器一样MicroPython 会将所有代码编译为 MicroPython 字节码或本地代码。实现这一目标的功能在 py/compile.c 中实现
mp_obj_t mp_compile(mp_parse_tree_t *parse_tree, qstr source_file, bool is_repl) {// 创建模块上下文并设置全局字典mp_module_context_t *context m_new_obj(mp_module_context_t);context-module.globals mp_globals_get();// 将输入的语法树编译为原始代码结构mp_compiled_module_t cm;cm.context context;mp_compile_to_raw_code(parse_tree, source_file, is_repl, cm);// 创建并返回一个执行外部模块的函数对象return mp_make_function_from_raw_code(cm.rc, cm.context, NULL);
}编译器分四次编译代码作用域、堆栈大小、代码大小和发射。每次都在相同的 AST 数据结构上运行相同的 C 代码每次都根据前一次的结果计算不同的内容。
第一遍
在第一道工序中编译器会了解已知标识符变量及其作用域全局、局部、封闭等。在同一过程中发射器字节码或本地代码还会计算发射代码所需的标签数量。
// 第一遍
comp-emit emit_bc;
comp-emit_method_table emit_bc_method_table;uint max_num_labels 0;
for (scope_t *s comp-scope_head; s ! NULL comp-compile_error MP_OBJ_NULL; s s-next) {if (s-emit_options MP_EMIT_OPT_ASM) {compile_scope_inline_asm(comp, s, MP_PASS_SCOPE);} else {compile_scope(comp, s, MP_PASS_SCOPE);// 检查是否要关闭隐式声明的变量。for (size_t i 0; i s-id_info_len; i) {id_info_t *id s-id_info[i];if (id-kind ID_INFO_KIND_GLOBAL_IMPLICIT) {scope_check_to_close_over(s, id);}}}...
}第二遍和第三遍
第二遍和第三遍涉及计算字节码或代码的Python堆栈和代码大小。第三次计算后代码大小不能改变否则跳转标签将不正确。
for (scope_t *s comp-scope_head; s ! NULL comp-compile_error MP_OBJ_NULL; s s-next) {...// 第二遍: 计算python堆栈大小compile_scope(comp, s, MP_PASS_STACK_SIZE);// 第三遍: 计算代码大小if (comp-compile_error MP_OBJ_NULL) {compile_scope(comp, s, MP_PASS_CODE_SIZE);}...
}在第二步之前可以选择要输出的代码类型可以是本地代码或字节码。
// 选择发射器类型
switch (s-emit_options) {case MP_EMIT_OPT_NATIVE_PYTHON:case MP_EMIT_OPT_VIPER:if (emit_native NULL) {emit_native NATIVE_EMITTER(new)(comp-compile_error, comp-next_label, max_num_labels);}comp-emit_method_table NATIVE_EMITTER_TABLE;comp-emit emit_native;break;default:comp-emit emit_bc;comp-emit_method_table emit_bc_method_table;break;
}缺省选项是字节码但需要注意通过VIPER还有另一个本地代码选项。有关 viper注释的更多详情请参阅 生成本地代码部分。
此外这里还支持内联汇编代码即汇编指令以Python函数调用的形式编写但直接以相应的机器码形式输出。这种汇编程序只有三次传递作用域、代码大小、发射并使用不同的实现而不是compile_scope函数。
第四遍
第四步是输出可执行的最终代码既可以是虚拟机中的字节码也可以是 CPU 直接执行的本地代码。
for (scope_t *s comp-scope_head; s ! NULL comp-compile_error MP_OBJ_NULL; s s-next) {...// 第四遍: 生成编译的字节码或本地代码if (comp-compile_error MP_OBJ_NULL) {compile_scope(comp, s, MP_PASS_EMIT);}
}生成字节码
Python 代码中的语句通常与所生成的字节码相对应例如a b会产生 “push a”然后是 “push b”然后是 “binary op add”。有些语句不会做任何事情但会影响其他一些事情比如变量的作用域例如global a。
输出字节码的函数的实现与此类似
void mp_emit_bc_unary_op(emit_t *emit, mp_unary_op_t op) {emit_write_bytecode_byte(emit, 0, MP_BC_UNARY_OP_MULTI op);
}这里使用一元运算符表达式作为示例但其他语句/表达式的实现细节与此类似。emit_write_bytecode_byte()方法是对主函数 emit_get_cur_to_write_bytecode() 的封装所有函数都必须调用该函数才能生成字节码。
生成本地代码
与字节码的生成方式类似py/emitnative.c 中的每个代码语句都应该有一个相应的函数
STATIC void emit_native_unary_op(emit_t *emit, mp_unary_op_t op) {vtype_kind_t vtype;emit_pre_pop_reg(emit, vtype, REG_ARG_2);if (vtype VTYPE_PYOBJ) {emit_call_with_imm_arg(emit, MP_F_UNARY_OP, op, REG_ARG_1);emit_post_push_reg(emit, VTYPE_PYOBJ, REG_RET);} else {adjust_stack(emit, 1);EMIT_NATIVE_VIPER_TYPE_ERROR(emit,MP_ERROR_TEXT(unary op %q not implemented), mp_unary_op_method_name[op]);}
}这里的区别在于必须处理viper typing。Viper装饰器允许处理不止一种类型的变量。默认情况下所有变量都是 Python 对象但使用 viper变量也可以声明为机器类型变量如本地整数或指针。可以将 Viper 视为 Python 的超集其中普通 Python 对象的处理方式与通常一样而本地机器变量的处理方式则经过优化直接使用机器指令进行操作。Viper 类型化可能会破坏 Python 的等价性例如整数会变成本地整数并可能溢出不像 Python 整数会自动扩展到任意精度。