📝 update doc

This commit is contained in:
ValKmjolnir 2022-09-17 18:23:27 +08:00
parent 24ae1c246f
commit 06f02ec0cb
2 changed files with 170 additions and 138 deletions

129
README.md
View File

@ -73,7 +73,7 @@ On `Windows (MinGW-w64)`:
> mingw32-make nasal.exe
You could create project in `Visual Studio` by this way: [__CLICK__](./doc/vs.md).
You could create project in `Visual Studio` by this way: [__Click__](./doc/vs.md).
On `Linux/macOS/Unix`:
@ -109,18 +109,18 @@ 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:
<details><summary> Basic value type </summary>
<details><summary> Basic type </summary>
__`vm_none`__ is error type.
This type is used to interrupt the execution of virtual machine and will not be created by user program.
__`none`__ is error type used to interrupt the execution.
This type is not created by user program.
__`vm_nil`__ is a null type. It means nothing.
__`nil`__ is a null type. Just like `null`.
```javascript
var spc=nil;
```
__`vm_num`__ has 3 formats: `dec`, `hex` and `oct`. Using IEEE754 `double` to store.
__`num`__ has 3 formats: `dec`, `hex` and `oct`. Using IEEE754 `double` to store.
```javascript
# this language use '#' to write notes
@ -131,7 +131,7 @@ var n=0xAA55; # hex
var n=0o170001; # oct
```
__`vm_str`__ has 3 formats. The third one is used to declare a character.
__`str`__ has 3 formats. The third one is used to declare a character.
```javascript
var s='str';
@ -144,7 +144,7 @@ var s=`c`;
'\"';
```
__`vm_vec`__ has unlimited length and can store all types of values.
__`vec`__ has unlimited length and can store all types of values.
```javascript
var vec=[];
@ -152,7 +152,7 @@ var vec=[0,nil,{},[],func(){return 0}];
append(vec,0,1,2);
```
__`vm_hash`__ is a hashmap(or like a dict in `python`) that stores values with strings/identifiers as the key.
__`hash`__ is a hashmap (or like a `dict` in `python`) that stores values with strings/identifiers as the key.
```javascript
var hash={
@ -165,11 +165,16 @@ var hash={
};
```
__`vm_func`__ is a function type (in fact it is lambda).
__`func`__ is a function type (in fact it is `lambda`).
```javascript
var f=func(x,y,z){return nil;}
var f=func{return 114514;}
var f=func(x,y,z){
return nil;
}
# function could be declared without parameters and `(`, `)`
var f=func{
return 114514;
}
var f=func(x,y,z,deft=1){
return x+y+z+deft;
}
@ -181,28 +186,27 @@ var f=func(args...){
}
```
__`vm_upval`__ is used to store upvalues, used in __`nasal_vm`__ to make sure closure runs correctly.
__`upval`__ is used to store upvalues, used in __`nasal_vm`__ to make sure closure runs correctly.
__`vm_obj`__ is used to store other complex C/C++ data types.
This type is often created by native-function of nasal. If want to define your own data type, see how to add native-functions by editing this project.
__`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.
</details>
<details><summary> Operators </summary>
Nasal has basic math operators `+` `-` `*` `/` and a special operator `~` that links two strings together.
Nasal has basic math operators `+` `-` `*` `/` and a special operator `~` that joints strings.
```javascript
1+2-(1+3)*(2+4)/(16-9);
'str1'~'str2';
"str1"~"str2";
```
For conditional expressions, operators `==` `!=` `<` `>` `<=` `>=` are used to compare two values.
`and` `or` have the same function as C/C++ `&&` `||`, link comparations together.
`and` `or` have the same function as C/C++ `&&` `||`.
```javascript
1+1 and 0;
1<0 or 1>0;
1+1 and (1<0 or 1>0);
1<=0 and 1>=0;
1==0 or 1!=0;
```
@ -218,20 +222,23 @@ Operators `=` `+=` `-=` `*=` `/=` `~=` are used in assignment expressions.
```javascript
a=b=c=d=1;
a+=1; a-=1; a*=1; a/=1;
a~='string';
a+=1;
a-=1;
a*=1;
a/=1;
a~="string";
```
</details>
<details><summary> Definition </summary>
As follows.
```javascript
var a=1;
var (a,b,c)=[0,1,2];
var (a,b,c)=(0,1,2);
(var a,b,c)=[0,1,2];
(var a,b,c)=(0,1,2);
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
```
</details>
@ -313,8 +320,10 @@ a[-1,1,0:2,0:,:3,:,nil:8,3:nil,nil:nil];
<details><summary> Special function call </summary>
This is of great use but is not very efficient
(because hashmap use string as the key to compare).
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...
```javascript
f(x:0,y:nil,z:[]);
@ -327,8 +336,12 @@ f(x:0,y:nil,z:[]);
Also functions have this kind of use:
```javascript
func(x,y){return x+y}(0,1);
func(x){return 1/(1+math.exp(-x));}(0.5);
func(x,y){
return x+y
}(0,1);
func(x){
return 1/(1+math.exp(-x));
}(0.5);
```
There's an interesting test file `y-combinator.nas`,
@ -463,18 +476,17 @@ If you want to use this trick to make the program running more efficiently, you
<details><summary> Native functions and module import </summary>
This part shows how we add native functions in this nasal interpreter.
This part shows how we add native functions in this interpreter.
If you are interested in this part, this may help you.
And...
__CAUTION:__ If you want to add your own functions __without__ changing the source code of the interpreter, see the __`module`__ after this part.
__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.
Definition:
```C++
nas_ref builtin_print(nas_ref*,nasal_gc&);
// you could also use a macro to define one.
nas_native(builtin_print);
```
@ -510,6 +522,27 @@ nas_ref builtin_print(nas_ref* local,nasal_gc& gc)
}
```
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++
nas_ref builtin_keys(nas_ref* local,nasal_gc& gc)
{
nas_ref hash=local[1];
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
nas_ref res=gc.temp=gc.alloc(vm_vec);
auto& vec=res.vec().elems;
for(auto& iter:hash.hash().elems)
vec.push_back(gc.newstr(iter.first));
gc.temp=nil;
return res;
}
```
After that, register the built-in function's name(in nasal) and the function's pointer in this table:
```C++
@ -542,8 +575,7 @@ var print=func(elems...){
```
If you don't warp built-in function in a normal nasal function,
this built-in function may cause a fault when searching arguments,
which will cause __segmentation error__.
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:
@ -553,27 +585,6 @@ import.dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
```
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++
nas_ref builtin_keys(nas_ref* local,nasal_gc& gc)
{
nas_ref hash=local[1];
if(hash.type!=vm_hash)
return nas_err("keys","\"hash\" must be hash");
// avoid being sweeped
nas_ref res=gc.temp=gc.alloc(vm_vec);
auto& vec=res.vec().elems;
for(auto& iter:hash.hash().elems)
vec.push_back(gc.newstr(iter.first));
gc.temp=nil;
return res;
}
```
</details>
<details><summary> Modules(for lib developers) </summary>
@ -591,7 +602,7 @@ var dylib=
dlopen: func(libname){return __dlopen;},
dlsym: func(lib,sym){return __dlsym; },
dlclose: func(lib){return __dlclose; },
dlcall: func(funcptr,args...){return __dlcall}
dlcall: func(funcptr,args...){return __dlcall;}
};
```
@ -642,7 +653,7 @@ Windows(`.dll`):
`g++ -shared -o libfib.dll fib.o`
Then we write a test nasal file to run this fib function, using `os.platform()` we could write a program that runs on three different OS:
Then we write a test nasal file to run this fib function, using `os.platform()` we could write a cross-platform program:
```javascript
var dlhandle=dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));

View File

@ -98,15 +98,15 @@ __如果你先前已经是C/C++,javascript选手那么这个教程几乎可
<details><summary>基本类型</summary>
__`vm_none`__ 是特殊的错误类型。这个类型用于终止虚拟机的执行,用户是无法申请到这个类型的,该类型只能由字节码虚拟机自己在抛出错误时产生。
__`none`__ 是特殊的错误类型。这个类型用于终止虚拟机的执行,该类型只能由虚拟机在抛出错误时产生。
__`vm_nil`__ 是空类型。类似于null。
__`nil`__ 是空类型。类似于null。
```javascript
var spc=nil;
```
__`vm_num`__ 有三种形式:十进制十六进制以及八进制。并且该类型使用IEEE754标准的浮点数`double`格式来存储。
__`num`__ 有三种形式:十进制十六进制以及八进制。并且该类型使用IEEE754标准的浮点数`double`格式来存储。
```javascript
# this language use '#' to write notes
@ -117,7 +117,7 @@ var n=0xAA55; # hex
var n=0o170001; # oct
```
__`vm_str`__ 也有三种不同的格式。第三种只允许包含一个的字符。
__`str`__ 也有三种不同的格式。第三种只允许包含一个的字符。
```javascript
var s='str';
@ -130,7 +130,7 @@ var s=`c`;
'\"';
```
__`vm_vec`__ 有不受限制的长度并且可以存储所有类型的数据。(当然不能超过可分配内存空间的长度)
__`vec`__ 有不受限制的长度并且可以存储所有类型的数据。(当然不能超过可分配内存空间的长度)
```javascript
var vec=[];
@ -138,7 +138,7 @@ var vec=[0,nil,{},[],func(){return 0}];
append(vec,0,1,2);
```
__`vm_hash`__ 使用哈希表(类似于`python`中的字典)通过键值对来存储数据。key可以是一个字符串也可以是一个标识符。
__`hash`__ 使用哈希表 (类似于`python`中的`dict`)通过键值对来存储数据。key可以是一个字符串也可以是一个标识符。
```javascript
var hash={
@ -151,11 +151,16 @@ var hash={
};
```
__`vm_func`__ 函数类型。(实际上在这个语言里函数是一种lambda表达式)
__`func`__ 函数类型。(实际上在这个语言里函数是一种`lambda`表达式)
```javascript
var f=func(x,y,z){return nil;}
var f=func{return 114514;}
var f=func(x,y,z){
return nil;
}
# 函数声明可以没有参数列表以及 `(`, `)`
var f=func{
return 114514;
}
var f=func(x,y,z,deft=1){
return x+y+z+deft;
}
@ -167,26 +172,25 @@ var f=func(args...){
}
```
__`vm_upval`__ 是存储闭包数据的特殊类型, 在 __`nasal_vm`__ 中使用,用于确保闭包功能正常。
__`upval`__ 是存储闭包数据的特殊类型, 在 __`nasal_vm`__ 中使用,确保闭包功能正常。
__`vm_obj`__ 是用来存储C/C++的一些复杂数据结构。这种类型的数据一般由内置函数或者库开发者提供的模块函数生成。如果想为nasal添加一种新的数据结构, 可以看下文如何通过修改本项目来添加自己的内置函数。
__`obj`__ 是用来存储`C/C++`的一些复杂数据结构。这种类型的数据由内置函数生成。如果想为nasal添加新的数据结构, 可以看下文如何通过修改本项目来添加内置函数。
</details>
<details><summary>运算符</summary>
Nasal拥有基本的四种数学运算符 `+` `-` `*` `/`以及一个特别的运算符 `~`这个运算符用于拼接两个字符串。
Nasal拥有基本的四种数学运算符 `+` `-` `*` `/`以及一个特别的运算符 `~`,用于拼接字符串。
```javascript
1+2-(1+3)*(2+4)/(16-9);
'str1'~'str2';
"str1"~"str2";
```
对于条件语句,可以使用`==` `!=` `<` `>` `<=` `>=`来比较两个数据。`and` `or` 有着与C/C++中 `&&` `||`运算符相同的功能,用于连接两个不同的条件语句
对于条件语句,可以使用`==` `!=` `<` `>` `<=` `>=`来比较数据。`and` `or` 与C/C++中 `&&` `||`运算符一致
```javascript
1+1 and 0;
1<0 or 1>0;
1+1 and (1<0 or 1>0);
1<=0 and 1>=0;
1==0 or 1!=0;
```
@ -202,20 +206,23 @@ Nasal拥有基本的四种数学运算符 `+` `-` `*` `/`以及一个特别的
```javascript
a=b=c=d=1;
a+=1; a-=1; a*=1; a/=1;
a~='string';
a+=1;
a-=1;
a*=1;
a/=1;
a~="string";
```
</details>
<details><summary>定义变量</summary>
如下所示。
```javascript
var a=1;
var (a,b,c)=[0,1,2];
var (a,b,c)=(0,1,2);
(var a,b,c)=[0,1,2];
(var a,b,c)=(0,1,2);
var a=1; # 定义单个变量
var (a,b,c)=[0,1,2]; # 从数组中初始化多个变量
var (a,b,c)=(0,1,2); # 从元组中初始化多个变量
```
</details>
@ -293,7 +300,9 @@ a[-1,1,0:2,0:,:3,:,nil:8,3:nil,nil:nil];
<details><summary>特殊函数调用语法</summary>
这种特别的调用方式有时非常有用,但是切记这种调用方式不是很高效,因为哈希表会使用字符串比对来找到数据存放的位置。
这种调用方式不是很高效,因为哈希表会使用字符串比对来找到数据存放的位置。
然而如果它用起来非常舒适,那效率也显得不是非常重要了……
```javascript
f(x:0,y:nil,z:[]);
@ -303,14 +312,18 @@ f(x:0,y:nil,z:[]);
<details><summary>lambda表达式</summary>
正如上文所述,函数有这样一种直接编写函数体并且直接调用的方式:
函数有这样一种直接编写函数体并且立即调用的方式:
```javascript
func(x,y){return x+y}(0,1);
func(x){return 1/(1+math.exp(-x));}(0.5);
func(x,y){
return x+y;
}(0,1);
func(x){
return 1/(1+math.exp(-x));
}(0.5);
```
测试文件中有一个非常有趣的文件`y-combinator.nas`也就是y组合子可以试一试非常有趣:
测试文件中有一个非常有趣的文件`y-combinator.nas`,可以试一试:
```javascript
var fib=func(f){
@ -329,7 +342,9 @@ var fib=func(f){
<details><summary>闭包</summary>
闭包是一种特别的作用域,你可以从这个作用域中获取其保存的所有变量,而这些变量原本不是你当前运行的函数的局部作用域中的。下面这个例子里,结果是`1`:
闭包是一种特别的作用域,你可以从这个作用域中获取其保存的所有变量,
而这些变量原本不是你当前运行的函数的局部作用域中的。
下面这个例子里,结果是`1`:
```javascript
var f=func(){
@ -360,8 +375,10 @@ var student=func(n,a){
当然,也有另外一种办法来面向对象编程,那就是利用`trait`。
当一个hash类型中有一个成员的key是`parents`并且该成员是一个数组的话那么当你试图从这个hash中寻找一个它自己没有的成员名时虚拟机会进一步搜索`parents`数组。
如果该数组中有一个hash类型有一个成员的key与当前你搜索的成员名一致那么你会得到这个成员对应的值。
当一个hash类型中有一个成员的key是`parents`,并且该成员是一个数组的话,
那么当你试图从这个hash中寻找一个它自己没有的成员名时虚拟机会进一步搜索`parents`数组。
如果该数组中有一个hash类型有一个成员的key与当前你搜索的成员名一致
那么你会得到这个成员对应的值。
使用这个机制,我们可以进行面向对象编程,下面样例的结果是`114514`:
@ -437,16 +454,18 @@ println(d());
<details><summary>原生内置函数以及模块导入(import)语法</summary>
这个部分对于纯粹的使用者来说是不需要了解的,它将告诉你我们是如何为这个解释器添加新的内置函数的。如果你对于添加自己私人订制的内置函数很感兴趣,那么这个部分可能会帮到你,并且……
这个部分对于纯粹的使用者来说是不需要了解的,
它将告诉你我们是如何为解释器添加新的内置函数的。
如果你对此很感兴趣,那么这个部分可能会帮到你,并且……
__警告:__ 如果你 __不想__ 通过直接修改解释器源码来添加你自定义的函数,那么你应该看下一个部分 __`模块`__ 的内容,而不是这个部分的内容。
__警告:__ 如果你 __不想__ 通过直接修改解释器源码来添加你自定义的函数,那么你应该看下一个__`模块`__ 的内容。
如果你确实是想修改源码来搞一个自己私人订制的解释器,那么你可以说:“我他妈就是想自己私人订制,你们他妈的管得着吗”,然后看看源码中关于内置函数的部分,以及`lib.nas`中是如何包装这些函数的,还有下面的样例:
如果你确实是想修改源码来搞一个自己私人订制的解释器,那么你可以说:“我他妈就是想自己私人订制,你们他妈的管得着吗”,
然后看看源码中关于内置函数的部分,以及`lib.nas`中是如何包装这些函数的,还有下面的样例:
定义新的内置函数:
```C++
nas_ref builtin_print(nas_ref*,nasal_gc&);
// 你可以使用这个宏来直接定义一个新的内置函数
nas_native(builtin_print);
```
@ -483,50 +502,10 @@ nas_ref builtin_print(nas_ref* local,nasal_gc& gc)
}
```
这些工作都完成之后在内置函数注册表中填写它在nasal中的别名并且在表中填对这个函数的函数指针:
```C++
struct func
{
const char* name;
nas_ref (*func)(nas_ref*,nasal_gc&);
} builtin[]=
{
{"__print",builtin_print},
{nullptr, nullptr }
};
```
最后将其包装起来扔到nasal文件中:
```javascript
var print=func(elems...){
return __print(elems);
};
```
事实上`__print`后面跟着的传参列表不是必须要写的。所以这样写也对:
```javascript
var print=func(elems...){
return __print;
};
```
一定要注意如果你不把内置函数包装到一个普通的nasal函数中那么直接调用这个内置函数会在参数传入阶段出现严重的错误这个错误会导致 __segmentation error__。也就是大家的老朋友段错误。
在nasal文件中使用`import("文件名.nas")`可以导入该文件中你包装的所有内置函数,接下来你就可以使用他们了。
当然也有另外一种办法来导入这些nasal文件下面两种导入方式的效果是一样的
```javascript
import.dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
```
当运行内置函数的时候内存分配器如果运行超过一次那么会有更大可能性多次触发垃圾收集器的mark-sweep。这个操作会在`gc::alloc`中触发。
如果先前获取的数值没有被正确存到可以被垃圾收集器索引到的地方,那么它会被错误地回收,这会导致严重的错误。
所以请使用`gc::temp`来暂时存储一个会被返回的需要gc管理的变量这样可以防止内部所有的申请错误触发垃圾回收。如下所示
可以使用`gc::temp`来暂时存储一个会被返回的需要gc管理的变量这样可以防止内部所有的申请错误触发垃圾回收。如下所示
```C++
nas_ref builtin_keys(nas_ref* local,nasal_gc& gc)
@ -544,11 +523,52 @@ nas_ref builtin_keys(nas_ref* local,nasal_gc& gc)
}
```
这些工作都完成之后在内置函数注册表中填写它在nasal中的别名并且在表中填对这个函数的函数指针:
```C++
struct func
{
const char* name;
nas_ref (*func)(nas_ref*,nasal_gc&);
} builtin[]=
{
{"__print",builtin_print},
{nullptr, nullptr }
};
```
最后将其包装到nasal文件中:
```javascript
var print=func(elems...){
return __print(elems);
};
```
事实上`__print`后面跟着的传参列表不是必须要写的。所以这样写也对:
```javascript
var print=func(elems...){
return __print;
};
```
如果你不把内置函数包装到一个普通的nasal函数中那么直接调用这个内置函数会在参数传入阶段出现 __segmentation fault(段错误)__
在nasal文件中使用`import("文件名.nas")`可以导入该文件中你包装的所有内置函数,接下来你就可以使用他们了。
当然也有另外一种办法来导入这些nasal文件下面两种导入方式的效果是一样的
```javascript
import.dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
```
</details>
<details><summary>模块(开发者教程)</summary>
如果只有上文中那种方式来添加你自定义的函数到nasal中这肯定是非常麻烦的。因此我们实现了一组实用的内置函数来帮助你添加你自己创建的模块。
如果只有上文中那种方式来添加你自定义的函数到nasal中这肯定是非常麻烦的。
因此,我们实现了一组实用的内置函数来帮助你添加你自己创建的模块。
在2021/12/3更新后我们给`lib.nas`添加了下面的这一批函数:
@ -558,7 +578,7 @@ var dylib=
dlopen: func(libname){return __dlopen;},
dlsym: func(lib,sym){return __dlsym; },
dlclose: func(lib){return __dlclose; },
dlcall: func(funcptr,args...){return __dlcall}
dlcall: func(funcptr,args...){return __dlcall;}
};
```
@ -605,7 +625,8 @@ Windows(`.dll`):
`g++ -shared -o libfib.dll fib.o`
好了那么我们可以写一个测试用的nasal代码来运行这个斐波那契函数了。下面例子中`os.platform()`是用来检测当前运行的系统环境的,这样我们可以对不同系统进行适配:
好了那么我们可以写一个测试用的nasal代码来运行这个斐波那契函数了。
下面例子中`os.platform()`是用来检测当前运行的系统环境的,这样可以实现跨平台:
```javascript
var dlhandle=dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));