complete simple tutorial

This commit is contained in:
ValKmjolnir 2021-10-14 13:42:07 +08:00
parent 818685c48d
commit 58ea303202
2 changed files with 220 additions and 148 deletions

364
README.md
View File

@ -3,45 +3,37 @@
## Introduction ## Introduction
[Nasal](http://wiki.flightgear.org/Nasal_scripting_language) [Nasal](http://wiki.flightgear.org/Nasal_scripting_language)
is a script language that used in [FlightGear](https://www.flightgear.org/). is an ECMAscript-like programming language that used in [FlightGear](https://www.flightgear.org/).
The interpreter is totally rewritten by ValKmjolnir using C++(standard c++11) The interpreter is totally rewritten by [ValKmjolnir](https://github.com/ValKmjolnir) using C++(`-std=c++11`)
without reusing the code in Andy Ross's nasal interpreter(<https://github.com/andyross/nasal>). 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 and his interpreter project. But we really appreciate that Andy created this amazing programming language and his interpreter project.
The interpreter is still in development(now it works well --2021/2/15) Now this project uses MIT license (2021/5/4).
We really need your support! Edit it if you want,
use this project to learn or create more interesting things
Also,i am a member of [FGPRC](https://www.fgprc.org/), (But don't forget me XD).
welcome to join us!
(2021/5/4) Now this project uses MIT license.Edit it if you want,
use this project to learn or create more interesting things(But don't forget me XD).
## Why Writing Nasal Interpreter ## Why Writing Nasal Interpreter
Nasal is a script language first used in Flightgear, Nasal is a script language first used in Flightgear,
created by Andy Ross(<https://github.com/andyross>). created by [Andy Ross](<https://github.com/andyross>).
But in 2019 summer holiday,
But in last summer holiday, members in [FGPRC](https://www.fgprc.org/) told me that it is hard to debug with nasal-console in Flightgear,
members in FGPRC told me that it is hard to debug with nasal-console in Flightgear,
especially when checking syntax errors. especially when checking syntax errors.
So i tried to write a new interpreter to help them checking syntax error and even, runtime error.
So i tried to write a new interpreter to help them checking syntax error and even,
runtime error.
I wrote the lexer, I wrote the lexer,
parser and runtimebytecode virtual machine(there was an ast-interpreter, parser and
bytecode virtual machine(there was an ast-interpreter,
but i deleted it after version4.0) to help checking errors. but i deleted it after version4.0) to help checking errors.
We found it much easier to check syntax and runtime
They found it much easier to check syntax and runtime
errors before copying nasal-codes in nasal-console in Flightgear to test. errors before copying nasal-codes in nasal-console in Flightgear to test.
Also, you could use this language to write some Also, you could use this language to write some
interesting programs and run them without the lib of Flightgear. interesting programs and run them without the lib of Flightgear.
You could add your own built-in functions to change You could add your own built-in functions to change
this interpreter to a useful tool in your own projects(such as a script in your own game). this interpreter to a useful tool in your own projects (such as a script in your own game).
## How to Compile ## How to Compile
@ -49,7 +41,7 @@ Better choose the latest update of the interpreter.
Download the source code and build it! Download the source code and build it!
It's quite easy to build this interpreter. It's quite easy to build this interpreter.
MUST USE -O2/-O3 if want to optimize the interpreter! MUST USE `-O2/-O3` if want to optimize the interpreter!
Also remember to use g++ or clang++. Also remember to use g++ or clang++.
@ -174,7 +166,7 @@ There's an example of byte code below:
for(var i=0;i<4000000;i+=1); for(var i=0;i<4000000;i+=1);
``` ```
```MIPS ```x86asm
.number 0 .number 0
.number 4e+006 .number 4e+006
.number 1 .number 1
@ -225,7 +217,7 @@ So the bytecode generator changed a lot.
for(var i=0;i<4000000;i+=1); for(var i=0;i<4000000;i+=1);
``` ```
```MIPS ```x86asm
.number 4e+006 .number 4e+006
0x00000000: intg 0x00000001 0x00000000: intg 0x00000001
0x00000001: pzero 0x00000000 0x00000001: pzero 0x00000000
@ -273,7 +265,7 @@ var f=func(x,y){return x+y;}
f(1024,2048); f(1024,2048);
``` ```
```MIPS ```x86asm
.number 1024 .number 1024
.number 2048 .number 2048
.symbol x .symbol x
@ -327,7 +319,7 @@ codegen will generate byte code by nasal_codegen::call_gen() instead of nasal_co
and the last child of the ast will be generated by nasal_codegen::mcall_gen(). and the last child of the ast will be generated by nasal_codegen::mcall_gen().
So the bytecode is totally different now: So the bytecode is totally different now:
```MIPS ```x86asm
.number 10 .number 10
.number 2 .number 2
.symbol _ .symbol _
@ -425,7 +417,7 @@ op_addc,op_subc,op_mulc,op_divc,op_lnkc,op_addeqc,op_subeqc,op_muleqc,op_diveqc,
Now the bytecode of test/bigloop.nas seems like this: Now the bytecode of test/bigloop.nas seems like this:
```MIPS ```x86asm
.number 4e+006 .number 4e+006
.number 1 .number 1
0x00000000: intg 0x00000001 0x00000000: intg 0x00000001
@ -454,7 +446,7 @@ var (a,b)=(1,2);
a=b=0; a=b=0;
``` ```
```MIPS ```x86asm
.number 2 .number 2
0x00000000: intg 0x00000002 0x00000000: intg 0x00000002
0x00000001: pone 0x00000000 0x00000001: pone 0x00000000
@ -487,7 +479,7 @@ Delete an old operand 'op_offset'.
The format of output information of bytecodes changes to this: The format of output information of bytecodes changes to this:
```MIPS ```x86asm
0x0000017c: jmp 0x181 0x0000017c: jmp 0x181
0x0000017d: calll 0x1 0x0000017d: calll 0x1
0x0000017e: calll 0x1 0x0000017e: calll 0x1
@ -588,48 +580,52 @@ running time:
|quick_sort.nas|0s|great improvement| |quick_sort.nas|0s|great improvement|
|bfs.nas|0.0156s|great improvement| |bfs.nas|0.0156s|great improvement|
## Use Nasal to Program ## Simple Tutorial
### basic value type Nasal is really easy to learn.
Reading this tutorial will not takes you over 15 minutes.
You could totally use it after reading this simple tutorial:
Nasal has 6 value types.Number,string,vector,hash,function,nil. ### __Basic Value Type__
__Number__ has 3 formats.Dec,hex and oct; `vm_none` is error type.
This type is used to interrupt the execution of virtual machine and will not be created by user program.
__String__ has 3 formats.But the third one is often used to declare a character.
__Vector__ has unlimited length and can store all types of values.
__Hash__ is a hashmap that stores values with strings/identifiers as the key.
__Function__ is also a value type in nasal.
`vm_nil` is a null type. It means nothing.
```javascript ```javascript
var spc=nil; var spc=nil;
```
var a=1; `vm_num` has 3 formats. Dec, hex and oct. Using IEEE754 double to store.
var a=2.71828; ```javascript
var a=2.147e16; var n=1;
var a=1e-10; var n=2.71828;
var a=0x7fffffff; var n=2.147e16;
var a=0xAA55; var n=1e-10;
var a=0o170001; var n=0x7fffffff;
var n=0xAA55;
var b='str'; var n=0o170001;
var b="another string"; ```
var b=`c`; `vm_str` has 3 formats. But the third one is often used to declare a character.
```javascript
var c=[]; var s='str';
var c=[ var s="another string";
var s=`c`;
```
`vm_vec` has unlimited length and can store all types of values.
```javascript
var vec=[];
var vec=[
0, 0,
nil, nil,
{}, {},
[], [],
func(){return 0;} func(){return 0;}
]; ];
append(c,0,1,2); append(vec,0,1,2);
```
var d={ `vm_hash` is a hashmap that stores values with strings/identifiers as the key.
```javascript
var hash={
member1:nil, member1:nil,
member2:'str', member2:'str',
'member3':'member\'s name can also be a string constant', 'member3':'member\'s name can also be a string constant',
@ -639,35 +635,61 @@ var d={
return a; return a;
} }
}; };
```
var f=func(x,y,z){return nil;} `vm_func` is a function type (in fact it is lambda).
var f=func{return 1024;} ```javascript
var f=func(x,y,z,default_para1=1,default_para2=2){ var f=func(x,y,z){
return x+y+z+default_para1+default_para2; return nil;
} }
var f=func(x,y,z,dynamic_para...){ var f=func{
return 1024;
}
var f=func(x,y,z,default1=1,default2=2){
return x+y+z+default1+default2;
}
var f=func(args...){
var sum=0; var sum=0;
foreach(var i;dynamic_para) foreach(var i;args)
sum+=i; sum+=i;
return sum+x+y+z; return sum;
} }
``` ```
`vm_obj` is a special type that stores user data.
This means you could use other complex C/C++ data types in nasal.
This type is used when you are trying to add a new data structure into nasal,
so this type is often created by native-function that programmed in C/C++ by library developers.
You could see how to write your own native-functions below.
```javascript
var my_new_obj=func(){
return __builtin_my_obj();
}
var obj=my_new_obj();
```
### operators ### __Operators__
Nasal has basic math operators `+` `-` `*` `/` and a special operator `~` that links two strings together.
```javascript ```javascript
1+2-1*2/1; 1+2-1*2/1;
'str1'~'str2'; 'str1'~'str2';
(1+2)*(3+4) (1+2)*(3+4)
```
For conditional expressions, operators `==` `!=` `<` `>` `<=` `>=` are used to compare two values.
`and` `or` have the same function as C/C++ `&&` `||`, link comparations together.
```javascript
1+1 and 0; 1+1 and 0;
1<0 or 1>0; 1<0 or 1>0;
1<=0 and 1>=0; 1<=0 and 1>=0;
1==0 or 1!=0; 1==0 or 1!=0;
```
Unary operators `-` `!` have the same function as C/C++.
```javascript
-1; -1;
!0; !0;
```
Operators `=` `+=` `-=` `*=` `/=` `~=` are used in assignment expressions.
```javascript
a=b=c=d=1; a=b=c=d=1;
a+=1; a+=1;
a-=1; a-=1;
@ -676,7 +698,7 @@ a/=1;
a~='string'; a~='string';
``` ```
### definition ### __Definition__
```javascript ```javascript
var a=1; var a=1;
@ -686,7 +708,7 @@ var (a,b,c)=(0,1,2);
(var a,b,c)=(0,1,2); (var a,b,c)=(0,1,2);
``` ```
### multi-assignment ### __Multi-Assignment__
```javascript ```javascript
(a,b[0],c.d)=[0,1,2]; (a,b[0],c.d)=[0,1,2];
@ -694,7 +716,10 @@ var (a,b,c)=(0,1,2);
(a,b)=(b,a); (a,b)=(b,a);
``` ```
### conditional expression ### __Conditional Expression__
In nasal there's a new key word `elsif`.
It has the same functions as `else if`.
```javascript ```javascript
if(1){ if(1){
@ -708,23 +733,30 @@ if(1){
} }
``` ```
### loop ### __Loop__
While loop and for loop is simalar to C/C++.
```javascript ```javascript
while(condition) while(condition)
continue; continue;
for(var i=0;i<10;i+=1) for(var i=0;i<10;i+=1)
break; break;
```
Nasal has another two kinds of loops that iterates through a vector:
`forindex` will get the index of a vector.
```javascript
forindex(var i;elem) forindex(var i;elem)
print(elem[i]); print(elem[i]);
```
`foreach` will get the element of a vector.
```javascript
foreach(var i;elem) foreach(var i;elem)
print(i); print(i);
``` ```
### subvec ### __Subvec__
Use index to search one element in the string will get the ascii number of this character. 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(). If you want to get the character,use built-in function chr().
@ -734,15 +766,16 @@ a[-1,1,0:2,0:,:3,:,nil:8,3:nil,nil:nil];
"hello world"[0]; "hello world"[0];
``` ```
### special function call ### __Special Function Call__
This is of great use but is not very efficient(because hashmap use string as the key to compare). This is of great use but is not very efficient
(because hashmap use string as the key to compare).
```javascript ```javascript
a(x:0,y:1,z:2); f(x:0,y:nil,z:[]);
``` ```
### lambda ### __Lambda__
Also functions have this kind of use: Also functions have this kind of use:
@ -766,60 +799,91 @@ var fib=func(f){
); );
``` ```
### closure ### __Closure__
Closure means you could get the variable that is not in the local scope of a function that you called.
Use closure to OOP. Here is an example, result is `1`:
```javascript ```javascript
var f=func(){ var f=func(){
var a=1; var a=1;
return func(){return a;}; return func(){return a;};
} }
print(f()()); print(f()());
```
var student=func(name,age){ Using closure makes it easier to OOP.
var val={ ```javascript
name:name, var student=func(n,a){
age:age var (name,age)=(n,a);
};
return { return {
print_info:func(){println(val.name,' ',val.age);}, print_info:func() {println(name,' ',age);},
set_age: func(age){val.age=age;}, set_age: func(a){age=a;},
get_age: func(){return val.age;}, get_age: func() {return age;},
set_name: func(name){val.name=name;}, set_name: func(n){name=n;},
get_name: func(){return val.name;} get_name: func() {return name;}
}; };
} }
``` ```
### native functions ### __Trait__
Must import lib.nas or has these functions' definitions inside your code. Also there's another way to OOP,that is `trait`.
Also you could add builtin functions of your own(written in C/C++) to help you calculate things more quickly.(Advanced usage) 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 is 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]
};
}
};
```
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.
```javascript
var a=class.new();
a.set(114514);
println(a.get());
```
### __Native Functions(This is for library developers)__
You could add builtin functions of your own
(written in C/C++) to help you calculate things more quickly.
(Advanced usage)
Check built-in functions in lib.nas! Check built-in functions in lib.nas!
You could use this file as the example to learn.
If you want to add your own built-in functions,define the function in nasal_builtin.h. If you want to add your own built-in functions,
define the function in nasal_builtin.h. (or any other place, but remember to compile it)
Definition: Definition:
```C++ ```C++
nasal_ref builtin_chr(std::vector<nasal_ref>&,nasal_gc&); nasal_ref builtin_chr(std::vector<nasal_ref>&,nasal_gc&);
``` ```
Then complete this function using C++: Then complete this function using C++:
```C++ ```C++
nasal_ref builtin_print(std::vector<nasal_ref>& local,nasal_gc& gc) nasal_ref builtin_print(std::vector<nasal_ref>& local,nasal_gc& gc)
{ {
// get arguments by using builtin_find
// find value with index begin from 1 // find value with index begin from 1
// because local_scope[0] is reserved for value 'me' // because local_scope[0] is reserved for value 'me'
nasal_ref vec=local[1]; nasal_ref vec=local[1];
// main process // main process
// also check number of arguments and type here // also check number of arguments and type here
// if get a type error,use builtin_err and return nullptr // if get an error,use builtin_err
for(auto i:vec.vec()->elems) for(auto i:vec.vec()->elems)
switch(i.type) switch(i.type)
{ {
@ -833,15 +897,12 @@ nasal_ref builtin_print(std::vector<nasal_ref>& local,nasal_gc& gc)
case vm_obj: std::cout<<"<object>"; break; case vm_obj: std::cout<<"<object>"; break;
} }
std::cout<<std::flush; std::cout<<std::flush;
// if a nasal value is not in use,use gc::del_reference to delete it
// generate return value,use gc::gc_alloc(type) to make a new value // generate return value,use gc::gc_alloc(type) to make a new value
// or use reserved reference gc.nil/gc.one/gc.zero // or use reserved reference gc.nil/gc.one/gc.zero
return gc.nil; return gc.nil;
} }
``` ```
After that, register the built-in function's name(in nasal) and the function's pointer in this table:
After that, write the built-in function's name(in nasal) and the function's pointer in this table:
```C++ ```C++
struct FUNC_TABLE struct FUNC_TABLE
{ {
@ -854,15 +915,15 @@ struct FUNC_TABLE
}; };
``` ```
At last,warp the '__builtin_print' in a nasal file: At last,warp the `__builtin_print` in a nasal file:
```javascript ```javascript
var print=func(elems...){ var print=func(elems...){
return __builtin_print(elems); return __builtin_print(elems);
}; };
``` ```
In fact the arguments that `__builtin_print` uses is not necessary.
In fact the arguments that '__builtin_print' uses is not necessary,So writting it like this is also right: So writting it like this is also right:
```javascript ```javascript
var print=func(elems...){ var print=func(elems...){
@ -870,12 +931,11 @@ var print=func(elems...){
}; };
``` ```
In version 5.0, If you don't warp built-in function in a normal nasal function,
if you don't warp built-in function in a normal nasal function,
this built-in function may cause a fault when searching arguments, this built-in function may cause a fault when searching arguments,
which will cause SIGSEGV segmentation error(maybe). which will segmentation error.
Use import("") to get the nasal file including your built-in functions, Use `import(".nas")` to get the nasal file including your built-in functions,
then you could use it. then you could use it.
version 6.5 update: version 6.5 update:
@ -893,8 +953,8 @@ So use builtin_alloc in builtin functions like this:
```C++ ```C++
nasal_ref builtin_keys(std::vector<nasal_ref>& local,nasal_gc& gc) nasal_ref builtin_keys(std::vector<nasal_ref>& local,nasal_gc& gc)
{ {
nasal_ref hash_addr=local[1]; nasal_ref hash=local[1];
if(hash_addr.type!=vm_hash) if(hash.type!=vm_hash)
{ {
builtin_err("keys","\"hash\" must be hash"); builtin_err("keys","\"hash\" must be hash");
return nasal_ref(vm_none); return nasal_ref(vm_none);
@ -903,11 +963,11 @@ nasal_ref builtin_keys(std::vector<nasal_ref>& local,nasal_gc& gc)
// push vector into local scope to avoid being sweeped // push vector into local scope to avoid being sweeped
local.push_back(gc.gc_alloc(vm_vec)); local.push_back(gc.gc_alloc(vm_vec));
std::vector<nasal_ref>& vec=local.back().vec()->elems; std::vector<nasal_ref>& vec=local.back().vec()->elems;
for(auto iter:hash_addr.hash()->elems) for(auto& iter:hash.hash()->elems)
{ {
nasal_ref str_addr=gc.builtin_alloc(vm_str); nasal_ref str=gc.builtin_alloc(vm_str);
*str_addr.str()=iter.first; *str.str()=iter.first;
vec.push_back(str_addr); vec.push_back(str);
} }
return local.back(); return local.back();
} }
@ -1001,26 +1061,25 @@ Function 'die' is used to throw error and crash.
```javascript ```javascript
hello hello
[vm] error: error occurred this line [vm] error: error occurred this line
[vm] error at 0x000000b0: native function error. [vm] error at 0x0000009b: native function error.
trace back: trace back:
0x000000b0: callb 0x00000021:__builtin_die (lib.nas line 85) 0x0000009b: callb 0x22 <__builtin_die> (lib.nas line 85)
0x0000017f: callfv 0x00000001 (a.nas line 19) 0x00000182: callfv 0x1 (a.nas line 6)
0x00000183: callfv 0x00000000 (a.nas line 21) 0x00000186: callfv 0x0 (a.nas line 8)
vm stack(limit 10): vm stack(limit 10):
0x0 nullptr null |
0x7fa5f8e19c80 func | func(1 para){..} func | <0x8b0f50> func{entry=0x9b}
0x7fa5f8e1a780 func | func(0 para){..} func | <0x8b1db0> func{entry=0x17c}
0x7fa5f8c0c040 num | 0.017453 num | 57.295780
0x7fa5f8e33370 hash | {9 member} num | 1852.000000
0x7fa5f8e33330 hash | {5 member} num | 1.943800
0x7fa5f8e332e0 hash | {2 member} num | 0.000540
0x7fa5f8e1a000 func | func(1 para){..} num | 39.370100
0x7fa5f8e19f80 func | func(2 para){..} num | 3.280800
0x7fa5f8e19f00 func | func(2 para){..} num | 0.453600
``` ```
Here is an example of stack overflow: Here is an example of stack overflow:
```javascript ```javascript
func(f){ func(f){
return f(f); return f(f);
@ -1032,15 +1091,30 @@ func(f){
``` ```
And the trace back info: And the trace back info:
```javascript ```javascript
[vm] stack overflow [vm] stack overflow
trace back: trace back:
0x00000011: callfv 0x00000001 (a.nas line 5) 0x0000000f: callfv 0x1 (a.nas line 5)
0x00000011: 4076 same call(s) ... 0x0000000f: 4090 same call(s) ...
0x00000008: callfv 0x00000001 (a.nas line 2) 0x00000007: callfv 0x1 (a.nas line 2)
0x00000015: callfv 0x00000001 (a.nas line 3) 0x00000013: callfv 0x1 (a.nas line 3)
vm stack(limit 10): vm stack(limit 10):
0x7fcc3110ad00 func | func(1 para){..} func | <0xc511e0> func{entry=0xd}
0x7fcc3110ad00 ... | 9 same value(s) ... | 9 same value(s)
``` ```
Error will be thrown if there's a fatal error when executing:
```javascript
func(){
return 0;
}()[1];
```
And the trace back info:
```javascript
[vm] error at 0x00000008: callv: must call a vector/hash/string
trace back:
0x00000008: callv 0x0 (a.nas line 3)
vm stack(limit 10):
num | 0.000000
```

View File

@ -1,8 +1,6 @@
import("lib.nas"); import("lib.nas");
var student=func(name,age) var student=func(n,a){
{
var (n,a)=(name,age);
return { return {
print_info:func println(n,' ',a), print_info:func println(n,' ',a),
set_age: func(age) a=age, set_age: func(age) a=age,