MicroPython核心:编译器
MicroPython编译过程包括以下步骤:
- 词法分析器将MicroPython程序文本流转换为标记。
- 语法解释器将标记转换为抽象语法(语法树)。
- 根据语法书输出字节码或本地代码。
本文以给MicroPython增加一个简单的语言特性为例来说明这一过程:
>>> add1 3
4
>>>
add1语句以整数作为参数,将其加 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 整数会自动扩展到任意精度)。
相关文章:
MicroPython核心:编译器
MicroPython编译过程包括以下步骤: 词法分析器将MicroPython程序文本流转换为标记。语法解释器将标记转换为抽象语法(语法树)。根据语法书输出字节码或本地代码。 本文以给MicroPython增加一个简单的语言特性为例来说明这一过程:…...
R语言【taxlist】——tax2traits():将分类信息设置为分类单元特征
Package taxlist version 0.2.4 Description 分类法分类可以包含在taxonRelations插槽提供的信息中的 taxlist 对象中。然而,对于统计分析来说,将这些信息插入到插槽taxonTraits中可能更方便。 Usage tax2traits(object, ...)## S3 method for class …...
CTF-WEB的知识体系
CTF概念 CTF是Capture The Flag的缩写,中文一般译作夺旗赛 CTF起源于1996年DEFCON全球黑客大会 DEFCONCTF是全球技术水平和影响力最高的CTF竞赛 竞赛模式 解题模式:解决网络安全技术挑战(即找到flag),提交后获取相应分值。 攻防赛模式:要求找到其他队…...
【Spring框架】@Cacheable注解:缓存最佳实践
在Java开发中,性能优化是一个永恒的话题。对于使用Spring框架的应用程序来说,Cacheable 注解提供了一种简单有效的方式来提升性能,特别是对于那些计算成本高或数据变化不频繁的操作。本文将深入探讨 Cacheable 的使用方法和注意事项ÿ…...
iZotope RX 10.4.2 mac激活版 音频修复和增强工具
iZotope RX 10 for Mac是一款专业的音频修复软件,旨在提供强大、精确的工具,让用户能够清晰、纯净地处理音频。以下是其主要功能和特点: 软件下载:iZotope RX 10.4.2 mac激活版下载 强大的降噪功能:iZotope RX 10采用了…...
vue核心知识点
一、Vue基础知识点总结 开发vue项目的模式有两种: 基于vue.js,在html中引入vue.js,让vue.js管理div#app元素。基于脚手架环境:通过vue脚手架环境可以方便的创建一个通用的vue项目框架的模板,在此基础之上开发vue项目…...
【乳腺肿瘤诊断分类及预测】基于Elman神经网络
课题名称:基于Elman神经网络的乳腺肿瘤诊断分类及预测 版本日期:2023-05-15 运行方式: 直接运行Elman0501.m 文件即可 代码获取方式:私信博主或QQ:491052175 模型描述: 威斯康辛大学医学院经过多年的收集和整理&a…...
【kubernets】由Evicted状态的Pod探讨k8s中pod的驱逐策略
背景 某天突然发现自己的测试环境中有Evicted状态的pod,于是需要排查原因。先来看看大致情况: [rootk8s-m1 ~]# kubectl get pod -A -o wide|grep k8s-m1 kube-system calico-kube-controllers-bcc6f659f-575mr 1/1 Running 3 177d…...
vxe-table3.0的表格树如何做深层查找,返回搜索关键字的树形结构
vxe-table2.0版本是提供深层查找功能的,因为他的数据源本身就是树形结构,所以深层查找查询出来也是树形结构。 但是vxe-table3.0版本为了做虚拟树功能,将整个数据源由树形垂直结构变成了扁平结构,便不提供深层查询功能,…...
幻兽帕鲁越玩越卡,内存溢出问题如何解决?
近期幻兽帕鲁游戏大火,在联机组队快乐游玩的同时,玩家们也发现了一些小问题。由于游戏有随机掉落材料的设定,服务器在加载掉落物的过程中很容易会出现掉帧、卡顿的情况。某些玩家甚至在游戏1~2时后就出现服务器崩溃的情况…...
C++_list
目录 一、模拟实现list 1、list的基本结构 2、迭代器封装 2.1 正向迭代器 2.2 反向迭代器 3、指定位置插入 4、指定位置删除 5、结语 前言: list是STL(标准模板库)中的八大容器之一,而STL属于C标准库的一部分,因此在C中可以直接使用…...
使用docker部署mongodb
1.创建目录 mkdir -p /opt/mongodb/{data,logs,config} 2.创建配置文件 进入目录 cd /opt写入配置 vim mongod.conf 内容如下 systemLog:# MongoDB发送所有日志输出的目标指定为文件destination: file# mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径path:…...
C#,打印漂亮的贝尔三角形(Bell Triangle)的源程序
以贝尔数为基础,参考杨辉三角形,也可以生成贝尔三角形(Bell triangle),也称为艾特肯阵列(Aitkens Array),皮埃斯三角形(Peirce Triangle)。 贝尔三角形的构造…...
开源电商系统
前言 做电商永不过时,但形式会不断变化。任何赚钱的事情大体都分为两大块:生产和销售。两者是并重的,首先要有好的产品,其次是做好推广运营和销售渠道建设。对于小微企业来说,前期如果能通过销售赚到第一桶金…...
责任链模式在java中的实现
1 总览 2 概念 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。 3 实现 公共部分,一个系…...
粤嵌Gec6818---小项目功能实现简单步骤(RFID+图片显示+音乐+视频)
项目设计开发环境: (1)VMware Workstation Pro软件 (2)ubuntu12 .04 (能交叉编译就行) (3)SecureCRT (4)代码编译器(notepad/Vis…...
opencv学习 特征提取
内容来源于《opencv4应用开发入门、进阶与工程化实践》 图像金字塔 略 拉普拉斯金字塔 对输入图像进行reduce操作会生成不同分辨率的图像,对这些图像进行expand操作,然后使用reduce减去expand之后的结果,就会得到拉普拉斯金字塔图像。 …...
关于maven项目构建的解释
在Idea中使用模块化构建项目 项目介绍: sky-take-out sky-common pom.xml sky-pojo pom.xml sky-server pom.xml pom.xml 说明 sky-server依赖sky-pojo和sky-common,继承sky-take-outsky-pojo继承sky-take-outsky-common继承sky-take-out 由于Idea编…...
IMU/捷联惯导常见的术语,以及性能评价标准(附Python解析代码)
0. 简介 现在的机器人领域在普遍使用IMU(惯性导航单元)。该系统有三个加速度传感器与三个角速度传感器(陀螺)组成,加速度计用来感受飞机相对于地垂线的加速度分量,陀螺仪用来感知飞机的角速率变化…...
Debezium发布历史98
原文地址: https://debezium.io/blog/2020/11/12/debezium-1-3-1-final-released/ 欢迎关注留言,我是收集整理小能手,工具翻译,仅供参考,笔芯笔芯. Debezium 1.3.1.Final 发布 十一月 12, 2020 作者: 克里…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
