Merge pull request #23 from ValKmjolnir/develop: add special variable "arg"

 add special variable "arg"
This commit is contained in:
Li Haokun 2023-07-09 01:06:04 +08:00 committed by GitHub
commit e2d8c5c495
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 922 additions and 458 deletions

View File

@ -71,7 +71,9 @@ __CAUTION__: If want to use the release zip/tar.gz file to build the interpreter
Make sure your MinGW thread model is `posix thread model`, otherwise it may not have the thread library. Make sure your MinGW thread model is `posix thread model`, otherwise it may not have the thread library.
> mingw32-make nasal.exe > mkdir build
>
> mingw32-make nasal.exe -j4
### __`Windows (Visual Studio)`__ ### __`Windows (Visual Studio)`__
@ -79,16 +81,14 @@ This project gives a [__CMakelists.txt__](./CMakeLists.txt) for you to create pr
### __`Linux/macOS/Unix`__ ### __`Linux/macOS/Unix`__
> make nasal > mkdir build
>
> make -j4
You could choose which compiler you want to use: You could choose which compiler you want to use:
> make nasal CXX=... > make nasal CXX=...
If you think `-O3` isn't that safe and stable, you could choose:
> make stable-release
## __How to Use__ ## __How to Use__
![usage](./doc/gif/help.gif) ![usage](./doc/gif/help.gif)

View File

@ -19,7 +19,7 @@
* [__开发历史__](../doc/dev_zh.md) * [__开发历史__](../doc/dev_zh.md)
* [__测试数据__](../doc/benchmark.md) * [__测试数据__](../doc/benchmark.md)
* [__特殊之处__](#与andy解释器的不同之处) * [__特殊之处__](#与andy解释器的不同之处)
* [__堆栈追踪信息__](#trace-back-info) * [__堆栈追踪信息__](#堆栈追踪信息)
* [__调试器__](#调试器) * [__调试器__](#调试器)
__如果有好的意见或建议欢迎联系我们!__ __如果有好的意见或建议欢迎联系我们!__
@ -62,7 +62,9 @@ __注意__: 如果你想直接下载发行版提供的zip/tar.gz压缩包来构
一定要确保您的 MinGW thread model 是 `posix thread model`, 否则可能存在没有 thread 库的问题。 一定要确保您的 MinGW thread model 是 `posix thread model`, 否则可能存在没有 thread 库的问题。
> mingw32-make nasal.exe > mkdir build
>
> mingw32-make nasal.exe -j4
### __`Windows` 平台(`Vistual Studio`)__ ### __`Windows` 平台(`Vistual Studio`)__
@ -70,16 +72,14 @@ __注意__: 如果你想直接下载发行版提供的zip/tar.gz压缩包来构
### __`Linux/macOS/Unix` 平台__ ### __`Linux/macOS/Unix` 平台__
> make nasal > mkdir build
>
> make -j4
你也可以通过如下的其中一行命令来指定你想要使用的编译器: 你也可以通过如下的其中一行命令来指定你想要使用的编译器:
> make nasal CXX=... > make nasal CXX=...
如果你觉得`-O3`编译的版本不是那么安全和稳定,你也可以选择生成稳定的版本:
> make stable-release
## __使用方法__ ## __使用方法__
![usage](../doc/gif/help.gif) ![usage](../doc/gif/help.gif)

View File

@ -51,7 +51,7 @@
<p> <p>
The interpreter is totally rewritten by <a href="https://github.com/ValKmjolnir">ValKmjolnir</a> using C++(-std=c++11) without reusing the code in Andy Ross's nasal interpreter. The interpreter is totally rewritten by <a href="https://github.com/ValKmjolnir">ValKmjolnir</a> 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. But we really appreciate that Andy created this amazing programming language and his interpreter project.
Now this project uses <a href="/license">GPL-2.0 license</a> (2021/5/4).<br/> Now this project uses <a href="/license">GPL-2.0 license</a>.<br/>
</p> </p>
<p> <p>
这个解释器是由<a href="https://github.com/ValKmjolnir">ValKmjolnir</a>用C++11编写的完全没有复用Andy Ross版解释器的代码。但是我们仍然非常感谢Andy为我们带来了这么一款有趣的编程语言。 这个解释器是由<a href="https://github.com/ValKmjolnir">ValKmjolnir</a>用C++11编写的完全没有复用Andy Ross版解释器的代码。但是我们仍然非常感谢Andy为我们带来了这么一款有趣的编程语言。
@ -71,14 +71,21 @@
nasal运行brainfuck绘制的曼德勃罗集合(右)。 nasal运行brainfuck绘制的曼德勃罗集合(右)。
</p> </p>
<p> <p>
Nasal can run this test file(test/bfcolored.nas) to draw this picture in about 220 seconds. Nasal can run this test file(test/bf.nas) to draw this picture in about 220 seconds.
In fact this test file cost over 2200 seconds before ver 8.0 . In fact this test file cost over 2200 seconds before ver 8.0 .
</p> </p>
<p> <p>
Nasal现在可以在220秒内运行该文件(test/bfcolored.nas)并绘制出这张图。 Nasal现在可以在220秒内运行该文件(test/bf.nas)并绘制出这张图。
在8.0版本中这个解释器需要跑超过2200秒来绘制这张图。 在8.0版本中这个解释器需要跑超过2200秒来绘制这张图。
</p> </p>
<p>
The figure below is the feigenbaum-figure generated by ppm script written in nasal.
</p>
<p>
下方是使用 nasal 的 ppm 生成脚本生成的 feigenbaum 图形。
</p>
</text> </text>
<img src="/doc/pic/feigenbaum.png" width="900" height="550" style="margin-left: 15px;"><br /></img>
<h2>&nbsp;Example | 样例代码</h2> <h2>&nbsp;Example | 样例代码</h2>
<form method="get"> <form method="get">
<text style="margin-left: 15px;"> </text> <text style="margin-left: 15px;"> </text>

BIN
doc/pic/feigenbaum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

109
makefile
View File

@ -1,81 +1,84 @@
STD=c++17 STD=c++17
NASAL_OBJECT=\ NASAL_OBJECT=\
nasal_err.o\ build/nasal_err.o\
nasal_ast.o\ build/nasal_ast.o\
ast_visitor.o\ build/ast_visitor.o\
ast_dumper.o\ build/ast_dumper.o\
nasal_lexer.o\ build/nasal_lexer.o\
nasal_parse.o\ build/nasal_parse.o\
nasal_import.o\ build/nasal_import.o\
optimizer.o\ build/optimizer.o\
nasal_opcode.o\ build/nasal_opcode.o\
symbol_finder.o\ build/symbol_finder.o\
nasal_codegen.o\ build/nasal_codegen.o\
nasal_misc.o\ build/nasal_misc.o\
nasal_gc.o\ build/nasal_gc.o\
nasal_builtin.o\ build/nasal_builtin.o\
nasal_vm.o\ build/nasal_vm.o\
nasal_dbg.o\ build/nasal_dbg.o\
main.o build/main.o
# for test # for test
nasal: $(NASAL_OBJECT) nasal: $(NASAL_OBJECT) | build
$(CXX) $(NASAL_OBJECT) -O3 -o nasal -ldl $(CXX) $(NASAL_OBJECT) -O3 -o nasal -ldl
nasal.exe: $(NASAL_OBJECT) nasal.exe: $(NASAL_OBJECT) | build
$(CXX) $(NASAL_OBJECT) -O3 -o nasal.exe $(CXX) $(NASAL_OBJECT) -O3 -o nasal.exe
main.o: src/main.cpp build:
$(CXX) -std=$(STD) -c -O3 src/main.cpp -fno-exceptions -fPIC -o main.o -I . @ if [ ! -d build ]; then mkdir build; fi
nasal_misc.o: src/nasal.h src/nasal_misc.cpp build/main.o: src/nasal.h src/nasal_err.h src/nasal_lexer.h src/main.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_misc.cpp -fno-exceptions -fPIC -o nasal_misc.o -I . $(CXX) -std=$(STD) -c -O3 src/main.cpp -fno-exceptions -fPIC -o build/main.o -I .
nasal_err.o: src/nasal_err.h src/nasal_err.cpp build/nasal_misc.o: src/nasal.h src/nasal_misc.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_err.cpp -fno-exceptions -fPIC -o nasal_err.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_misc.cpp -fno-exceptions -fPIC -o build/nasal_misc.o -I .
nasal_gc.o: src/nasal_gc.h src/nasal_gc.cpp build/nasal_err.o: src/nasal_err.h src/nasal_err.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_gc.cpp -fno-exceptions -fPIC -o nasal_gc.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_err.cpp -fno-exceptions -fPIC -o build/nasal_err.o -I .
nasal_import.o: src/nasal_import.h src/nasal_import.cpp build/nasal_gc.o: src/nasal_gc.h src/nasal_gc.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_import.cpp -fno-exceptions -fPIC -o nasal_import.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_gc.cpp -fno-exceptions -fPIC -o build/nasal_gc.o -I .
nasal_lexer.o: src/nasal_lexer.h src/nasal_lexer.cpp build/nasal_import.o: src/nasal_import.h src/nasal_import.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_lexer.cpp -fno-exceptions -fPIC -o nasal_lexer.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_import.cpp -fno-exceptions -fPIC -o build/nasal_import.o -I .
nasal_ast.o: src/nasal_ast.h src/nasal_ast.cpp build/nasal_lexer.o: src/nasal_lexer.h src/nasal_lexer.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_ast.cpp -fno-exceptions -fPIC -o nasal_ast.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_lexer.cpp -fno-exceptions -fPIC -o build/nasal_lexer.o -I .
nasal_builtin.o: src/nasal_builtin.h src/nasal_builtin.cpp build/nasal_ast.o: src/nasal_ast.h src/nasal_ast.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_builtin.cpp -fno-exceptions -fPIC -o nasal_builtin.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_ast.cpp -fno-exceptions -fPIC -o build/nasal_ast.o -I .
nasal_codegen.o: src/nasal_codegen.h src/nasal_codegen.cpp build/nasal_builtin.o: src/nasal_builtin.h src/nasal_builtin.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_codegen.cpp -fno-exceptions -fPIC -o nasal_codegen.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_builtin.cpp -fno-exceptions -fPIC -o build/nasal_builtin.o -I .
nasal_opcode.o: src/nasal_opcode.h src/nasal_opcode.cpp build/nasal_codegen.o: src/nasal_codegen.h src/nasal_codegen.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_opcode.cpp -fno-exceptions -fPIC -o nasal_opcode.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_codegen.cpp -fno-exceptions -fPIC -o build/nasal_codegen.o -I .
nasal_parse.o: src/nasal_parse.h src/nasal_parse.cpp src/nasal_ast.h build/nasal_opcode.o: src/nasal_opcode.h src/nasal_opcode.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_parse.cpp -fno-exceptions -fPIC -o nasal_parse.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_opcode.cpp -fno-exceptions -fPIC -o build/nasal_opcode.o -I .
optimizer.o: src/optimizer.h src/optimizer.cpp src/nasal_ast.h build/nasal_parse.o: src/nasal_parse.h src/nasal_parse.cpp src/nasal_ast.h | build
$(CXX) -std=$(STD) -c -O3 src/optimizer.cpp -fno-exceptions -fPIC -o optimizer.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_parse.cpp -fno-exceptions -fPIC -o build/nasal_parse.o -I .
symbol_finder.o: src/symbol_finder.h src/symbol_finder.cpp src/nasal_ast.h build/optimizer.o: src/optimizer.h src/optimizer.cpp src/nasal_ast.h | build
$(CXX) -std=$(STD) -c -O3 src/symbol_finder.cpp -fno-exceptions -fPIC -o symbol_finder.o -I . $(CXX) -std=$(STD) -c -O3 src/optimizer.cpp -fno-exceptions -fPIC -o build/optimizer.o -I .
ast_visitor.o: src/nasal_ast.h src/ast_visitor.h src/ast_visitor.cpp build/symbol_finder.o: src/symbol_finder.h src/symbol_finder.cpp src/nasal_ast.h | build
$(CXX) -std=$(STD) -c -O3 src/ast_visitor.cpp -fno-exceptions -fPIC -o ast_visitor.o -I . $(CXX) -std=$(STD) -c -O3 src/symbol_finder.cpp -fno-exceptions -fPIC -o build/symbol_finder.o -I .
ast_dumper.o: src/nasal_ast.h src/ast_visitor.h src/ast_dumper.h src/ast_dumper.cpp build/ast_visitor.o: src/nasal_ast.h src/ast_visitor.h src/ast_visitor.cpp | build
$(CXX) -std=$(STD) -c -O3 src/ast_dumper.cpp -fno-exceptions -fPIC -o ast_dumper.o -I . $(CXX) -std=$(STD) -c -O3 src/ast_visitor.cpp -fno-exceptions -fPIC -o build/ast_visitor.o -I .
nasal_vm.o: src/nasal_vm.h src/nasal_vm.cpp build/ast_dumper.o: src/nasal_ast.h src/ast_visitor.h src/ast_dumper.h src/ast_dumper.cpp
$(CXX) -std=$(STD) -c -O3 src/nasal_vm.cpp -fno-exceptions -fPIC -o nasal_vm.o -I . $(CXX) -std=$(STD) -c -O3 src/ast_dumper.cpp -fno-exceptions -fPIC -o build/ast_dumper.o -I .
nasal_dbg.o: src/nasal_dbg.h src/nasal_dbg.cpp build/nasal_vm.o: src/nasal_vm.h src/nasal_vm.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_dbg.cpp -fno-exceptions -fPIC -o nasal_dbg.o -I . $(CXX) -std=$(STD) -c -O3 src/nasal_vm.cpp -fno-exceptions -fPIC -o build/nasal_vm.o -I .
build/nasal_dbg.o: src/nasal_dbg.h src/nasal_dbg.cpp | build
$(CXX) -std=$(STD) -c -O3 src/nasal_dbg.cpp -fno-exceptions -fPIC -o build/nasal_dbg.o -I .
.PHONY: clean .PHONY: clean
clean: clean:

View File

@ -4,7 +4,7 @@ dynamic_libs_so = libfib.so libkey.so libnasock.so libmat.so
dynamic_libs_dll = libfib.dll libkey.dll libnasock.dll libmat.dll dynamic_libs_dll = libfib.dll libkey.dll libnasock.dll libmat.dll
used_header = ../src/nasal.h ../src/nasal_gc.h used_header = ../src/nasal.h ../src/nasal_gc.h
used_object = ../nasal_misc.o ../nasal_gc.o used_object = ../build/nasal_misc.o ../build/nasal_gc.o
STD=c++17 STD=c++17

View File

@ -135,7 +135,7 @@ void execute(
} }
// run // run
auto start=clk::now(); auto start = clk::now();
if (cmd&VM_DEBUG) { if (cmd&VM_DEBUG) {
dbg(err).run(gen, ld, argv); dbg(err).run(gen, ld, argv);
} else if (cmd&VM_TIME || cmd&VM_EXEC) { } else if (cmd&VM_TIME || cmd&VM_EXEC) {
@ -144,7 +144,7 @@ void execute(
// get running time // get running time
if (cmd&VM_TIME) { if (cmd&VM_TIME) {
f64 tm=(clk::now()-start).count()*1.0/den; f64 tm = (clk::now()-start).count()*1.0/den;
std::clog << "process exited after " << tm << "s.\n\n"; std::clog << "process exited after " << tm << "s.\n\n";
} }
} }

View File

@ -7,7 +7,7 @@
#include <unordered_map> #include <unordered_map>
enum class expr_type:u32 { enum class expr_type:u32 {
ast_null=0, // null node ast_null = 0, // null node
ast_file_info, // stores file info ast_file_info, // stores file info
ast_block, // code block ast_block, // code block
ast_nil, // nil keyword ast_nil, // nil keyword
@ -25,9 +25,9 @@ enum class expr_type:u32 {
ast_callf, // id() ast_callf, // id()
ast_subvec, // id[index:index] ast_subvec, // id[index:index]
ast_param, // function parameter ast_param, // function parameter
ast_ternary, ast_ternary, // ternary operator
ast_binary, ast_binary, // binary operator
ast_unary, ast_unary, // unary operator
ast_for, // for keyword ast_for, // for keyword
ast_forei, // foreach or forindex loop ast_forei, // foreach or forindex loop
ast_while, // while ast_while, // while
@ -35,10 +35,10 @@ enum class expr_type:u32 {
ast_cond, // mark a sub-tree of conditional expression ast_cond, // mark a sub-tree of conditional expression
ast_if, // if keyword ast_if, // if keyword
ast_multi_id, // multi identifiers sub-tree ast_multi_id, // multi identifiers sub-tree
ast_tuple, ast_tuple, // tuple, stores multiple scalars
ast_def, // definition ast_def, // definition
ast_assign, ast_assign, // assignment
ast_multi_assign, ast_multi_assign,// multiple assignment
ast_continue, // continue keyword, only used in loop ast_continue, // continue keyword, only used in loop
ast_break, // break keyword, only used in loop ast_break, // break keyword, only used in loop
ast_ret // return keyword, only used in function block ast_ret // return keyword, only used in function block

File diff suppressed because it is too large Load Diff

View File

@ -1091,7 +1091,18 @@ const error& codegen::compile(parse& parse, linker& import) {
fileindex = 0; fileindex = 0;
file = import.filelist().data(); file = import.filelist().data();
in_iterloop.push(0); in_iterloop.push(0);
// add special symbol globals, which is a hash stores all global variables
add_symbol("globals"); add_symbol("globals");
// add special symbol arg here, which is used to store function arguments
// for example:
// var f = func(a) {print(arg)}
// f(1, 2, 3);
// then the arg is [2, 3], because 1 is accepted by "a"
// so in fact "f" is the same as:
// var f = func(a, arg...) {return(arg)}
add_symbol("arg");
find_symbol(parse.tree()); // search symbols first find_symbol(parse.tree()); // search symbols first
gen(op_intg, global.size(), 0); gen(op_intg, global.size(), 0);
block_gen(parse.tree()); // generate main block block_gen(parse.tree()); // generate main block

View File

@ -643,6 +643,12 @@ inline void vm::o_callfv() {
for(u32 i=psize;i<argc;++i) { for(u32 i=psize;i<argc;++i) {
dynamic.vec().elems.push_back(local[i]); dynamic.vec().elems.push_back(local[i]);
} }
} else if (psize<argc) {
// load arguments to "arg", located at stack+1
stack[1] = ngc.alloc(vm_vec);
for(u32 i=psize;i<argc;++i) {
stack[1].vec().elems.push_back(local[i]);
}
} }
// should reset stack top after allocating vector // should reset stack top after allocating vector
// because this may cause gc // because this may cause gc
@ -906,6 +912,9 @@ inline void vm::o_ret() {
ctx.funcr = ctx.top[0]; ctx.funcr = ctx.top[0];
ctx.top[0] = ret; // rewrite func with returned value ctx.top[0] = ret; // rewrite func with returned value
// reset "arg"
stack[1] = nil;
if (up.type==vm_upval) { // synchronize upvalue if (up.type==vm_upval) { // synchronize upvalue
auto& upval = up.upval(); auto& upval = up.upval();
auto size = func.func().lsize; auto size = func.func().lsize;

48
test/feigenbaum.nas Normal file
View File

@ -0,0 +1,48 @@
import.std.process_bar;
var ppm=func(filename, width, height, RGB){
# P3 use ASCII number
# P6 use binary character
var fd = io.open(filename, "wb");
io.write(fd, "P6\n"~width~" "~height~"\n255\n");
for(var i = 0; i<height; i+=1) {
for(var j = 0; j<width; j+=1) {
io.write(fd, RGB(i, j));
}
}
io.close(fd);
}
var width = 1600;
var height = 900;
var bar=(os.platform()=="windows")?
process_bar.bar(front:"sharp",back:"point",sep:"line",length:50):
process_bar.high_resolution_bar(50);
var RGB = func(h, w) {
var r = 2+w*2/width;
var x = (height-h)/height;
var res = 0;
var tmp = 0.5;
for(var i = 0; i<50; i+=1) {
tmp = r*tmp*(1-tmp);
}
for(var i = 0; i<100; i+=1) {
tmp = r*tmp*(1-tmp);
if (math.abs(tmp-x)<0.001) {
res = 255;
break;
}
}
var progress = (h*width+w+1)/(width*height);
if(progress*100-int(progress*100)==0){
print(bar.bar(progress), " ", progress*100, "% \r");
}
var c = char(res);
return c~c~c;
}
ppm("feigenbaum.ppm", width, height, RGB);
println();

View File

@ -12,4 +12,13 @@ globals.test_func = func() {
} }
println(); println();
println(globals.test_func); println(globals.test_func);
globals.test_func(); globals.test_func();
var f = func() {
println(arg);
}
f(1, 2, 3);
println(arg);
println(globals.arg);

View File

@ -314,7 +314,10 @@ while(1){
http.send(client,respond.ok(io.readfile("./doc/pic/favicon.ico"))); http.send(client,respond.ok(io.readfile("./doc/pic/favicon.ico")));
elsif(path=="/license") elsif(path=="/license")
http.send(client,respond.ok(io.readfile("./LICENSE"))); http.send(client,respond.ok(io.readfile("./LICENSE")));
elsif(path=="/doc/pic/nasal.png" or path=="/doc/pic/benchmark.png" or path=="/doc/pic/mandelbrot.png") elsif(path=="/doc/pic/nasal.png" or
path=="/doc/pic/benchmark.png" or
path=="/doc/pic/mandelbrot.png" or
path=="/doc/pic/feigenbaum.png")
http.send(client,respond.ok(io.readfile("."~path))); http.send(client,respond.ok(io.readfile("."~path)));
else{ else{
var filename=substr(path,1,size(path)-1); var filename=substr(path,1,size(path)-1);

378
test/sample.nas Normal file
View File

@ -0,0 +1,378 @@
# This file is written by Andy Ross, and is protected by GPLv2.0
# A no-op function used below to get this file to run. Ignore and read on...
dummyFunc = func { 1 }
#
# Literal numbers can be decimal, exponential, or hex constants. All
# numbers are stored internally as IEEE double-precision values.
#
n1 = 3;
n2 = 3.14;
n3 = 6.023e23;
n3 = 0x123456;
#
# Two identical string literals with different quotes. Double quotes
# use typical escape sequences. Single quotes treat everything as
# literal except for embedded single quotes (including embedded
# whitespace like newlines). Double quotes handle the following
# C-like escapes: \n \r \t \xnn \"
#
s1 = 'Andy\'s "computer" has a C:\righteous\newstuff directory.';
s2 = "Andy's \"computer\" has a C:\\righteous\\newstuff directory.";
#
# Literal lists use square brackets with a comma-separated expression
# list.
#
list1 = ["a", "b", 1, 2];
#
# Literal hashes (or objects -- same thing) use curlies and colons to
# separate the key/value pairs. Note that the key can be either a
# symbol or a literal scalar. Object definitions will typically want
# to use symbols, lookup tables of other types will be more
# comfortable with literals.
#
hash1 = { name : "Andy", job : "Hacker" };
EnglishEspanol = { "one" : "uno", "two": "dos", "blue" : "azul" };
#
# Both vectors and hashes use square brackets for the lookup operation:
#
list1[0] == "a";
hash1["name"] == "Andy";
#
# A simple function. By default, arguments are passed in the "arg"
# array, not unlike perl. Note that this is just an assignment of an
# (anonymous) function argument to the local "log_message" variable.
# There is no function declaration syntax in Nasal.
#
log_message = func {
print(arg[0]);
}
#
# You can also pass named arguments to a function, thus saving the
# typing and performance costs of extracting them from the arg array.
#
sqrt = dummyFunc;
dist = func(x1, y1, x2, y2) {
dx = x2-x1;
dy = y2-y1;
return sqrt(dx*dx + dy*dy);
}
dist(0,0,1,1); # == sqrt(2)
#
# These arguments can have default values, as in C++. Note that the
# default value must be a scalar (number, string, function, nil) and
# not a mutable composite object (list, hash).
#
read = func(bytes, flags=0) { }
#
# Any extra arguments after the named list are placed in the "arg"
# vector as above. You can rename this to something other than "arg"
# by specifying a final argument name with an ellipsis:
#
listify = func(elements...) { return elements; }
listify(1, 2, 3, 4); # returns a list: [1, 2, 3, 4]
#
# You can use a "var" qualifier to make sure that an assigment is made
# to a local variable, and not one from the enclosing scope. This is
# good practice in general, although it is not required. Note that
# this is not a "declaration", just a qualifier on the "=" operator.
#
innerFunc = func {
for(var dist=0; dist<100; dist += 1) {
# Does not interfere with the "dist" symbol defined above
}
}
#
# Nasal has no "statements", which means that any expression can appear
# in any context. This means that you can use an if/else clause to do
# what the ?: does in C. The last semicolon in a code block is
# optional, to make this prettier.
#
abs = func(n) { if(n<0) { -n } else { n } }
#
# But for those who don't like typing, the ternary operator works like
# you expect:
#
abs = func(n) { n < 0 ? -n : n }
#
# Nasal supports a "nil" value for use as a null pointer equivalent.
# It can be tested for equality, matching only other nils.
#
listNode = { data : ["what", "ever"], next : nil };
#
# Nasal's binary boolean operators are "and" and "or", unlike C.
# unary not is still "!" however. They short-circuit like you expect
#
toggle = 0;
a = nil;
if(a and a.field == 42) {
toggle = !toggle; # doesn't crash when a is nil
}
#
# Looping constructs are mostly C-like. The differences are that
# there is no do{}while(); construct, and there is a foreach, which
# takes a local variable name as its first argument and a vector as
# its second.
#
doSomething = dummyFunc;
stillGoing = 0;
while(stillGoing) { doSomething(); }
for(i=0; i < 3; i = i+1) {
elem = list1[i];
doSomething(elem);
}
foreach(elem; list1) { doSomething(elem) } # Shorthand for above
#
# There is also a "forindex", which is like foreach except that it
# assigns the index of each element, instead of the value, to the loop
# variable.
#
forindex(i; list1) { doSomething(list1[i]); }
#
# Define a class object with one method, one field and one "new"
# contstructor. The "new" function is not special in any way; it
# simply returns an initialized object with the "parents" field set
# appropriately. Member functions can get their local object (the
# equivalent of the "this" pointer in C++) as the "me" variable.
#
Class1 = {};
Class1.new = func {
obj = { parents : [Class1],
count : 0 };
return obj;
}
Class1.getcount = func {
me.count = me.count + 1;
return me.count;
}
c = Class1.new();
print(c.getcount(), "\n"); # prints 1
print(c.getcount(), "\n"); # prints 2
print(c.getcount(), "\n"); # prints 3
# Note that member *get* operations recurse into the parents array.
# But *set* operations always go to the local object. You can't
# corrupt a parent class via OOP operations on its instances (but you
# *can* get to it via hand-inspection of the parents arrays).
c2 = Class1.new();
c2.getcount() = func { 12345 }; # custom "derived" function!
print(c2.getcount(), "\n"); # prints 12345
print(c1.getcount(), "\n"); # prints 4, Class1.getcount is unchanged
#
# This creates an identical class using alternative syntax.
#
Class2 = {
new : func {
obj = {};
obj.parents = [Class2];
obj.count = 0;
return obj;
},
getcount : func {
me.count = me.count + 1;
return me.count;
}
};
#
# Advanced strings: Nasal strings are always arrays of bytes (never
# characters: see the utf8 library if you want character-based
# equivalents of substr() et. al.). They can be indexed just like in
# C (although note that there is no nul termination -- get the length
# with size()):
#
string = "abcdefghijklmnopqrstuvwxyz";
var ascii_sum = 0;
for(var i=0; i<size(string); i+=1) { ascii_sum += string[i]; }
#
# You can use backquotes to write UTF8 character constants
#
if(`A` != 65) { print("ASCII violation bug!\n"); }
if(`©` != 169) { print("Unicode violation bug!\n"); }
#
# And you can mutate strings by assigning to their indices, as long as
# they are not constants. You
# can make a mutable string either with the append operator or the
# bits.buf() function.
#
ascii_lc = func(string) {
var mutable = string ~ "";
for(var i=0; i<size(mutable); i+=1) {
if(mutable[i] >= `A` and mutable[i] <= `Z`) {
mutable[i] += (`a` - `A`);
}
}
return mutable;
}
print(ascii_lc("ABCDEFG"), "\n"); # prints "abcdefg"
#
# Advanced vectors: The lookup index can be negative, where -1
# indicates the last element in the vector (or string).
#
next_to_last = list1[-2];
#
# Remember that strings look syntactically like vectors of bytes; so
# conversely, the "~" concatenation operator works equally well to
# concatenate vectors:
#
joined_list = [1, 2, 3] ~ [4, 5, 6];
###
### Now some fun examples:
###
#
# Make a "inverted index" hash out of a vector that returns the index
# for each element.
#
invert = func(vec) {
hash = {};
for(i=0; i<size(vec); i = i+1) {
hash[vec[i]] = i;
}
return hash;
}
#
# Use the return value of the above function to do an "index of"
# lookup on a vector
#
vecfind = func(vec, elem) { return invert(vec)[elem]; }
#
# Joins its arguments with the empty string and returns a scalar.
# Note use of "~" operator to do string concatenation (Nasal's only
# funny syntax).
#
join = func {
s = "";
foreach(elem; arg) { s = s ~ elem; }
return s;
}
#
# Labeled break/continue syntax puts the label in as an extra first
# argument to the for/while/foreach.
#
doneWithInnerLoopEarly = dummyFunc;
completelyDone = dummyFunc;
for(OUTER; i=0; i<100; i = i+1) {
for(j=0; j<100; j = j+1) {
if(doneWithInnerLoopEarly()) {
break;
} elsif(completelyDone()) {
break OUTER;
}
}
}
##
## A rockin' metaprogramming hack. Generates and returns a deep copy
## of the object in valid Nasal syntax. A warning to those who might
## want to use this: it ignores function objects (which cannot be
## inspected from Nasal) and replaces them with nil references. It
## also makes no attempt to escape special characters in strings, which
## can break re-parsing in strange (and possibly insecure!) ways.
##
dump = func(o) {
result = "";
if(typeof(o) == "scalar") {
n = num(o);
if(n == nil) { result = result ~ '"' ~ o ~ '"'; }
else { result = result ~ o; }
} elsif(typeof(o) == "vector") {
result = result ~ "[ ";
if(size(o) > 0) { result = result ~ dump(o[0]); }
for(i=1; i<size(o); i=i+1) {
result = result ~ ", " ~ dump(o[i]);
}
result = result ~ " ]";
} elsif(typeof(o) == "hash") {
ks = keys(o);
result = result ~ "{ ";
if(size(o) > 0) {
k = ks[0];
result = result ~ k ~ ":" ~ dump(o[k]);
}
for(i=1; i<size(o); i=i+1) {
k = ks[i];
result = result ~ ", " ~ k ~ " : " ~ dump(o[k]);
}
result = result ~ " }";
} else {
result = result ~ "nil";
}
return result;
}
#
# Functional programming A: All function expressions are inherently
# anonymous lambdas and can be used and evaluated mid-expression:
#
# (Note the parens around the function expression. This is necessary
# because otherwise the parser would read a func following an
# assignment as a "code block" instead of a subexpression. This is
# the rule that allows you to omit the semicolon at the end of a
# normal function definition. Oh well, every language has a syntactic
# quirk or two...)
#
a = (func(n){ n + 1 })(232); # "a" now equals 233
#
# Functional programming B. All expressions have a value, the last
# expression in a code block is the return value of that code block.
# There are no "statements" in Nasal, although some expressions
# (assignment, duh) have side effects. e.g. The "if" expression works
# both for code flow and as the ?: expression in C/C++.
#
factorial = func(n) { if(n == 0) { 1 }
else { n * factorial(n-1) } }
print(factorial(10), "\n");
#
# Functional programming C: Lexical closures. Functions "remember"
# the context in which they were defined, and can continue to use the
# local variables in the outer scope even after their creator has
# returned.
#
getcounter = func { count = 0; return func { count = count + 1 } }
mycounter = getcounter();
print(mycounter(), "\n"); # prints 1
print(mycounter(), "\n"); # prints 2
print(mycounter(), "\n"); # prints 3
print(mycounter(), "\n"); # prints 4...
#
# Functional programming D: see the documentation on the functions
# bind(), call(), closure() and caller(). Basically, every piece of
# the symbol lookup environment is inspectable and mutable at runtime.
#