Skip to content

ValKmjolnir/Nasal-Interpreter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nasal - Modern Interpreter

GitHub code size 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.

This project uses MIT license (2019/7 ~ 2021/5/4 ~ 2023/5), 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.

How to Compile

g++ clang++ vs

Better download the latest update source of the interpreter and build it! It's quite easy to build this interpreter, 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)

windows

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

mingw32-make nasal.exe -j4

Windows (Visual Studio)

windows

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");
}

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:

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"

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.

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  |

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

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:
    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
>>

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

>>>