NSASM MANUAL

NSASM MANUAL

NyaSama Assembly Script Module Manual


Introduction (Java, C#, C++)

注意: 操作符不区分大小写

大概是8086的风格(其实这已经完全不是汇编了)

目前实现了动态类型, 反射, 多态, 闭包等高等特性(大概是)


代码示例

run <main>

<main> {
    mov r0, "test"
    code c = (
        prt r0
        prt r0, r0
        prt r0
    )
    eval r0, c
    out "r0 is "
    prt r0
    prt c, "prt \"hello, gensokyo\""
    eval r0, c
    out "r0 is"
    prt r0
    end
}

注释格式

rem "注释内容"

对这是一条指令, 还不能跨行(


变量声明

var 变量名 = 初始值

初始值可以为这些格式: 1, 0x1, 1h, 1.0, 1.0F, '1', "1", "1" * 重复次数

其中重复次数除了可以是立即数, 还可以是其他整数型变量或寄存器

以下为详细说明

数据类型 对应立即数 示例
整数 1, 0x1, 1h var num = 0x32
浮点数 1.0, 1.0F var f = 2.333
字符 ‘c’, ‘\n’ var c = ‘\t’
字符串 “hello”, “gensokyo” * 2 var str = “HO!” * num

需要注意的是, 字符串型变量声明后变量本身是只读的, 需要传送至一个通用寄存器

同时这个寄存器初始类型不能是字符型或字符串型

特殊变量声明

函数型变量

NSASM拥有类似于函数型变量的变量, 即把一段代码作为变量, 可以执行和追加

未来还会加入更多的特性(比如插入, 替换等)

多行代码声明如下, 此时等号右侧小括号范围内依然属于立即数

var c = (
    prt "hello, world!"
    prt "hello, gensokyo!"
)

单行代码声明如下

var c = ( prt "single" )
映射型变量

这种变量和C++的map, C#的Dictionary, Java的LinkedHashMap类似

其实NSASM在不同语言上的实现就是用的以上三种数据结构

其声明和函数型变量类似, NSASM就是基于函数型变量实现的映射型变量初始化

在小括号左侧使用 M 或者 m 修饰符即为映射型变量立即数形式

括号内使用 put key, value 即存入当前的映射, 当然也可加入循环程序结构等普通程序.

使用 use map 来选中要操作的映射型变量, map 是映射型变量或寄存器

取值使用 get var, key , 其中 var 需要是可写的变量或寄存器

要注意一个问题, 由于NSASM的设计, 目标操作数不能是字符型, 字符串型, 函数型和映射型立即数. 因此要将这些立即数用为键的话, 需要使用通用寄存器做次替代. 或者说, 这里的目标操作数仅支持寄存器寻址和数值型立即数寻址.

多行声明如下

map m1 = M(
    mov r0, 20
    mov r1, 0
[head]
    inc r1
    mov r2, r1
    mul r2, r1
    put r1, r2
    cmp r1, r0
    jnz [head]
)

单行声明如下 (此时声明的是空的映射型变量)

var map1 = m()

显式变量声明

使用这些关键字: int, char, float, str, code, map

要注意的是这样声明的话需要写出初始值

这样可以作为参数在代码中使用

int a = 123
float b = 1.23
char c = 'c'
str s = "CHina"
str t = "t" * 10
code c = (
    int x = 3
    int y = 2
)
map m = M(
    put 0, 'a'
    put 1, 'b'
)

代码段声明

这里的代码段类似于8086汇编中的概念, 也类似于一般语言的函数的概念

同时, 函数型变量则类似于Java的Runnable接口以及C#的delegate

函数型变量的立即数则类似于Java和C#中的Lambda表达式

有基本格式(其中修饰符可省略)

修饰符<代码段名> {
    代码块
    ...
}

以下为示例

<func> {
    prt "hello"
    prt "this is a sample"
}

.<conf> {
    heap 64
    stack 32
    reg 16
}

特别地, 当代码段名左侧三角括号的左侧存在如下修饰符时

修饰符 作用
. (小数点) 当段名为 conf 时, 为解释器配置段, 否则整个段不被解释器加载
@ 当解释器中已经存在同名段时, 会覆盖掉已有的段, 否则解释器抛出错误

当修饰符为 . 小数点时

.<conf> {
    heap 64
    stack 32
    reg 16
}

heap 为堆大小配置(变量空间), stack 是栈大小配置, reg 是寄存器数目配置

这里仅可跟正的整数型立即数, 因为这部分代码不送入解释器执行

当修饰符为 @ 时

首先声明了如下段

<seg> {
    prt "hello"
}

然后声明如下段(或存在另外一个文件内加载)

@<seg> {
    prt "hello, gensokyo!"
}

此时执行 seg 段输出是 hello, gensokyo! 而非 hello


程序标号

这里的标号单独占一行, 使用 [] 包括, 而非传统汇编在指令头部

[head]
...
  jnz [head]
[tag]
  prt "..."
...

指令格式

指令名 目标操作数, 源操作数
指令名 操作数
指令名

需要注意的是, 在绝大部分双操作数指令中目标操作数不能是立即数

而在单操作数指令中, 其操作数要求视不同指令而不同

特别地, 函数型变量的立即数仅能作为双操作数指令的源操作数
而在绝大部分双操作数指令中, 函数型变量通常作为源操作数, 此时其代码会被执行
并返回一个值, 在未指定的情况下返回的是最后所使用的目标操作数
若指定则是返回所指定的操作数, 如立即数或寄存器

需要注意的是, 在函数型变量中的代码里声明的变量不和函数型变量以外的冲突

同时将这些变量作为返回值是返回的拷贝, 原变量的对象在函数型变量执行完后就销毁了

因为函数型变量里的代码其实是在一个新的解释器实例下运行, 这个实例继承了来自父实例的寄存器组的拷贝

而子实例的堆栈大小和父实例相同, 但并未获取父实例的拷贝, 因此子实例的变量声明不和父实例冲突


寄存器组

寄存器是ARM的风格, 并非是8086的AX, BX之类, 而是r0, r1

这寄存器更像是静态变量, 而非硬件意义上的寄存器

以下为介绍

寄存器名 作用
r# 通用寄存器组, 其中#为编号, r 不区分大小写, 可直接在程序内使用
映射寄存器 位于通用寄存器组的最后, 默认是 r16 , 建议由指令进行修改访问
状态寄存器 用于程序流程控制, 不可在程序内直接使用, 需要由指令进行修改访问
段寄存器 用于程序流程控制, 不可在程序内直接使用, 需要由指令进行修改访问
程序计数器 用于程序流程控制, 不可在程序内直接使用, 需要由指令进行修改访问

指令一览

指令名 作用 示例 备注
rem 行注释 rem “注释” 不作为实际代码运行
var 变量声明 var a = 0 不建议夹杂在普通指令内
int 整数型变量声明 int a = 0 不建议夹杂在普通指令内
char 字符型变量声明 char c = ‘c’ 不建议夹杂在普通指令内
float 浮点型变量声明 float f = 0.1 不建议夹杂在普通指令内
str 字符串型变量声明 str s = “string” 不建议夹杂在普通指令内
code 函数型变量声明 code c = ( ret 0 ) 不建议夹杂在普通指令内
map 映射型变量声明 map m = M( put 0, 1 ) 不建议夹杂在普通指令内
mov 数据传送指令 mov r0, r1 存在重载指令
push 压栈指令 push r0
pop 出栈指令 pop r1
in IO输入指令 in 0x00, r0 存在重载指令
out IO输出指令 out “12345” 存在重载指令
prt 屏幕打印指令 prt “hello” 存在重载指令
add 算数加法指令 add r0, 2 源操作数为函数型变量时会执行代码
inc 算数加一指令 inc r0
sub 算数减法指令 sub r2, 1.3 源操作数为函数型变量时会执行代码
dec 算数减一指令 dec r1
mul 算数乘法指令 mul r1, 2 源操作数为函数型变量时会执行代码
div 算数除法指令 div r2, 1.5F 源操作数为函数型变量时会执行代码
and 逻辑与指令 and r0, 0x7F 源操作数为函数型变量时会执行代码
or 逻辑或指令 or r0, 0x40 源操作数为函数型变量时会执行代码
xor 逻辑异或指令 xor r0, r0 源操作数为函数型变量时会执行代码
not 逻辑非指令 not r12
shl 逻辑左移指令 shl r0, 4 源操作数为函数型变量时会执行代码
shr 逻辑右移指令 shr r0, 2 源操作数为函数型变量时会执行代码
cmp 比较指令 cmp r0, 0 源操作数为函数型变量时会执行代码, 会修改状态寄存器
test 测试指令 test r1 会修改状态寄存器
jmp 无条件转移指令 jmp [tag]
jz 条件转移指令 jz [tag] 状态寄存器为零时跳转
jnz 条件转移指令 jnz [head] 状态寄存器非零时跳转
jg 条件转移指令 jg [tag] 状态寄存器大于零时跳转
jl 条件转移指令 jl [head] 状态寄存器小于零时跳转
end 程序结束指令 end 不存在操作的寄存器
ret 结果返回指令 ret 0 存在重载指令
nop 空操作指令 nop
rst 复位指令 rst 会修改段寄存器和程序计数器
run 段执行(跳转)指令 run <seg> 会修改段寄存器和程序计数器
call 段调用指令 call <seg> 会修改段寄存器和程序计数器
ld 程序加载指令 ld “test.ns” 会修改程序缓存区
eval 函数型变量执行指令 eval r0, c 存在重载指令
use 映射型变量选中指令 use map1 会修改映射寄存器
put 映射型变量存入指令 put 0, 1 源操作数为函数型时不会被执行
get 映射型变量读取指令 get r0, 0 源操作数为函数型变量时会执行代码
cat 字符串连接指令 cat r0, “123” 存在映射型变量重载
dog 字符串移除指令 dog r0, “AB” 存在映射型变量重载
type 类型测试指令 type r0, a 目标操作数会变为字符串类型
len 长度测试指令 len r0, str 存在映射型变量重载
ctn 字符串查找指令 ctn str, “abc” 存在映射型变量重载
equ 字符串比较指令 equ r0, str 相同时状态寄存器为0

这里的指令仅包含原生的NSASM指令集, 不含重载指令集

部分指令解释

TODO

运行原理

解释器加载程序后, 会将段外的代码集中到一起, 作为公共段加载, 并优先运行

公共段程序运行完成后且未终止解释器, 解释器会随机运行各个段的程序

需要指出的是, Java和C#版是按文件顺序执行, 而C++是随机的, 因此不建议使用这一特性

程序是按文件内的顺序逐行执行, 报错会给出段名和行号

运行过程中若用程序加载指令加载新的程序, 并且新程序中有同名的带有覆盖修饰符的段时

原段会被新段覆盖, 加载指令之后对这个段的调用是调用的新段的程序


高阶用法

这里有部分代码参考

https://github.com/NSDN/NyaSamaRailway/blob/master/src/main/java/club/nsdn/nyasamarailway/Util/NSASM.java


其他

目前不建议在函数型变量内进行段声明, 这里会有bug, 等待后续修复


Copyright © NSDN 2014 – 2018

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据