ValKmjolnir / Nasal-Interpreter

Modern efficient runtime for Nasal: using stack-based direct-threading virtual machine.
https://www.fgprc.org.cn/nasal_interpreter.html
GNU General Public License v2.0
55 stars 4 forks source link
bytecode-interpreter compiler-principles flightgear interpreter language linux macos nasal programming-language script-language scripting-language windows

Nasal - Modern Interpreter

GitHub release(latest by date) license downloads C/C++ CI

This document is also available in: 中文 | English

Contents

Contact us if having great ideas to share!

Introduction

star fork issue pr

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).

Why writing this Nasal interpreter?

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.

Download

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:

Compile

g++ clang++ vs

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.

Windows (MinGW-w64)

Make sure thread model is posix thread model, otherwise no thread library exists.

mingw32-make nasal.exe -j4

Windows (Visual Studio)

There is a CMakelists.txt to create project.

Linux / macOS / Unix

linux macOS

make -j

You could choose which compiler you want to use:

make nasal CXX=... -j

How to Use

usage

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();

Difference Between Andy's and This Interpreter

error

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: ```javascript 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: ```javascript 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" ```

Trace Back Info

stackoverflow

When interpreter crashes, it will print trace back information:

Native function `die` Function `die` is used to throw error and crash immediately. ```javascript func() { println("hello"); die("error occurred this line"); return; }(); ``` ```javascript 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: ```javascript func(f) { return f(f); }( func(f) { f(f); } )(); ``` ```javascript [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: ```javascript func() { return 0; }()[1]; ``` ```javascript [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: ```javascript 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 | ```

Debugger

dbg

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 ```javascript 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 ```javascript 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...) {..} >> ```

REPL

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(..) {..}}

>>>