如果有好的意见或建议,欢迎联系我们!
-
E-mail:
-
lhk101lhk101@qq.com (ValKmjolnir)
-
1467329765@qq.com(Sidi762)
-
Nasal
是一款语法与 ECMAscript 相似的编程语言,并作为脚本语言被著名开源飞行模拟器 FlightGear 所使用。
该语言的设计者为 Andy Ross。
该解释器由 ValKmjolnir 使用 C++
(-std=c++17
)重新实现。非常感谢 Andy 为我们设计了这个神奇且简洁的编程语言: Andy Ross 的 nasal 解释器。
该项目旧版本使用 MIT 协议开源 (2019/7 ~ 2021/5/4 ~ 2023/5),从 2023/6 开始新版本使用 GPL v2 协议。
2019 年暑假,FGPRC 的成员告诉我,在 Flightgear 中提供的 nasal 控制台窗口中进行调试很不方便,仅仅是想检查语法错误,也得花时间打开软件等待加载进去后进行调试。所以我就写了一个全新的解释器来帮助他们检查语法错误以及运行时错误。
我编写了 nasal 的词法分析器和语法分析器,以及一个全新的字节码虚拟机,并用这个运行时来进行 nasal 程序的调试。我们发现使用这个解释器来检测语法和运行时错误极大的提高了效率。
你也可以使用这个语言来写一些与 Flightgear 运行环境无关的有趣的程序,并用这个解释器来执行。你也可以让解释器来调用你自己编写的模块,使它成为项目中一个非常有用的工具。
现在支持下载预览版(Nightly Build)。 Windows 平台的预览版解释器现在还没配置相关流水线, 请耐心等候或者直接在本地编译。 我们提供了一份 Cmake 文件,可以很方便地在 Visual Studio 中编译:
- macOS-nightly-build
- linux-nightly-build
- windows-nightly-build: [施工中...]
推荐下载最新代码包编译,这个项目非常小巧, 没有使用任何第三方库,因此编译起来非常轻松, 只需要这两样东西: C++ 编译器以及make程序。
确保 thread model 是 posix thread model
, 否则没有 thread 库。
mingw32-make nasal.exe -j4
项目提供了 CMakeLists.txt 用于在Visual Studio
中创建项目。
make -j
你也可以通过如下的其中一行命令来指定你想要使用的编译器:
make nasal CXX=... -j
如果你是 Windows
用户且想正常输出unicode,在nasal代码里写这个来开启unicode代码页:
if (os.platform()=="windows") {
system("chcp 65001");
}
必须用 var 定义变量
这个解释器使用了更加严格的语法检查来保证你可以更轻松地debug。这是非常有必要的严格,否则debug会非常痛苦。
同样的,flightgear 内置的 nasal 解释器也采取了类似的措施,所以使用变量前务必用 var
先进行声明。
在Andy的解释器中:
foreach(i; [0, 1, 2, 3])
print(i)
这个程序可以正常运行。然而这个i
标识符实际上在这里是被第一次定义,而且没有使用var
。我认为这样的设计很容易让使用者迷惑。他们可能都没有发现这里实际上是第一次定义i
的地方。没有使用var
的定义会让程序员认为这个i
也许是在别的地方定义的。
所以在这个解释器中,我直接使用严格的语法检查方法来强行要求用户必须要使用var
来定义新的变量或者迭代器。如果你忘了加这个关键字,那么你就会得到这个:
code: undefined symbol "i"
--> test.nas:1:9
|
1 | foreach(i; [0, 1, 2, 3])
| ^ undefined symbol "i"
code: undefined symbol "i"
--> test.nas:2:11
|
2 | print(i)
| ^ undefined symbol "i"
当解释器崩溃时,它会反馈错误产生过程的堆栈追踪信息:
内置函数 die
die
函数用于直接抛出错误并终止执行。
func() {
println("hello");
die("error occurred this line");
return;
}();
hello
[vm] error: error occurred this line
[vm] error: error occurred in native function
call trace (main)
call func@0x557513935710() {entry: 0x850}
trace back (main)
0x000547 4c 00 00 16 callb 0x16 <__die@0x557512441780>(std/lib.nas:150)
0x000856 4a 00 00 01 callfv 0x1(a.nas:3)
0x00085a 4a 00 00 00 callfv 0x0(a.nas:5)
stack (0x5575138e8c40, limit 10, total 14)
0x00000d | null |
0x00000c | pc | 0x856
0x00000b | addr | 0x5575138e8c50
0x00000a | nil |
0x000009 | nil |
0x000008 | str | <0x5575138d9190> error occurred t...
0x000007 | nil |
0x000006 | func | <0x5575139356f0> entry:0x850
0x000005 | pc | 0x85a
0x000004 | addr | 0x0
栈溢出
这是一个会导致栈溢出的例子:
func(f) {
return f(f);
}(
func(f) {
f(f);
}
)();
[vm] error: stack overflow
call trace (main)
call func@0x564106058620(f) {entry: 0x859}
--> 583 same call(s)
call func@0x5641060586c0(f) {entry: 0x851}
trace back (main)
0x000859 45 00 00 01 calll 0x1(a.nas:5)
0x00085b 4a 00 00 01 callfv 0x1(a.nas:5)
0x00085b 582 same call(s)
0x000853 4a 00 00 01 callfv 0x1(a.nas:2)
0x00085f 4a 00 00 01 callfv 0x1(a.nas:3)
stack (0x56410600be00, limit 10, total 4096)
0x000fff | func | <0x564106058600> entry:0x859
0x000ffe | pc | 0x85b
0x000ffd | addr | 0x56410601bd20
0x000ffc | nil |
0x000ffb | nil |
0x000ffa | func | <0x564106058600> entry:0x859
0x000ff9 | nil |
0x000ff8 | func | <0x564106058600> entry:0x859
0x000ff7 | pc | 0x85b
0x000ff6 | addr | 0x56410601bcb0
运行时错误
如果在执行的时候出现错误,程序会直接终止执行:
func() {
return 0;
}()[1];
[vm] error: must call a vector/hash/string but get number
trace back (main)
0x000854 47 00 00 00 callv 0x0(a.nas:3)
stack (0x564993f462b0, limit 10, total 1)
0x000000 | num | 0
详细的崩溃信息
使用命令 -d
或 --detail
后,trace back信息会包含更多的细节内容:
hello
[vm] error: error occurred this line
[vm] error: error occurred in native function
call trace (main)
call func@0x55dcb5b8fbf0() {entry: 0x850}
trace back (main)
0x000547 4c 00 00 16 callb 0x16 <__die@0x55dcb3c41780>(std/lib.nas:150)
0x000856 4a 00 00 01 callfv 0x1(a.nas:3)
0x00085a 4a 00 00 00 callfv 0x0(a.nas:5)
stack (0x55dcb5b43120, limit 10, total 14)
0x00000d | null |
0x00000c | pc | 0x856
0x00000b | addr | 0x55dcb5b43130
0x00000a | nil |
0x000009 | nil |
0x000008 | str | <0x55dcb5b33670> error occurred t...
0x000007 | nil |
0x000006 | func | <0x55dcb5b8fbd0> entry:0x850
0x000005 | pc | 0x85a
0x000004 | addr | 0x0
registers (main)
[pc ] | pc | 0x547
[global] | addr | 0x55dcb5b53130
[local ] | addr | 0x55dcb5b43190
[memr ] | addr | 0x0
[canary] | addr | 0x55dcb5b53110
[top ] | addr | 0x55dcb5b431f0
[funcr ] | func | <0x55dcb5b65620> entry:0x547
[upval ] | nil |
global (0x55dcb5b53130)
0x000000 | nmspc| <0x55dcb5b33780> namespace [95 val]
0x000001 | vec | <0x55dcb5b64c20> [0 val]
...
0x00005e | func | <0x55dcb5b8fc70> entry:0x846
local (0x55dcb5b43190 <+7>)
0x000000 | nil |
0x000001 | str | <0x55dcb5b33670> error occurred t...
0x000002 | nil |
在v8.0
版本中我们添加了调试器。
使用这个命令./nasal -dbg xxx.nas
来启用调试器,接下来调试器会打开文件并输出以下内容:
展开
source code:
--> var fib=func(x)
{
if(x<2) return x;
return fib(x-1)+fib(x-2);
}
for(var i=0;i<31;i+=1)
print(fib(i),'\n');
next bytecode:
0x000848 4a 00 00 01 callfv 0x1(std/lib.nas:427)
0x000849 3d 00 00 00 pop 0x0(std/lib.nas:427)
0x00084a 07 00 00 00 pnil 0x0(std/lib.nas:423)
0x00084b 56 00 00 00 ret 0x0(std/lib.nas:423)
0x00084c 03 00 00 5e loadg 0x5e(std/lib.nas:423)
--> 0x00084d 0b 00 08 51 newf 0x851(test/fib.nas:1)
0x00084e 02 00 00 03 intl 0x3(test/fib.nas:1)
0x00084f 0d 00 00 08 para 0x8 (x)(test/fib.nas:1)
stack (0x55ccd0a1b9d0, limit 10, total 0)
>>
如果需要查看命令的使用方法,可以输入h
获取帮助信息。
当运行调试器的时候,你可以看到现在的操作数栈上到底有些什么数据。 这些信息可以帮助你调试,同时也可以帮助你理解这个虚拟机是如何工作的:
展开
source code:
var fib=func(x)
{
--> if(x<2) return x;
return fib(x-1)+fib(x-2);
}
for(var i=0;i<31;i+=1)
print(fib(i),'\n');
next bytecode:
0x000850 3e 00 08 60 jmp 0x860(test/fib.nas:1)
--> 0x000851 45 00 00 01 calll 0x1(test/fib.nas:3)
0x000852 39 00 00 07 lessc 0x7 (2)(test/fib.nas:3)
0x000853 40 00 08 56 jf 0x856(test/fib.nas:3)
0x000854 45 00 00 01 calll 0x1(test/fib.nas:3)
0x000855 56 00 00 00 ret 0x0(test/fib.nas:3)
0x000856 44 00 00 5f callg 0x5f(test/fib.nas:4)
0x000857 45 00 00 01 calll 0x1(test/fib.nas:4)
stack (0x55ccd0a1b9d0, limit 10, total 8)
0x000007 | pc | 0x869
0x000006 | addr | 0x0
0x000005 | nil |
0x000004 | nil |
0x000003 | num | 0
0x000002 | nil |
0x000001 | nil |
0x000000 | func | <0x55ccd0a58fa0> entry:0x487
>>
v11.0 版本新增了交互式解释器 (REPL),使用如下命令开启:
nasal -r
接下来就可以随便玩了~
[nasal-repl] Initializating enviroment...
[nasal-repl] Initialization complete.
Nasal REPL interpreter version 11.0 (Oct 7 2023 17:28:31)
.h, .help | show help
.e, .exit | quit the REPL
.q, .quit | quit the REPL
.c, .clear | clear the screen
.s, .source | show source code
>>>
试试引入 std/json.nas
模块 ~
[nasal-repl] Initializating enviroment...
[nasal-repl] Initialization complete.
Nasal REPL interpreter version 11.1 (Nov 1 2023 23:37:30)
.h, .help | show help
.e, .exit | quit the REPL
.q, .quit | quit the REPL
.c, .clear | clear the screen
.s, .source | show source code
>>> use std.json;
{stringify:func(..) {..},parse:func(..) {..}}
>>>