Nasal-Interpreter/README.md

1021 lines
26 KiB
Markdown
Raw Normal View History

2023-07-04 00:33:57 +08:00
# __Nasal - Modern Interpreter__
2022-11-26 23:36:12 +08:00
<img src="./doc/pic/header.png" style="width:800px"></img>
2019-07-25 02:14:33 +08:00
2022-07-24 17:05:36 +08:00
![GitHub code size](https://img.shields.io/github/languages/code-size/ValKmjolnir/Nasal-Interpreter?style=flat-square&logo=github)
![GitHub release(latest by date)](https://img.shields.io/github/v/release/ValKmjolnir/Nasal-Interpreter?style=flat-square&logo=github)
![in dev](https://img.shields.io/badge/dev-v11.0-blue?style=flat-square&logo=github)
2023-07-02 21:37:14 +08:00
[![license](https://img.shields.io/badge/license-GPLv2-green?style=flat-square&logo=github)](./LICENSE)
> This document is also available in: [__中文__](./doc/README_zh.md) | [__English__](./README.md)
2022-06-11 12:49:26 +08:00
2022-01-25 15:02:57 +08:00
## __Contents__
* [__Introduction__](#introduction)
* [__Compile__](#how-to-compile)
* [__Usage__](#how-to-use)
2022-07-24 17:05:36 +08:00
* [__Tutorial__](#tutorial)
2022-07-24 13:01:42 +08:00
* [__Release Notes__](./doc/dev.md#release-notes)
* [__Development History__](./doc/dev.md)
* [__Benchmark__](./doc/benchmark.md)
2022-01-25 15:02:57 +08:00
* [__Difference__](#difference-between-andys-and-this-interpreter)
* [__Trace Back Info__](#trace-back-info)
* [__Debugger__](#debugger)
2023-10-04 11:52:09 +08:00
* [__REPL__](#repl)
2022-01-25 15:02:57 +08:00
__Contact us if having great ideas to share!__
2022-01-22 22:05:42 +08:00
2022-02-13 16:10:02 +08:00
* __E-mail__: __lhk101lhk101@qq.com__
2022-01-25 15:02:57 +08:00
## __Introduction__
2022-09-13 23:00:48 +08:00
[Nasal](http://wiki.flightgear.org/Nasal_scripting_language)
is an ECMAscript-like language used in [FlightGear](https://www.flightgear.org/).
2022-07-24 17:05:36 +08:00
The designer is [Andy Ross](https://github.com/andyross).
2022-01-25 15:02:57 +08:00
2023-06-26 23:59:09 +08:00
This interpreter is totally rewritten by [ValKmjolnir](https://github.com/ValKmjolnir) using `C++`(`-std=c++17`)
2022-09-13 22:14:17 +08:00
without reusing the code in [Andy Ross's nasal interpreter](https://github.com/andyross/nasal).
But we really appreciate that Andy created this amazing programming language.
2023-07-04 00:33:57 +08:00
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](https://www.fgprc.org/) told me that it is hard to debug with nasal-console in Flightgear,
2021-10-08 23:18:26 +08:00
especially when checking syntax errors.
So i wrote a new interpreter to help checking syntax error and runtime error.
2020-09-14 13:52:25 +08:00
2022-07-24 00:10:26 +08:00
I wrote the lexer, parser and
2022-09-13 22:14:17 +08:00
bytecode virtual machine to help checking errors.
We found it much easier to debug.
2022-09-13 22:14:17 +08:00
You could also use this language to write some
2021-10-08 23:18:26 +08:00
interesting programs and run them without the lib of Flightgear.
2022-01-22 22:05:42 +08:00
You could add your own modules to make
2022-09-13 22:14:17 +08:00
the interpreter a useful tool in your own projects.
2020-11-20 00:18:17 +08:00
2022-01-25 15:02:57 +08:00
## __How to Compile__
2020-11-20 00:18:17 +08:00
![windows](https://img.shields.io/badge/Microsoft-Windows-green?style=flat-square&logo=windows)
![macOS](https://img.shields.io/badge/Apple%20Inc.-MacOS-green?style=flat-square&logo=apple)
![linux](https://img.shields.io/badge/GNU-Linux-green?style=flat-square&logo=GNU)
![g++](https://img.shields.io/badge/GNU-g++-A42E2B?style=flat-square&logo=GNU)
![clang++](https://img.shields.io/badge/LLVM-clang++-262D3A?style=flat-square&logo=LLVM)
![vs](https://img.shields.io/badge/Visual_Studio-MSVC-5C2D91?style=flat-square&logo=visualstudio)
2023-01-31 19:01:36 +08:00
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.
2022-09-13 22:14:17 +08:00
__CAUTION__: If want to use the release zip/tar.gz file to build the interpreter, please read the [__Release Notes__](./doc/dev.md#release-notes) to make sure this release file has no fatal bugs.
2023-07-02 20:39:28 +08:00
### __`Windows (MinGW-w64)`__
2022-03-14 20:29:49 +08:00
Make sure your MinGW thread model is `posix thread model`, otherwise it may not have the thread library.
2023-07-09 00:59:17 +08:00
> mkdir build
>
> mingw32-make nasal.exe -j4
2022-03-14 20:29:49 +08:00
2023-07-02 20:39:28 +08:00
### __`Windows (Visual Studio)`__
This project gives a [__CMakelists.txt__](./CMakeLists.txt) for you to create project in `Visual Studio`.
2022-09-13 22:14:17 +08:00
2023-07-02 20:39:28 +08:00
### __`Linux/macOS/Unix`__
2022-03-14 20:29:49 +08:00
2023-07-09 00:59:17 +08:00
> mkdir build
>
> make -j4
2022-03-14 20:29:49 +08:00
2022-07-20 22:22:50 +08:00
You could choose which compiler you want to use:
2022-07-20 22:22:50 +08:00
> make nasal CXX=...
2022-01-25 15:02:57 +08:00
## __How to Use__
2022-09-13 23:00:48 +08:00
![usage](./doc/gif/help.gif)
2022-02-14 17:29:26 +08:00
If your system is `Windows` and you want to output unicode, you could write this in nasal code:
2022-04-03 18:10:00 +08:00
```javascript
if(os.platform()=="windows")
system("chcp 65001");
```
2022-02-15 23:42:17 +08:00
## __Tutorial__
2022-02-13 16:10:02 +08:00
2022-02-15 23:42:17 +08:00
Nasal is really __easy__ to learn.
Reading this tutorial will not takes you over 15 minutes.
__If you have learnt C/C++/Javascript before, this will take less time.__
You could totally use it after reading this simple tutorial:
2022-02-13 16:10:02 +08:00
2022-09-17 18:23:27 +08:00
<details><summary> Basic type </summary>
2022-02-13 16:10:02 +08:00
2022-09-17 18:23:27 +08:00
__`none`__ is error type used to interrupt the execution.
This type is not created by user program.
2022-02-13 16:10:02 +08:00
2022-09-17 18:23:27 +08:00
__`nil`__ is a null type. Just like `null`.
2022-02-13 16:10:02 +08:00
2022-02-15 23:42:17 +08:00
```javascript
var spc=nil;
```
2022-02-13 16:10:02 +08:00
2022-09-17 18:23:27 +08:00
__`num`__ has 3 formats: `dec`, `hex` and `oct`. Using IEEE754 `double` to store.
2022-02-13 16:10:02 +08:00
2022-02-15 23:42:17 +08:00
```javascript
# this language use '#' to write notes
var n=2.71828; # dec
var n=2.147e16; # dec
var n=1e-10; # dec
var n=0xAA55; # hex
var n=0o170001; # oct
# caution: true and false also useful in nasal now
var n=true; # in fact n is now 1.0
var n=false; # in face n is now 0.0
2022-02-15 23:42:17 +08:00
```
2019-09-25 20:49:06 +08:00
2022-09-17 18:23:27 +08:00
__`str`__ has 3 formats. The third one is used to declare a character.
2020-01-22 19:07:33 +08:00
2021-01-23 19:21:37 +08:00
```javascript
2022-02-15 23:42:17 +08:00
var s='str';
var s="another string";
var s=`c`;
2022-03-01 14:36:05 +08:00
# some special characters is allowed in this language:
2022-07-20 22:22:50 +08:00
'\a'; '\b'; '\e'; '\f';
'\n'; '\r'; '\t'; '\v';
'\0'; '\\'; '\?'; '\'';
2022-03-01 14:36:05 +08:00
'\"';
2021-01-23 19:21:37 +08:00
```
2020-12-19 01:26:15 +08:00
2022-09-17 18:23:27 +08:00
__`vec`__ has unlimited length and can store all types of values.
2021-01-23 19:21:37 +08:00
2022-02-15 23:42:17 +08:00
```javascript
var vec=[];
2022-07-24 17:05:36 +08:00
var vec=[0,nil,{},[],func(){return 0}];
2022-02-15 23:42:17 +08:00
append(vec,0,1,2);
```
2020-12-19 01:26:15 +08:00
2022-09-17 18:23:27 +08:00
__`hash`__ is a hashmap (or like a `dict` in `python`) that stores values with strings/identifiers as the key.
```javascript
2022-02-15 23:42:17 +08:00
var hash={
member1:nil,
2022-07-20 22:22:50 +08:00
member2:"str",
2022-07-24 17:05:36 +08:00
"member3":"member\'s name can also be a string constant",
funct:func(){
return me.member2~me.member3;
2022-02-15 23:42:17 +08:00
}
};
```
2022-09-17 18:23:27 +08:00
__`func`__ is a function type (in fact it is `lambda`).
```javascript
2022-09-17 18:23:27 +08:00
var f=func(x,y,z){
return nil;
}
# function could be declared without parameters and `(`, `)`
var f=func{
return 114514;
}
2022-07-24 17:05:36 +08:00
var f=func(x,y,z,deft=1){
return x+y+z+deft;
2022-02-15 23:42:17 +08:00
}
var f=func(args...){
var sum=0;
foreach(var i;args)
sum+=i;
return sum;
}
```
__`upval`__ is used to store upvalues, used in __`vm`__ to make sure closure runs correctly.
2022-02-15 23:42:17 +08:00
2022-09-17 18:23:27 +08:00
__`obj`__ is used to store other complex `C/C++` data types.
This type is created by native-function of nasal. If want to define a new data type, see how to add native-functions by editing code.
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Operators </summary>
2022-09-17 18:23:27 +08:00
Nasal has basic math operators `+` `-` `*` `/` and a special operator `~` that joints strings.
2022-02-15 23:42:17 +08:00
```javascript
2022-07-24 17:05:36 +08:00
1+2-(1+3)*(2+4)/(16-9);
2022-09-17 18:23:27 +08:00
"str1"~"str2";
2022-02-15 23:42:17 +08:00
```
2022-02-15 23:42:17 +08:00
For conditional expressions, operators `==` `!=` `<` `>` `<=` `>=` are used to compare two values.
2022-09-17 18:23:27 +08:00
`and` `or` have the same function as C/C++ `&&` `||`.
2022-02-15 23:42:17 +08:00
```javascript
2022-09-17 18:23:27 +08:00
1+1 and (1<0 or 1>0);
2022-02-15 23:42:17 +08:00
1<=0 and 1>=0;
1==0 or 1!=0;
```
2022-02-15 23:42:17 +08:00
Unary operators `-` `!` have the same function as C/C++.
2022-02-15 23:42:17 +08:00
```javascript
-1;
!0;
```
2020-09-13 16:40:23 +08:00
Bitwise operators `~` `|` `&` `^` have the same function as C/C++.
```javascript
# these operators will:
# 1. convert f64 to i32 (static_cast<int32_t>)
# 2. do the bitwise function
~0x80000000; # not 2147483647
0x8|0x1; # or
0x1&0x2; # and
0x8^0x1; # xor
```
Operators `=` `+=` `-=` `*=` `/=` `~=` `^=` `&=` `|=` are used in assignment expressions.
2020-09-13 16:40:23 +08:00
2022-02-15 23:42:17 +08:00
```javascript
a=b=c=d=1;
2022-09-17 18:23:27 +08:00
a+=1;
a-=1;
a*=1;
a/=1;
a~="string";
a^=0xff;
a&=0xca;
a|=0xba;
2022-02-15 23:42:17 +08:00
```
2020-09-13 16:40:23 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Definition </summary>
2020-01-13 14:13:35 +08:00
2022-09-17 18:23:27 +08:00
As follows.
2022-02-15 23:42:17 +08:00
```javascript
2022-09-17 18:23:27 +08:00
var a=1; # define single variable
var (a,b,c)=[0,1,2]; # define multiple variables from a vector
var (a,b,c)=(0,1,2); # define multiple variables from a tuple
2022-02-15 23:42:17 +08:00
```
2019-10-13 11:01:31 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Multi-assignment </summary>
2020-06-01 01:28:49 +08:00
2022-02-15 23:42:17 +08:00
The last one is often used to swap two variables.
2020-09-13 16:40:23 +08:00
2022-02-15 23:42:17 +08:00
```javascript
(a,b[0],c.d)=[0,1,2];
(a,b[1],c.e)=(0,1,2);
(a,b)=(b,a);
```
2020-06-01 01:28:49 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Conditional expression </summary>
2020-06-01 01:28:49 +08:00
2022-02-15 23:42:17 +08:00
In nasal there's a new key word `elsif`.
It has the same functions as `else if`.
2020-09-13 16:40:23 +08:00
2022-02-15 23:42:17 +08:00
```javascript
if(1){
;
}elsif(2){
;
}else if(3){
;
}else{
;
}
```
2020-12-19 01:26:15 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Loop </summary>
2020-12-19 01:26:15 +08:00
2022-02-15 23:42:17 +08:00
While loop and for loop is simalar to C/C++.
2020-12-19 01:26:15 +08:00
2022-02-15 23:42:17 +08:00
```javascript
while(condition)
continue;
for(var i=0;i<10;i+=1)
break;
```
2020-12-12 20:13:23 +08:00
2022-02-15 23:42:17 +08:00
Nasal has another two kinds of loops that iterates through a vector:
2020-12-12 20:13:23 +08:00
2022-02-15 23:42:17 +08:00
`forindex` will get the index of a vector. Index will be `0` to `size(elem)-1`.
2020-12-12 20:13:23 +08:00
2022-02-15 23:42:17 +08:00
```javascript
forindex(var i;elem)
print(elem[i]);
```
2020-12-12 20:13:23 +08:00
2022-02-15 23:42:17 +08:00
`foreach` will get the element of a vector. Element will be `elem[0]` to `elem[size(elem)-1]`.
2020-12-12 20:13:23 +08:00
```javascript
2022-02-15 23:42:17 +08:00
foreach(var i;elem)
print(i);
2020-12-12 20:13:23 +08:00
```
2020-09-13 16:40:23 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Subvec </summary>
2020-12-19 01:26:15 +08:00
Nasal provides this special syntax to help user generate a new vector by getting values by one index or getting values by indexes in a range from an old vector.
If there's only one index in the bracket, then we will get the value directly.
2022-02-15 23:42:17 +08:00
Use index to search one element in the string will get the __ascii number__ of this character.
If you want to get the character, use built-in function `chr()`.
2020-12-19 01:26:15 +08:00
2022-02-15 23:42:17 +08:00
```javascript
a[0];
2022-02-15 23:42:17 +08:00
a[-1,1,0:2,0:,:3,:,nil:8,3:nil,nil:nil];
"hello world"[0];
```
2020-12-19 01:26:15 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Special function call </summary>
2021-01-23 19:21:37 +08:00
2022-09-17 18:23:27 +08:00
This is not very efficient,
because hashmap use string as the key to compare.
But if it really useful, the efficientcy may not be so important...
2021-01-23 19:21:37 +08:00
2022-02-15 23:42:17 +08:00
```javascript
f(x:0,y:nil,z:[]);
```
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Lambda </summary>
2022-02-15 23:42:17 +08:00
Also functions have this kind of use:
2022-02-15 23:42:17 +08:00
```javascript
2022-09-17 18:23:27 +08:00
func(x,y){
return x+y
}(0,1);
func(x){
return 1/(1+math.exp(-x));
}(0.5);
2022-02-15 23:42:17 +08:00
```
2022-02-15 23:42:17 +08:00
There's an interesting test file `y-combinator.nas`,
try it for fun:
2022-02-15 23:42:17 +08:00
```javascript
var fib=func(f){
return f(f);
}(
func(f){
return func(x){
if(x<2) return x;
return f(f)(x-1)+f(f)(x-2);
}
}
);
```
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Closure </summary>
2022-02-15 23:42:17 +08:00
Closure means you could get the variable that is not in the local scope of a function that you called.
Here is an example, result is `1`:
2022-02-15 23:42:17 +08:00
```javascript
var f=func(){
var a=1;
return func(){return a;};
}
print(f()());
```
2022-02-15 23:42:17 +08:00
Using closure makes it easier to OOP.
```javascript
2022-02-15 23:42:17 +08:00
var student=func(n,a){
var (name,age)=(n,a);
return {
print_info:func() {println(name,' ',age);},
set_age: func(a){age=a;},
get_age: func() {return age;},
set_name: func(n){name=n;},
get_name: func() {return name;}
};
}
```
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Trait </summary>
2022-02-15 23:42:17 +08:00
Also there's another way to OOP, that is `trait`.
When a hash has a member named `parents` and the value type is vector,
then when you are trying to find a member that is not in this hash,
virtual machine will search the member in `parents`.
If there is a hash that has the member, you will get the member's value.
Using this mechanism, we could OOP like this, the result is `114514`:
```javascript
var trait={
get:func{return me.val;},
set:func(x){me.val=x;}
};
var class={
new:func(){
return {
val:nil,
parents:[trait]
};
}
};
var a=class.new();
a.set(114514);
println(a.get());
```
2022-02-15 23:42:17 +08:00
First virtual machine cannot find member `set` in hash `a`, but in `a.parents` there's a hash `trait` has the member `set`, so we get the `set`.
variable `me` points to hash `a`, so we change the `a.val`.
And `get` has the same process.
And we must remind you that if you do this:
2022-02-15 23:42:17 +08:00
```javascript
2022-07-04 00:16:04 +08:00
var trait={
get:func{return me.val;},
set:func(x){me.val=x;}
};
var class={
new:func(){
return {
val:nil,
parents:[trait]
};
}
};
var a=class.new();
var b=class.new();
a.set(114);
b.set(514);
println(a.get());
println(b.get());
var c=a.get;
var d=b.get;
println(c());
println(c());
println(d());
println(d());
2022-02-15 23:42:17 +08:00
```
2022-07-04 00:16:04 +08:00
You will get this result now:
```bash
2022-07-04 00:16:04 +08:00
114
514
514
514
514
514
```
2022-07-04 00:16:04 +08:00
Because `a.get` will set `me=a` in the `trait.get`. Then `b.get` do the `me=b`. So in fact c is `b.get` too after running `var d=b.get`.
If you want to use this trick to make the program running more efficiently, you must know this special mechanism.
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Native functions and module import </summary>
2022-09-17 18:23:27 +08:00
This part shows how we add native functions in this interpreter.
If you are interested in this part, this may help you.
And...
2022-09-17 18:23:27 +08:00
__CAUTION:__ If you want to add your own functions __without__ changing the source code, see the __`module`__ after this part.
If you really want to change source code, check built-in functions in `lib.nas` and see the example below.
2022-02-15 23:42:17 +08:00
Definition:
2021-10-08 23:18:26 +08:00
2022-02-15 23:42:17 +08:00
```C++
// you could also use a macro to define one.
nas_native(builtin_print);
2022-02-15 23:42:17 +08:00
```
2021-10-08 23:18:26 +08:00
2022-02-15 23:42:17 +08:00
Then complete this function using C++:
2022-02-15 23:42:17 +08:00
```C++
2022-10-23 01:29:20 +08:00
var builtin_print(var* local,gc& ngc)
2022-02-15 23:42:17 +08:00
{
// find value with index begin from 1
// because local[0] is reserved for value 'me'
var vec=local[1];
2022-02-15 23:42:17 +08:00
// main process
// also check number of arguments and type here
// if get an error,use nas_err
for(auto& i:vec.vec().elems)
2022-02-15 23:42:17 +08:00
switch(i.type)
{
2022-07-19 23:55:12 +08:00
case vm_none: std::cout<<"undefined"; break;
case vm_nil: std::cout<<"nil"; break;
case vm_num: std::cout<<i.num(); break;
case vm_str: std::cout<<i.str(); break;
case vm_vec: std::cout<<i.vec(); break;
case vm_hash: std::cout<<i.hash(); break;
2022-07-19 23:55:12 +08:00
case vm_func: std::cout<<"func(..){..}";break;
case vm_obj: std::cout<<"<object>"; break;
2022-02-15 23:42:17 +08:00
}
std::cout<<std::flush;
// generate return value,
2022-10-23 01:29:20 +08:00
// use ngc::alloc(type) to make a new value
2022-02-15 23:42:17 +08:00
// or use reserved reference nil/one/zero
return nil;
}
```
2022-09-17 18:23:27 +08:00
When running a builtin function, alloc will run more than one time, this may cause mark-sweep in `gc::alloc`.
The value got before will be collected, but stil in use in this builtin function, this will cause a fatal error.
So use `gc::temp` in builtin functions to temprorarily store the gc-managed value that you want to return later. Like this:
```C++
2022-10-23 01:29:20 +08:00
var builtin_keys(var* local,gc& ngc)
2022-09-17 18:23:27 +08:00
{
var hash=local[1];
2022-09-17 18:23:27 +08:00
if(hash.type!=vm_hash)
return nas_err("keys","\"hash\" must be hash");
// use gc.temp to store the gc-managed-value, to avoid being sweeped
2022-10-23 01:29:20 +08:00
var res=ngc.temp=ngc.alloc(vm_vec);
2022-09-17 18:23:27 +08:00
auto& vec=res.vec().elems;
for(auto& iter:hash.hash().elems)
2022-10-23 01:29:20 +08:00
vec.push_back(ngc.newstr(iter.first));
ngc.temp=nil;
2022-09-17 18:23:27 +08:00
return res;
}
```
2022-02-15 23:42:17 +08:00
After that, register the built-in function's name(in nasal) and the function's pointer in this table:
```C++
struct func
{
const char* name;
2022-10-23 01:29:20 +08:00
var (*func)(var*,gc&);
} builtin[]=
2022-02-15 23:42:17 +08:00
{
2022-07-09 16:24:58 +08:00
{"__print",builtin_print},
2022-07-20 22:22:50 +08:00
{nullptr, nullptr }
2022-02-15 23:42:17 +08:00
};
```
2022-07-09 16:24:58 +08:00
At last,warp the `__print` in a nasal file:
```javascript
2022-02-15 23:42:17 +08:00
var print=func(elems...){
2022-07-09 16:24:58 +08:00
return __print(elems);
2022-02-15 23:42:17 +08:00
};
```
2022-07-09 16:24:58 +08:00
In fact the arguments that `__print` uses are not necessary.
2022-02-15 23:42:17 +08:00
So writting it like this is also right:
```javascript
var print=func(elems...){
2022-07-09 16:24:58 +08:00
return __print;
2022-02-15 23:42:17 +08:00
};
```
2022-02-15 23:42:17 +08:00
If you don't warp built-in function in a normal nasal function,
2022-09-17 18:23:27 +08:00
this native function may cause __segmentation fault__ when searching arguments.
Use `import("filename.nas")` to get the nasal file including your built-in functions, then you could use it.
Also there's another way of importing nasal files, the two way of importing have the same function:
```javascript
import.dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
```
2022-07-24 17:05:36 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary> Modules(for lib developers) </summary>
2022-02-15 23:42:17 +08:00
If there is only one way to add your own functions into nasal,
that is really inconvenient.
2022-02-15 23:42:17 +08:00
Luckily, we have developed some useful native-functions to help you add modules that created by you.
2023-10-04 11:52:09 +08:00
Functions used to load dynamic libraries are added to `std/dylib.nas`:
2022-02-15 23:42:17 +08:00
```javascript
2023-10-04 11:52:09 +08:00
var dlopen = func(libname) {
...
}
var dlclose = func(lib) {
...
}
var dlcall = func(ptr, args...) {
...
}
var limitcall = func(arg_size = 0) {
...
}
```
As you could see, these functions are used to load dynamic libraries into the nasal runtime and execute.
2022-02-15 23:42:17 +08:00
Let's see how they work.
2022-02-15 23:42:17 +08:00
First, write a cpp file that you want to generate the dynamic lib, take the `fib.cpp` as the example(example codes are in `./module`):
2022-02-15 23:42:17 +08:00
```C++
// add header file nasal.h to get api
#include "nasal.h"
2023-10-04 11:52:09 +08:00
double fibonaci(double x) {
if (x<=2) {
2022-02-15 23:42:17 +08:00
return x;
2023-10-04 11:52:09 +08:00
}
2022-02-15 23:42:17 +08:00
return fibonaci(x-1)+fibonaci(x-2);
}
// module functions' parameter list example
2023-10-04 11:52:09 +08:00
var fib(var* args, usize size, gc* ngc) {
if (!size) {
return nas_err("fib", "lack arguments");
}
2022-02-15 23:42:17 +08:00
// the arguments are generated into a vm_vec: args
// get values from the vector that must be used here
2023-10-04 11:52:09 +08:00
var num = args[0];
2022-02-15 23:42:17 +08:00
// if you want your function safer, try this
// nas_err will print the error info on screen
2022-02-15 23:42:17 +08:00
// and return vm_null for runtime to interrupt
2023-10-04 11:52:09 +08:00
if(num.type!=vm_num) {
return nas_err("extern_fib", "\"num\" must be number");
}
2022-02-15 23:42:17 +08:00
// ok, you must know that vm_num now is not managed by gc
// if want to return a gc object, use ngc->alloc(type)
2022-02-15 23:42:17 +08:00
// usage of gc is the same as adding a native function
return var::num(fibonaci(num.tonum()));
2022-02-15 23:42:17 +08:00
}
2022-11-15 21:23:34 +08:00
// then put function name and address into this table
// make sure the end of the table is {nullptr,nullptr}
2023-10-04 11:52:09 +08:00
module_func_info func_tbl[] = {
{"fib", fib},
{nullptr, nullptr}
2022-11-15 21:23:34 +08:00
};
// must write this function, this will help nasal to
// get the function pointer by name
// the reason why using this way to get function pointer
// is because `var` has constructors, which is not compatiable in C
// so "extern "C" var fib" may get compilation warnings
2023-10-04 11:52:09 +08:00
extern "C" module_func_info* get() {
2022-11-15 21:23:34 +08:00
return func_tbl;
}
```
2022-02-15 23:42:17 +08:00
Next, compile this `fib.cpp` into dynamic lib.
2022-02-15 23:42:17 +08:00
Linux(`.so`):
2021-10-08 23:18:26 +08:00
2022-02-15 23:42:17 +08:00
`clang++ -c -O3 fib.cpp -fPIC -o fib.o`
2021-10-08 23:18:26 +08:00
2022-02-15 23:42:17 +08:00
`clang++ -shared -o libfib.so fib.o`
2021-10-08 23:18:26 +08:00
2022-02-15 23:42:17 +08:00
Mac(`.so` & `.dylib`): same as Linux.
2021-10-08 23:18:26 +08:00
2022-02-15 23:42:17 +08:00
Windows(`.dll`):
2022-02-15 23:42:17 +08:00
`g++ -c -O3 fib.cpp -fPIC -o fib.o`
2022-02-15 23:42:17 +08:00
`g++ -shared -o libfib.dll fib.o`
2022-09-17 18:23:27 +08:00
Then we write a test nasal file to run this fib function, using `os.platform()` we could write a cross-platform program:
2022-02-15 23:42:17 +08:00
```javascript
2023-10-04 11:52:09 +08:00
import.std.dylib;
var dlhandle = dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));
var fib = dlhandle.fib;
for(var i = 1; i<30; i+=1)
println(dylib.dlcall(fib, i));
2022-11-15 21:23:34 +08:00
dylib.dlclose(dlhandle.lib);
```
2022-11-15 21:23:34 +08:00
`dylib.dlopen` is used to load dynamic library and get the function address.
2022-01-22 22:05:42 +08:00
`dylib.dlcall` is used to call the function, the first argument is the function address, make sure this argument is `vm_obj` and `type=obj_extern`.
2022-02-13 16:10:02 +08:00
`dylib.dlclose` is used to unload the library, at the moment that you call the function, all the function addresses that got from it are invalid.
2022-02-13 16:10:02 +08:00
`dylib.limitcall` is used to get `dlcall` function that has limited parameter size, this function will prove the performance of your code because it does not use `vm_vec` to store the arguments, instead it uses local scope to store them, so this could avoid frequently garbage collecting. And the code above could also be written like this:
```javascript
2023-10-04 11:52:09 +08:00
import.std.dylib;
var dlhandle = dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));
var fib = dlhandle.fib;
var invoke = dylib.limitcall(1); # this means the called function has only one parameter
for(var i = 1; i<30; i+=1)
println(invoke(fib, i));
2022-11-15 21:23:34 +08:00
dylib.dlclose(dlhandle.lib);
```
2022-02-15 23:42:17 +08:00
If get this, Congratulations!
2022-02-13 16:10:02 +08:00
2022-02-15 23:42:17 +08:00
```bash
./nasal a.nas
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
```
2022-02-13 16:10:02 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-01-25 15:02:57 +08:00
## __Difference Between Andy's and This Interpreter__
2022-09-13 23:00:48 +08:00
![error](./doc/gif/error.gif)
2022-09-13 22:14:17 +08:00
<details><summary>Must use `var` to define variables</summary>
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.
2023-07-10 20:34:21 +08:00
So do not use variable without using `var` to declare it.
In Andy's interpreter:
```javascript
foreach(i;[0,1,2,3])
print(i)
```
2022-07-25 20:09:41 +08:00
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.
2022-07-25 20:09:41 +08:00
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
2022-09-13 22:14:17 +08:00
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"
```
2022-09-13 22:14:17 +08:00
</details>
2022-01-25 15:02:57 +08:00
## __Trace Back Info__
2021-08-10 17:55:49 +08:00
2022-09-13 23:00:48 +08:00
![stackoverflow](./doc/gif/stackoverflow.gif)
2022-07-25 20:09:41 +08:00
When interpreter crashes,
2021-10-08 23:18:26 +08:00
it will print trace back information:
2021-08-10 17:55:49 +08:00
2022-09-13 22:14:17 +08:00
<details><summary>Native function `die`</summary>
2022-01-25 15:02:57 +08:00
Function `die` is used to throw error and crash immediately.
2021-08-10 17:55:49 +08:00
```javascript
func()
{
println("hello");
die("error occurred this line");
return;
}();
```
```javascript
hello
[vm] error: error occurred this line
[vm] native function error.
2021-08-10 17:55:49 +08:00
trace back:
2022-09-13 22:14:17 +08:00
0x000000ac 40 00 00 00 25 callb 0x25 <__die@0x41afc0> (lib.nas:131)
0x000004f6 3e 00 00 00 01 callfv 0x1 (a.nas:4)
0x000004fa 3e 00 00 00 00 callfv 0x0 (a.nas:6)
vm stack (0x7fffcd21bc68 <sp+80>, limit 10, total 12):
2022-05-25 14:05:58 +08:00
0x0000005b | null |
2022-07-24 00:10:26 +08:00
...
2022-05-25 14:05:58 +08:00
0x00000057 | str | <0x138ff60> error occurred t...
2022-07-24 00:10:26 +08:00
...
2022-04-27 12:49:29 +08:00
0x00000052 | nil |
2021-08-10 17:55:49 +08:00
```
2022-07-25 20:09:41 +08:00
</details>
2022-09-13 23:00:48 +08:00
<details><summary>Stack overflow</summary>
2022-01-25 15:02:57 +08:00
2021-08-10 17:55:49 +08:00
Here is an example of stack overflow:
2021-08-10 17:55:49 +08:00
```javascript
func(f){
return f(f);
}(
func(f){
f(f);
}
)();
```
```javascript
[vm] stack overflow
trace back:
2022-09-13 22:14:17 +08:00
0x000004fb 3e 00 00 00 01 callfv 0x1 (a.nas:5)
0x000004fb 1349 same call(s)
0x000004f3 3e 00 00 00 01 callfv 0x1 (a.nas:2)
0x000004ff 3e 00 00 00 01 callfv 0x1 (a.nas:3)
vm stack (0x7fffd3781d58 <sp+80>, limit 10, total 8108):
2022-05-25 14:05:58 +08:00
0x00001ffb | func | <0x15f8d90> entry:0x4f9
0x00001ffa | func | <0x15f8d90> entry:0x4f9
0x00001ff9 | pc | 0x4fb
2022-07-24 00:10:26 +08:00
...
2022-05-25 14:05:58 +08:00
0x00001ff2 | addr | 0x7fffd37a16e8
2021-10-08 23:18:26 +08:00
```
2021-10-14 13:42:07 +08:00
2022-07-25 20:09:41 +08:00
</details>
2022-09-13 22:14:17 +08:00
<details><summary>Normal vm error crash info</summary>
2022-01-25 15:02:57 +08:00
2021-10-14 13:42:07 +08:00
Error will be thrown if there's a fatal error when executing:
2021-10-14 13:42:07 +08:00
```javascript
func(){
return 0;
}()[1];
```
```javascript
[vm] callv: must call a vector/hash/string
2021-10-14 13:42:07 +08:00
trace back:
2022-09-13 22:14:17 +08:00
0x000004f4 3b 00 00 00 00 callv 0x0 (a.nas:3)
vm stack (0x7fffff539c28 <sp+80>, limit 10, total 1):
2022-05-25 14:05:58 +08:00
0x00000050 | num | 0
```
2022-07-25 20:09:41 +08:00
</details>
2022-01-25 15:02:57 +08:00
2022-09-13 22:14:17 +08:00
<details><summary>Detailed crash info</summary>
2022-07-25 20:09:41 +08:00
Use command __`-d`__ or __`--detail`__ the trace back info will show more details:
2022-07-24 17:05:36 +08:00
```javascript
2022-04-27 12:49:29 +08:00
hello
[vm] error: error occurred this line
2022-09-13 22:14:17 +08:00
[vm] error: native function error
trace back (main)
0x000000b0 40 00 00 00 2b callb 0x2b <__die@0x41c380> (lib.nas:131)
0x00000553 3e 00 00 00 01 callfv 0x1 (test.nas:4)
0x00000557 3e 00 00 00 00 callfv 0x0 (test.nas:6)
vm stack (0x7fffe0ffed90 <sp+63>, limit 10, total 12)
0x0000004a | null |
0x00000049 | pc | 0x553
0x00000048 | addr | 0x7fffe0ffeda0
2022-07-24 00:10:26 +08:00
...
2022-09-13 22:14:17 +08:00
0x00000041 | nil |
registers (main)
[ pc ] | pc | 0xb0
[ global ] | addr | 0x7fffe0ffe9a0
[ localr ] | addr | 0x7fffe0ffedf0
2022-05-25 14:05:58 +08:00
[ memr ] | addr | 0x0
2022-09-13 22:14:17 +08:00
[ canary ] | addr | 0x7fffe1002990
[ top ] | addr | 0x7fffe0ffee40
[ funcr ] | func | <0x677cd0> entry:0xb0
2022-05-25 14:05:58 +08:00
[ upvalr ] | nil |
2022-09-13 22:14:17 +08:00
global (0x7fffe0ffe9a0 <sp+0>)
0x00000000 | func | <0x65fb00> entry:0x5
0x00000001 | func | <0x65fb20> entry:0xd
2022-07-20 00:06:00 +08:00
...
2022-09-13 22:14:17 +08:00
0x0000003d | func | <0x66bf00> entry:0x51f
0x0000003e | hash | <0x65ffa0> {5 val}
local (0x7fffe0ffedf0 <sp+45>)
2022-04-27 12:49:29 +08:00
0x00000000 | nil |
2022-09-13 22:14:17 +08:00
0x00000001 | str | <0x6cb630> error occurred t...
```
2021-12-23 21:46:53 +08:00
2022-07-24 17:05:36 +08:00
</details>
2022-01-25 15:02:57 +08:00
## __Debugger__
2021-12-23 21:46:53 +08:00
2022-09-13 23:00:48 +08:00
![dbg](./doc/gif/dbg.gif)
2022-07-24 00:10:26 +08:00
We added a debugger in `v8.0`.
2021-12-23 21:46:53 +08:00
Use command `./nasal -dbg xxx.nas` to use the debugger,
and the debugger will print this:
2022-09-13 22:14:17 +08:00
<details><summary>Click to unfold</summary>
2021-12-23 21:46:53 +08:00
```javascript
source code:
2022-09-13 22:14:17 +08:00
--> var fib=func(x)
{
2022-07-24 00:10:26 +08:00
if(x<2) return x;
return fib(x-1)+fib(x-2);
2022-09-13 22:14:17 +08:00
}
for(var i=0;i<31;i+=1)
2022-07-24 00:10:26 +08:00
print(fib(i),'\n');
2021-12-23 21:46:53 +08:00
next bytecode:
2022-09-13 22:14:17 +08:00
--> 0x00000000 01 00 00 00 41 intg 0x41 (test/fib.nas:0)
0x00000001 0b 00 00 00 05 newf 0x5 (lib.nas:6)
0x00000002 02 00 00 00 02 intl 0x2 (lib.nas:6)
0x00000003 0f 00 00 00 00 dyn 0x0 ("elems") (lib.nas:6)
0x00000004 32 00 00 00 07 jmp 0x7 (lib.nas:6)
0x00000005 40 00 00 00 00 callb 0x0 <__print@0x419c80> (lib.nas:7)
0x00000006 4a 00 00 00 00 ret 0x0 (lib.nas:7)
0x00000007 03 00 00 00 00 loadg 0x0 (lib.nas:6)
vm stack (0x7fffd0259138 <sp+65>, limit 10, total 0)
2021-12-23 21:46:53 +08:00
>>
```
2022-09-13 22:14:17 +08:00
</details>
2021-12-23 21:46:53 +08:00
If want help, input `h` to get help.
2022-01-25 15:02:57 +08:00
2022-02-13 16:10:02 +08:00
When running the debugger, you could see what is on stack.
This will help you debugging or learning how the vm works:
2022-09-13 22:14:17 +08:00
<details><summary>Click to unfold</summary>
2022-04-27 12:49:29 +08:00
```javascript
2022-02-13 16:10:02 +08:00
source code:
2022-09-13 22:14:17 +08:00
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');
2022-07-24 00:10:26 +08:00
2022-02-13 16:10:02 +08:00
next bytecode:
2022-09-13 22:14:17 +08:00
0x00000548 0c 00 00 00 aa happ 0xaa ("running") (lib.nas:503)
0x00000549 03 00 00 00 3e loadg 0x3e (lib.nas:498)
0x0000054a 0b 00 00 05 4e newf 0x54e (test/fib.nas:1)
0x0000054b 02 00 00 00 02 intl 0x2 (test/fib.nas:1)
0x0000054c 0d 00 00 00 1b para 0x1b ("x") (test/fib.nas:1)
0x0000054d 32 00 00 05 5d jmp 0x55d (test/fib.nas:1)
--> 0x0000054e 39 00 00 00 01 calll 0x1 (test/fib.nas:3)
0x0000054f 2d 00 00 00 03 lessc 0x3 (2) (test/fib.nas:3)
vm stack (0x7fffd0259138 <sp+65>, limit 10, total 7)
0x00000047 | pc | 0x566
0x00000046 | addr | 0x0
0x00000045 | nil |
0x00000044 | num | 0
0x00000043 | nil |
0x00000042 | nil |
0x00000041 | func | <0x88d2f0> entry:0x5
>>
2022-02-13 16:10:02 +08:00
```
2022-09-13 22:14:17 +08:00
</details>
2023-10-04 11:52:09 +08:00
## REPL
We added experimental repl interpreter in v11.0.
Use this command to use the repl interpreter:
> nasal -r