From 58ea303202f4ffdf0fbf2108061b385b1eb48bf4 Mon Sep 17 00:00:00 2001 From: ValKmjolnir Date: Thu, 14 Oct 2021 13:42:07 +0800 Subject: [PATCH] complete simple tutorial --- README.md | 364 +++++++++++++++++++++++++++++-------------------- test/class.nas | 4 +- 2 files changed, 220 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index 3c724d9..dad99c2 100644 --- a/README.md +++ b/README.md @@ -3,45 +3,37 @@ ## Introduction [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) -without reusing the code in Andy Ross's nasal interpreter(). +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](). 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) - We really need your support! - -Also,i am a member of [FGPRC](https://www.fgprc.org/), -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). +Now this project uses MIT license (2021/5/4). +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 Nasal is a script language first used in Flightgear, -created by Andy Ross(). - -But in last summer holiday, -members in FGPRC told me that it is hard to debug with nasal-console in Flightgear, +created by [Andy Ross](). +But in 2019 summer holiday, +members in [FGPRC](https://www.fgprc.org/) told me that it is hard to debug with nasal-console in Flightgear, 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, -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. - -They found it much easier to check syntax and runtime +We found it much easier to check syntax and runtime errors before copying nasal-codes in nasal-console in Flightgear to test. Also, you could use this language to write some interesting programs and run them without the lib of Flightgear. - 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 @@ -49,7 +41,7 @@ Better choose the latest update of the interpreter. Download the source code and build it! 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++. @@ -174,7 +166,7 @@ There's an example of byte code below: for(var i=0;i<4000000;i+=1); ``` -```MIPS +```x86asm .number 0 .number 4e+006 .number 1 @@ -225,7 +217,7 @@ So the bytecode generator changed a lot. for(var i=0;i<4000000;i+=1); ``` -```MIPS +```x86asm .number 4e+006 0x00000000: intg 0x00000001 0x00000001: pzero 0x00000000 @@ -273,7 +265,7 @@ var f=func(x,y){return x+y;} f(1024,2048); ``` -```MIPS +```x86asm .number 1024 .number 2048 .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(). So the bytecode is totally different now: -```MIPS +```x86asm .number 10 .number 2 .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: -```MIPS +```x86asm .number 4e+006 .number 1 0x00000000: intg 0x00000001 @@ -454,7 +446,7 @@ var (a,b)=(1,2); a=b=0; ``` -```MIPS +```x86asm .number 2 0x00000000: intg 0x00000002 0x00000001: pone 0x00000000 @@ -487,7 +479,7 @@ Delete an old operand 'op_offset'. The format of output information of bytecodes changes to this: -```MIPS +```x86asm 0x0000017c: jmp 0x181 0x0000017d: calll 0x1 0x0000017e: calll 0x1 @@ -588,48 +580,52 @@ running time: |quick_sort.nas|0s|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; - -__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_none` is error type. +This type is used to interrupt the execution of virtual machine and will not be created by user program. +`vm_nil` is a null type. It means nothing. ```javascript var spc=nil; - -var a=1; -var a=2.71828; -var a=2.147e16; -var a=1e-10; -var a=0x7fffffff; -var a=0xAA55; -var a=0o170001; - -var b='str'; -var b="another string"; -var b=`c`; - -var c=[]; -var c=[ +``` +`vm_num` has 3 formats. Dec, hex and oct. Using IEEE754 double to store. +```javascript +var n=1; +var n=2.71828; +var n=2.147e16; +var n=1e-10; +var n=0x7fffffff; +var n=0xAA55; +var n=0o170001; +``` +`vm_str` has 3 formats. But the third one is often used to declare a character. +```javascript +var s='str'; +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, nil, {}, [], func(){return 0;} ]; -append(c,0,1,2); - -var d={ +append(vec,0,1,2); +``` +`vm_hash` is a hashmap that stores values with strings/identifiers as the key. +```javascript +var hash={ member1:nil, member2:'str', 'member3':'member\'s name can also be a string constant', @@ -639,35 +635,61 @@ var d={ return a; } }; - -var f=func(x,y,z){return nil;} -var f=func{return 1024;} -var f=func(x,y,z,default_para1=1,default_para2=2){ - return x+y+z+default_para1+default_para2; +``` +`vm_func` is a function type (in fact it is lambda). +```javascript +var f=func(x,y,z){ + 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; - foreach(var i;dynamic_para) + foreach(var i;args) 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 1+2-1*2/1; 'str1'~'str2'; (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<0 or 1>0; 1<=0 and 1>=0; 1==0 or 1!=0; - +``` +Unary operators `-` `!` have the same function as C/C++. +```javascript -1; !0; - +``` +Operators `=` `+=` `-=` `*=` `/=` `~=` are used in assignment expressions. +```javascript a=b=c=d=1; a+=1; a-=1; @@ -676,7 +698,7 @@ a/=1; a~='string'; ``` -### definition +### __Definition__ ```javascript var a=1; @@ -686,7 +708,7 @@ var (a,b,c)=(0,1,2); (var a,b,c)=(0,1,2); ``` -### multi-assignment +### __Multi-Assignment__ ```javascript (a,b[0],c.d)=[0,1,2]; @@ -694,7 +716,10 @@ var (a,b,c)=(0,1,2); (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 if(1){ @@ -708,23 +733,30 @@ if(1){ } ``` -### loop +### __Loop__ +While loop and for loop is simalar to C/C++. ```javascript while(condition) continue; for(var i=0;i<10;i+=1) 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) print(elem[i]); - +``` +`foreach` will get the element of a vector. +```javascript foreach(var i;elem) print(i); ``` -### subvec +### __Subvec__ 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(). @@ -734,15 +766,16 @@ a[-1,1,0:2,0:,:3,:,nil:8,3:nil,nil:nil]; "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 -a(x:0,y:1,z:2); +f(x:0,y:nil,z:[]); ``` -### lambda +### __Lambda__ Also functions have this kind of use: @@ -766,60 +799,91 @@ var fib=func(f){ ); ``` -### closure - -Use closure to OOP. - +### __Closure__ +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`: ```javascript var f=func(){ var a=1; return func(){return a;}; } print(f()()); - -var student=func(name,age){ - var val={ - name:name, - age:age - }; +``` +Using closure makes it easier to OOP. +```javascript +var student=func(n,a){ + var (name,age)=(n,a); return { - print_info:func(){println(val.name,' ',val.age);}, - set_age: func(age){val.age=age;}, - get_age: func(){return val.age;}, - set_name: func(name){val.name=name;}, - get_name: func(){return val.name;} + 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;} }; } ``` -### 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! +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: ```C++ nasal_ref builtin_chr(std::vector&,nasal_gc&); ``` - Then complete this function using C++: - ```C++ nasal_ref builtin_print(std::vector& local,nasal_gc& gc) { - // get arguments by using builtin_find // find value with index begin from 1 // because local_scope[0] is reserved for value 'me' nasal_ref vec=local[1]; // main process // 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) switch(i.type) { @@ -833,15 +897,12 @@ nasal_ref builtin_print(std::vector& local,nasal_gc& gc) case vm_obj: std::cout<<""; break; } std::cout<& local,nasal_gc& gc) { - nasal_ref hash_addr=local[1]; - if(hash_addr.type!=vm_hash) + nasal_ref hash=local[1]; + if(hash.type!=vm_hash) { builtin_err("keys","\"hash\" must be hash"); return nasal_ref(vm_none); @@ -903,11 +963,11 @@ nasal_ref builtin_keys(std::vector& local,nasal_gc& gc) // push vector into local scope to avoid being sweeped local.push_back(gc.gc_alloc(vm_vec)); std::vector& 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); - *str_addr.str()=iter.first; - vec.push_back(str_addr); + nasal_ref str=gc.builtin_alloc(vm_str); + *str.str()=iter.first; + vec.push_back(str); } return local.back(); } @@ -1001,26 +1061,25 @@ Function 'die' is used to throw error and crash. ```javascript hello [vm] error: error occurred this line -[vm] error at 0x000000b0: native function error. +[vm] error at 0x0000009b: native function error. trace back: - 0x000000b0: callb 0x00000021:__builtin_die (lib.nas line 85) - 0x0000017f: callfv 0x00000001 (a.nas line 19) - 0x00000183: callfv 0x00000000 (a.nas line 21) + 0x0000009b: callb 0x22 <__builtin_die> (lib.nas line 85) + 0x00000182: callfv 0x1 (a.nas line 6) + 0x00000186: callfv 0x0 (a.nas line 8) vm stack(limit 10): - 0x0 nullptr - 0x7fa5f8e19c80 func | func(1 para){..} - 0x7fa5f8e1a780 func | func(0 para){..} - 0x7fa5f8c0c040 num | 0.017453 - 0x7fa5f8e33370 hash | {9 member} - 0x7fa5f8e33330 hash | {5 member} - 0x7fa5f8e332e0 hash | {2 member} - 0x7fa5f8e1a000 func | func(1 para){..} - 0x7fa5f8e19f80 func | func(2 para){..} - 0x7fa5f8e19f00 func | func(2 para){..} + null | + func | <0x8b0f50> func{entry=0x9b} + func | <0x8b1db0> func{entry=0x17c} + num | 57.295780 + num | 1852.000000 + num | 1.943800 + num | 0.000540 + num | 39.370100 + num | 3.280800 + num | 0.453600 ``` Here is an example of stack overflow: - ```javascript func(f){ return f(f); @@ -1032,15 +1091,30 @@ func(f){ ``` And the trace back info: - ```javascript [vm] stack overflow trace back: - 0x00000011: callfv 0x00000001 (a.nas line 5) - 0x00000011: 4076 same call(s) ... - 0x00000008: callfv 0x00000001 (a.nas line 2) - 0x00000015: callfv 0x00000001 (a.nas line 3) + 0x0000000f: callfv 0x1 (a.nas line 5) + 0x0000000f: 4090 same call(s) ... + 0x00000007: callfv 0x1 (a.nas line 2) + 0x00000013: callfv 0x1 (a.nas line 3) vm stack(limit 10): - 0x7fcc3110ad00 func | func(1 para){..} - 0x7fcc3110ad00 ... | 9 same value(s) + func | <0xc511e0> func{entry=0xd} + ... | 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 +``` \ No newline at end of file diff --git a/test/class.nas b/test/class.nas index affc716..9951a67 100644 --- a/test/class.nas +++ b/test/class.nas @@ -1,8 +1,6 @@ import("lib.nas"); -var student=func(name,age) -{ - var (n,a)=(name,age); +var student=func(n,a){ return { print_info:func println(n,' ',a), set_age: func(age) a=age,