- Introduction
- Download
- Compile
- Usage
- Tutorial
- Release Notes
- Development History
- Benchmark
- Difference
- Trace Back Info
- Debugger
- REPL
- Web Interface
Contact us if having great ideas to share!
-
lhk101lhk101@qq.com (ValKmjolnir)
-
sidi.liang@gmail.com (Sidi)
Nasal
is an ECMAscript-like language used in FlightGear.
The designer is Andy Ross.
This interpreter is rewritten by ValKmjolnir using C++
(-std=c++17
).
We really appreciate that Andy created this amazing programming language: Andy Ross's nasal interpreter.
Old version of this project uses MIT license (2019/7 ~ 2021/5/4 ~ 2023/5). Now it uses GPL v2 license (since 2023/6).
2019 summer, members in FGPRC told me that it is hard to debug with nasal-console in Flightgear, especially when checking syntax errors. So i wrote a new interpreter to help checking syntax error and runtime error.
I wrote the lexer, parser and bytecode virtual machine to help checking errors. We found it much easier to debug.
You could also use this language to write some interesting programs and run them without the lib of Flightgear. You could add your own modules to make the interpreter a useful tool in your own projects.
Nightly build could be found here. Windows nightly build is not supported yet, please wait or just compile it by yourself, a Cmake file is given for Visual Studio to compile this project easily:
Download the latest source of the interpreter and build it! It's quite easy to build, what you need are only two things: C++ compiler and the make
. There is no third-party library used in this project.
Make sure thread model is posix thread model
, otherwise no thread library exists.
mkdir build; mingw32-make nasal.exe -j4
There is a CMakelists.txt to create project.
make -j
You could choose which compiler you want to use:
make nasal CXX=... -j
If your system is Windows
and you want to output unicode, you could write this in nasal code:
if (os.platform()=="windows") {
system("chcp 65001");
}
Or use std.runtime.windows.set_utf8_output()
:
use std.runtime;
runtime.windows.set_utf8_output();
Must use `var` to define variables
This interpreter uses more strict syntax to make sure it is easier for you to program and debug.
And flightgear's nasal interpreter also has the same rule.
So do not use variable without using var
to declare it.
In Andy's interpreter:
foreach(i; [0, 1, 2, 3])
print(i)
This program can run normally.
But take a look at the iterator i
,
it is defined in foreach without using keyword var
.
I think this design will make programmers feeling confused that they maybe hard to find the i
is defined here.
Without var
, they may think this i
is defined anywhere else.
So in this interpreter i use a more strict syntax to force users to use var
to define iterator of forindex and foreach.
If you forget to add the keyword var
, you will get this:
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"
When interpreter crashes, it will print trace back information:
Native function `die`
Function die
is used to throw error and crash immediately.
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
Stack overflow
Here is an example of stack overflow:
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
Normal vm error crash info
Error will be thrown if there's a fatal error when executing:
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
Detailed crash info
Use command -d
or --detail
the trace back info will show more details:
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 |
We added a debugger in v8.0
.
Use command ./nasal -dbg xxx.nas
to use the debugger,
and the debugger will print this:
Click to unfold
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:
0x0003a8 07:00 00 00 00 00 00 00 00 pnil 0x0 (std/lib.nas:413)
0x0003a9 56:00 00 00 00 00 00 00 00 ret 0x0 (std/lib.nas:413)
0x0003aa 03:00 00 00 00 00 00 00 56 loadg 0x56 (std/lib.nas:413)
--> 0x0003ab 0b:00 00 00 00 00 00 03 af newf 0x3af (test/fib.nas:1)
0x0003ac 02:00 00 00 00 00 00 00 03 intl 0x3 (test/fib.nas:1)
0x0003ad 0d:00 00 00 00 00 00 00 22 para 0x22 (x) (test/fib.nas:1)
0x0003ae 3e:00 00 00 00 00 00 03 be jmp 0x3be (test/fib.nas:1)
0x0003af 45:00 00 00 00 00 00 00 01 calll 0x1 (test/fib.nas:2)
vm stack (0x7fca7e9f1010, limit 16, total 0)
>>
If want help, input h
to get help.
When running the debugger, you could see what is on stack. This will help you debugging or learning how the vm works:
Click to unfold
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:
0x0003a8 07:00 00 00 00 00 00 00 00 pnil 0x0 (std/lib.nas:413)
0x0003a9 56:00 00 00 00 00 00 00 00 ret 0x0 (std/lib.nas:413)
0x0003aa 03:00 00 00 00 00 00 00 56 loadg 0x56 (std/lib.nas:413)
0x0003ab 0b:00 00 00 00 00 00 03 af newf 0x3af (test/fib.nas:1)
0x0003ac 02:00 00 00 00 00 00 00 03 intl 0x3 (test/fib.nas:1)
0x0003ad 0d:00 00 00 00 00 00 00 22 para 0x22 (x) (test/fib.nas:1)
0x0003ae 3e:00 00 00 00 00 00 03 be jmp 0x3be (test/fib.nas:1)
--> 0x0003af 45:00 00 00 00 00 00 00 01 calll 0x1 (test/fib.nas:2)
vm stack (0x7fca7e9f1010, limit 16, total 8)
0x000007 | pc | 0x3c7
0x000006 | addr | 0x0
0x000005 | nil |
0x000004 | nil |
0x000003 | num | 0
0x000002 | nil |
0x000001 | nil |
0x000000 | func | <0x5573f66ef5f0> func(elems...) {..}
>>
We added experimental repl interpreter in v11.0. Use this command to use the repl interpreter:
nasal -r
Then enjoy!
[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
>>>
Try import 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(..) {..}}
>>>
A web-based interface is now available for trying out Nasal code directly in your browser. It includes both a code editor and an interactive REPL (WIP).
- Syntax highlighting using CodeMirror
- Error highlighting and formatting
- Example programs
- Execution time display option
- Configurable execution time limits
- Notice: The security of the online interpreter is not well tested, please use it with sandbox mechanism or other security measures.
- ** IMPORTANT: The time limit in REPL is not correctly implemented yet. Thus this REPL web binding is not considered finished. Do not use it in production before it's fixed. **
- Interactive command-line style interface in browser
- Multi-line input support with proper prompts (
>>>
and...
) - Command history navigation
- Error handling with formatted error messages
- Example snippets for quick testing
- Build the Nasal shared library:
cmake -DBUILD_SHARED_LIBS=ON .
make nasal-web
- Set up and run the web application:
For the code editor:
cd nasal-web-app
npm install
node server.js
Visit http://127.0.0.1:3000/
For the REPL:
cd nasal-web-app
npm install
node server_repl.js
Visit http://127.0.0.1:3001/repl.html