深入 Uxntal:面向 Uxn 虚拟机的栈式汇编语言完全指南 文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载Uxntalאוקסנטל是一种面向 Uxn 虚拟机的栈式汇编语言所有编程都通过对栈的操作来完成。本指南以仓库中的 uxntal.md英文原文与 he/uxntal.md希伯来语翻译版为骨架系统讲解其后缀记法、32 条操作码与三种模式、宏、内存模型、设备 I/O、标签与子程序等核心机制并给出可直接运行的完整示例。读完本文你将掌握用 Uxntal 编写、编译并运行 Uxn 虚拟机程序所需的全部基础能力。Uxntal 是什么面向 Uxn 虚拟机的栈式汇编语言Uxntal 是 Uxn 虚拟机的汇编语言。Uxn 是一个小型、极简的栈式虚拟机它的指令集被刻意设计得非常精简整个架构只有 32 条操作码opcode每条操作码又可以通过模式mode派生出多种行为。因此Uxntal 的学习曲线并不陡峭一旦理解了栈的运作方式就能以极少的指令完成复杂的程序逻辑。本仓库以「代码即文档」的形式收录了该语言的教学文件uxntal.md英文原文frontmatter 声明filename: learnuxn.tal即该教程的完整代码可作为learnuxn.tal文件下载运行he/uxntal.md希伯来语翻译版内容与英文版一一对应es/curto.md仓库中另一份与 Uxn 相关的资料它是面向 Uxn 虚拟机的一种 Forth 方言 Curto 的教学文档其中的ver命令会展示某个词被编译成的 Uxntal 代码例如DUP2 MUL2 JMP2r可以作为 Uxntal 操作码实际被编译器产出的佐证。下文所有代码示例、指令语义与内存参数均以仓库内这两份 Uxntal 文档为准。后缀记法一切从逆波兰表示法开始栈式机器编程初看可能有些奇怪因为它使用后缀记法postfix notation——操作符永远位于一次操作的末尾。例如常规写法3 4在 Uxntal 中写作3 4 常规写法(5 10) * 3在 Uxntal 中写作10 5 3 *即著名的逆波兰表示法Reverse Polish Notation。这种记法的核心思想是先把操作数「推入」push栈再用操作符把它们「弹出」pop并计算结果留在栈顶。例如10 5 先把 10 和 5 入栈弹出二者、把 15 压回栈顶随后3 *弹出 15 和 3得到 45。在 Uxntal 中注释用括号包裹例如( 这是一条注释 )。文档中大量使用「代码 行内注释展示栈状态」的写法这是阅读 Uxntal 最重要的习惯( 这是一条注释 ) ( Uxntal 的所有编程都通过操作栈来完成 ) #12 ( 推入一个字节 byte值为 0x12 ) #3456 ( 推入一个短整型 short值为 0x3456占用两个字节 )这里#前缀表示十六进制字面量#12推入 1 个字节#3456推入 2 个字节short。注意 Uxntal 的字节序是大端序#3456入栈后栈顶先是低字节56其下是高字节34这从后文的内存示例中可以得到印证。指令集核心32 条操作码与三种模式Uxn 共有32 条操作码每条操作码都有3 种可选模式mode模式通过追加在操作码名后的后缀字母启用模式名称作用2short 模式从栈上消费两个字节即处理一个 short默认模式是处理单个字节kkeep 模式执行操作但不从栈上消费操作数操作结果直接追加在栈顶rreturn 模式让操作符作用于**返回栈return stack**而非工作栈例如POP弹出一个字节而POP2弹出两个字节一个 shortPOP ( 弹出一个字节 ) POP2 ( 弹出一个短整型 )三种模式可以相互组合。看下面的算术示例括号内是执行后栈上的内容#12 #34 ADD ( 46 ) 0x12 0x34 0x46 #12 #34 ADDk ( 12 34 46 ) 结果 0x46 追加在栈顶原操作数保留ADDk中的k表示 keepADD正常会弹出两个操作数但ADDk会把结果直接追加在栈顶而 0x12 和 0x34 原样保留。模式还可以组合使用例如ADD2k同时启用 short 与 keep 两种模式#1234 #5678 ADD2k ( 12 34 56 78 68 ac )逐步拆解#1234入栈为两个字节12 34#5678入栈为56 78栈上共 4 个字节ADD2k将两个 short0x1234 与 0x5678相加0x1234 0x5678 0x68AC由于 keep 模式不消费操作数最终栈上为12 34 56 78 68 ac与注释完全一致。算术与位运算操作码Uxntal 的算术与位运算操作码共有 8 条ADD SUB MUL DIV ( 加、减、乘、除 ) AND ORA EOR SFT ( 按位与、按位或、按位异或、移位 )它们默认按字节运算配合2模式可处理 short配合k模式可保留操作数。上一节的#12 #34 ADD就是最基础的用法。用宏创建新操作码Uxntal 支持用**宏macro**把已有指令组合成新的「伪操作码」。宏用%开头定义花括号内是展开体%MOD2 { DIV2k MUL2 SUB2 }这个宏实现了「对两个 short 取模」先DIV2k求商并保留两个操作数再MUL2用商乘以除数最后SUB2用被除数减去乘积余数即为结果。使用它#1234 #0421 MOD2 ( 01 b0 )验证一下0x1234 ÷ 0x0421商为 40x0421 × 4 0x10840x1234 − 0x1084 0x01B0最终栈上为01 b0与注释一致。由此可以看到宏与 keep 模式配合的典型用法——把中间结果保留在栈上供后续指令继续消费。栈操作short 就是两个相邻的字节一个 short 本质上只是两个相邻的字节因此既可以整体操作也可以单独操作其中的每个字节。Uxntal 的栈操作码共有 6 条POP DUP NIP SWP OVR ROTSWP交换栈顶两个字节SWP2交换栈顶两个 short#1234 SWP ( 34 12 ) 交换 12 与 34 两个字节 #1234 #5678 SWP2 ( 56 78 12 34 ) 交换 1234 与 5678 两个 short #1234 #5678 SWP ( 12 34 78 56 ) SWP 只交换栈顶两个字节56 与 78POP与NIP都可以从栈上移除数据区别在于移除的位置POP移除栈顶字节NIP移除栈顶之下的第二个字节#1234 POP ( 12 ) 移除栈顶字节 34 #1234 NIP ( 34 ) 移除第二字节 12在 Uxntal 中POP2、SWP2、NIP2等带2的变体处理整个 short而不带2的指令则只作用于栈顶的单字节——这是理解字节级操作与 short 级操作差异的关键。比较与逻辑在栈上构造条件Uxntal 的逻辑操作码共有 4 条EQU NEQ GTH LTH ( 等于、不等、大于、小于 )它们会弹出两个操作数进行比较并向栈上推入一个标志flag其值为00假或01真#12 #34 EQU ( 00 ) 0x12 ≠ 0x34标志为 00 #12 #12 EQU ( 01 ) 0x12 0x12标志为 01 #12 #34 LTH ( 01 ) 0x12 0x34真 #78 #56 GTH ( 01 ) 0x78 0x56真注意示例中紧随其后的这一行是接续写法#0101 EQU2 ( 01 ) 比较两个 short0x0101 与 0x0101 相等标志为 01EQU2是比较两个 short 的变体。逻辑操作码产生的00/01标志正是后续条件跳转JCN的判断依据这一点在「条件、循环与子程序」一节会具体展开。内存模型与寻址Uxn 的可用内存布局如下区域大小工作栈working stack256 字节返回栈return stack256 字节主存memory65536 字节I/O 内存IO memory256 字节主存的可寻址范围是0000-ffff。文档用以下示例演示了写入与读取#12 #0200 STA ( 把 0x12 存入内存地址 0x0200 ) #3456 #0201 STA2 ( 把 0x3456 存入地址 0x0201 起大端序34 在 020156 在 0202 ) #0200 LDA2 ( 12 34 ) 从 0x0200 读取一个 short得到字节 12 与 34这里STASTore Absolute用 16 位绝对地址存储LDALoaD Absolute按 16 位地址读取。零页寻址单字节地址主存开头的256 字节零页zero page地址 0x0000–0x00ff可以用单个字节寻址从而节省指令字节数。对应的操作码是STZstore zero-page与LDZload zero-page#1234 #80 STZ2 ( 把 0x1234 存入零页12 存入 0x008034 存入 0x0081 ) #80 LDZ2 ( 12 34 ) 从零页 0x0080 读回一个 short完整的内存操作码清单LDZ STZ LDR STR LDA STA DEI DEO其中LDZ/STZ是零页寻址LDA/STA是 16 位绝对寻址LDR/STR是相对寻址DEI/DEO则是设备输入/输出见下一节。带2后缀的变体按 short 处理。设备与端口与外部世界通信设备device是 Uxn 与外部世界通信的方式。Uxn 最多可以同时连接16 个设备每个设备的寄存器称为端口port。以控制台设备 Console 为例它占用10-1f端口段其中端口18十六进制即0x18名为/write——向该端口写入一个字节就会把它打印到控制台。文档用宏封装了这个「输出一个字符」的常用操作%EMIT { #18 DEO }DEODEvice Output把栈顶字节写到指定设备端口。使用EMIT输出字符#31 EMIT ( 向控制台打印字符 1 )0x31正是 ASCII 字符1的十六进制编码所以这条指令会在控制台打印出1。类似的DEIDEvice Input从设备端口读取字节DEI/DEO也属于内存操作码家族因为它们操作的 256 字节 I/O 内存本身就是内存映射的。标签、子标签与位置引用标签label等同于程序中的一个位置position。用定义标签用定义子标签sublabelparent ( 定义一个名为 parent 的标签 ) child ( 定义一个子标签 parent/child )子标签的完整名称会带上父标签前缀用于在父标签内部引用局部位置例如循环起点。标签的位置可以被推入栈供跳转指令使用共有三种形式写法含义占用;parent推入绝对位置2 字节,parent推入相对位置1 字节.parent推入零页位置1 字节三种形式分别对应不同的寻址方式与指令字节数绝对地址用于跨程序段的跳转相对地址以当前指令为基准偏移更省字节零页地址则复用主存零页。条件、循环与子程序组合成真实程序逻辑操作码产生的标志配合跳转指令可以构造出条件分支、for循环、while循环与子程序。Uxntal 的跳转操作码共有 3 条JMP JCN JSR条件分支JCNJump if flag is Non-zero弹出标志和跳转地址标志非00时跳转否则继续顺序执行#12 #34 NEQ ,skip JCN ( 0x12 ≠ 0x34标志为 01跳转到 skip ) #31 EMIT ( 只有标志为 00 时才会执行打印 1 ) skipNEQ比较 0x12 与 0x34 得到真01JCN因此跳过EMIT直接落到skip。for 循环下面的循环会在控制台打印出0123456789#3a #30 loop DUP EMIT ( 把栈顶字节复制一份并打印 ) INC GTHk ,loop JCN POP2拆解#3a #30推入终止值0x3a字符:和起始值0x30字符0。循环体内DUP EMIT复制栈顶并打印INC把计数器加一GTHk比较计数器与终止值0x3akeep 模式保留操作数JCN在标志非零时跳回loop。于是计数器从0递增到9逐字符打印当它达到0x3a时0x3a 0x3a为假循环结束。退出后栈上还留着两个值计数器的当前值与终止值最后POP2清理栈。while 循环与字符串下面的循环遍历一个以00字节结尾的字符串并打印;word while LDAk EMIT INC2 LDAk ,while JCN POP2 BRK word vermilion $1;word把word标签的绝对地址2 字节推入栈循环体内LDAk从该地址读取一个字节keep 模式保留地址EMIT打印INC2把 16 位地址加一LDAk读取下一个字节若读到的字节非00则JCN跳回。word处定义字符串字面量vermilion末尾的$1预留 1 个字节——从循环逻辑可以推断这个预留字节正是字符串的00终止符循环读到它时标志为假、随即退出。程序最后用BRK停止虚拟机。子程序JSR 与 JMP2r用JSR可以跳入子程序用JMP2r返回JMP2r即「跳转到返回栈栈顶的地址」;word ,print-word JSR BRK print-word ( word* -- ) while LDAk EMIT INC2 LDAk ,while JCN POP2 JMP2r word cerulean这段程序把字符串cerulean的地址推入栈跳转到print-word子程序打印整串字符后返回最后BRK停机。子程序注释( word* -- )是 Uxntal 文档中常见的栈效果说明写法word*表示入参是一个指向字符串的指针--之后为空表示子程序结束时栈上不留结果。注意这里while定义在print-word内部形成子程序局部的循环标签。把示例组织成完整程序将上述片段按顺序组合就构成一份完整的 Uxntal 程序骨架对应文档中的learnuxn.tal示例文件( 注释与字面量 ) #12 ( 推入字节 0x12 ) #3456 ( 推入 short 0x3456 ) ( 算术模式演示 ) #12 #34 ADD ( 46 ) #12 #34 ADDk ( 12 34 46 ) #1234 #5678 ADD2k ( 12 34 56 78 68 ac ) ( 宏取模 ) %MOD2 { DIV2k MUL2 SUB2 } #1234 #0421 MOD2 ( 01 b0 ) ( 栈操作 ) #1234 SWP ( 34 12 ) #1234 #5678 SWP2 ( 56 78 12 34 ) ( 比较与逻辑 ) #12 #34 EQU ( 00 ) #12 #12 EQU ( 01 ) ( 内存访问 ) #12 #0200 STA #3456 #0201 STA2 #0200 LDA2 ( 12 34 ) ( 设备输出 ) %EMIT { #18 DEO } #31 EMIT ( 打印 1 ) ( 标签与子标签 ) parent child ( 条件分支 ) #12 #34 NEQ ,skip JCN #31 EMIT skip ( for 循环打印 0123456789 ) #3a #30 loop DUP EMIT INC GTHk ,loop JCN POP2 ( while 循环打印 vermilion ) ;word while LDAk EMIT INC2 LDAk ,while JCN POP2 BRK word vermilion $1 ( 子程序JSR / JMP2r ) ;word2 ,print-word JSR BRK print-word ( word* -- ) while LDAk EMIT INC2 LDAk ,while JCN POP2 JMP2r word2 cerulean这份代码忠实保留了文档中的全部示例片段命名做了必要区分以避免标签冲突可作为入门 Uxntal 的第一份可运行素材配合 Uxn 工具链汇编器与模拟器逐段验证栈状态。仓库延伸从哪里继续学习仓库内与 Uxntal 直接相关的资料还包括es/curto.md面向 Uxn 虚拟机的 Forth 方言 Curto 教程。它的ver命令可以查看一个词被编译成的 Uxntal 代码例如示例中ver cuadrado输出DUP2 MUL2 JMP2r——这些正是本文讲解过的操作码与模式组合可以作为「Uxntal 是编译目标」的交叉印证uxntal.md 与 he/uxntal.md同一教程的英文与希伯来语版本内容完全对应适合对照阅读以巩固概念。仓库对这类教学文档有统一的维护规范CONTRIBUTING.md 说明了 frontmattername、filename、contributors、translators等字段的写法lint/frontmatter.py 中实现了对这些字段的自动化校验README.md 则介绍了「以可运行的注释代码讲解语言」的整体项目理念。本教程的 frontmatter 声明了filename: learnuxn.tal意味着文中所有代码片段正是设计为拼合后可下载运行的单个.tal文件。需要说明的是本文所有指令语义、内存布局、端口编号等参数均以上述两份文档为准Uxn 生态的完整实现细节如汇编器与模拟器的具体用法属于仓库之外的工具链范畴不在此文覆盖范围内。建议在学习时以文档中的栈注释为线索逐行推演栈状态这是掌握任何栈式汇编语言最有效的方法。赞分享文档教程【免费下载链接】learnxinyminutes-docsCode documentation written as code! How novel and totally my idea!项目地址https://gitcode.com/gh_mirrors/le/learnxinyminutes-docs点击查看免费下载相关推荐Curto 快速上手面向 UXN 虚拟机的西班牙语 Forth 方言完整入门learnxinyminutes-docsCurto 快速上手面向 UXN 虚拟机的西班牙语 Forth 方言完整入门learnxinyminutes docs Curto 是 es/curto.文档教程BEX VM 深度解析BAML 语言的栈式字节码虚拟机BEX VM 深度解析BAML 语言的栈式字节码虚拟机 导读 BEX VM 是 BAML面向 Agent 的编程语言在 Rust 中实现的 栈式字节码解释编程语言AI Agent编译器CLI人工智能Gleam 语言完全入门指南BEAM 虚拟机上的强类型函数式编程实战教程Gleam 语言完全入门指南BEAM 虚拟机上的强类型函数式编程实战教程 本篇技术指南以开源仓库 learnxinyminutes docs 中的 gleam文档教程上一篇TestDisk数据恢复终极指南免费开源工具拯救丢失数据的完整方案下一篇从 CHANGELOG.md 看 git-cliff 的功能演进与自举实践解读一个 Changelog 生成器的完整版本史创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考