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 release(latest by date) license downloads C/C++ CI Test 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.

mkdir build; 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:

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

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

>>>

Web Interface

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

Web Code Editor

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

Web REPL

  • ** 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

Running the Web Interface

  1. Build the Nasal shared library:
cmake -DBUILD_SHARED_LIBS=ON .
make nasal-web
  1. 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