369 Commits
next ... v11.2

Author SHA1 Message Date
ValK
0ffa62e5cc Merge pull request #45 from ValKmjolnir/develop
📝 add tutorial about new operators
2024-06-07 00:21:42 +08:00
ValKmjolnir
0f80dd7588 📝 rename enums 2024-06-07 00:11:54 +08:00
ValKmjolnir
b09e2a7875 📝 update tutorial about new operators 2024-06-06 23:45:27 +08:00
ValK
5aa99f396b Merge pull request #44 from ValKmjolnir/develop
 support `??` and `?.` operators & fix utf8 output on MSVC
2024-06-06 00:03:56 +08:00
ValKmjolnir
f62d747b23 🐛 really enable utf8 on MSVC 2024-06-06 00:00:48 +08:00
ValKmjolnir
ce9f28274e 🐛 fix compilation error in MSVC 2024-06-05 23:42:56 +08:00
ValKmjolnir
f83b693f65 📝 code improvement 2024-06-05 23:38:48 +08:00
ValKmjolnir
d535c8f7c4 📝 use the same function table 2024-06-05 23:12:38 +08:00
ValKmjolnir
958f669348 🐛 fix typo oprand => operand 2024-06-05 22:59:41 +08:00
ValKmjolnir
1436693cd6 add codegen for ?. 2024-06-05 01:13:39 +08:00
ValKmjolnir
4adf9541b9 add codegen for ?? operator 2024-06-05 00:01:46 +08:00
ValKmjolnir
43c229fc72 add parse process for ?? & ?. 2024-06-04 00:49:01 +08:00
ValKmjolnir
32c0b93e05 📝 rename tokname => token_name_mapper 2024-06-04 00:14:47 +08:00
ValKmjolnir
5e2268e4c5 add token ?? and ?. 2024-06-03 23:29:40 +08:00
ValKmjolnir
5a165d3255 beautiful unicode output info 2024-06-02 23:56:49 +08:00
ValK
0cf8e3bd23 Merge pull request #43 from ValKmjolnir/develop
🎨 rename & file structure change & improve ast/gc dump format
2024-06-02 22:51:28 +08:00
ValKmjolnir
0f61f8e18e 🐛 fix config error in cmake file 2024-06-02 22:47:53 +08:00
ValKmjolnir
18285c1c5a 🐛 fix link error 2024-06-02 22:39:14 +08:00
ValKmjolnir
b020ebf5b5 🐛 fix weird error on mac build 2024-06-02 22:36:41 +08:00
ValKmjolnir
627cb9d773 Merge branch 'master' into develop 2024-06-02 22:33:59 +08:00
ValKmjolnir
886703f3bd 📝 improve output format of gc dump 2024-06-02 22:32:36 +08:00
ValKmjolnir
f6d4d79c51 add windows code page manager 2024-06-02 22:08:17 +08:00
ValKmjolnir
4ac4675491 📝 delete nasal_misc.cpp 2024-06-02 19:58:50 +08:00
ValKmjolnir
05605c3570 📝 move functions from nasal_misc => util 2024-06-02 16:53:03 +08:00
ValKmjolnir
c840d70a9c 📝 add src/util 2024-06-02 16:35:48 +08:00
ValKmjolnir
764a0c6b4b 📝 move repl.* => repl/* 2024-06-02 16:10:23 +08:00
ValKmjolnir
a7dfd34120 📝 rename nasal_builtin.* 2024-06-02 16:06:18 +08:00
ValKmjolnir
2c6a0fd84d using new cli parser 2024-06-02 00:33:21 +08:00
ValKmjolnir
40a53a4224 add cli module and test/wavecity.nas 2024-06-01 18:52:22 +08:00
ValKmjolnir
3b71c5fee4 📝 change output info width from 6 -> 8 2024-06-01 00:41:05 +08:00
ValKmjolnir
b02168fc55 📝 delete system call in test files 2024-05-22 23:41:14 +08:00
ValKmjolnir
f9f2cf6d47 📝 change eol to lf 2024-05-22 20:29:13 +08:00
ValKmjolnir
6a155f56e5 📝 update doc about enabling 65001 code page 2024-05-22 20:02:37 +08:00
ValKmjolnir
971583b1c7 support utf8 ast dump on windows 2024-05-22 19:23:09 +08:00
ValKmjolnir
b6a7b7f46d 📝 rename flstream => filestream 2024-05-22 00:08:36 +08:00
ValKmjolnir
7590a286c3 more beautiful ast dump 2024-05-21 23:51:44 +08:00
ValKmjolnir
77a14699f4 📝 rename lexical tokens 2024-05-21 23:31:21 +08:00
ValKmjolnir
2c851613ce 📝 update docs 2024-05-17 00:10:24 +08:00
ValK
b32d4f8f82 Merge pull request #42 from ValKmjolnir/develop
📝 update nightly build ci
2024-05-16 19:39:54 +08:00
ValKmjolnir
ad87298737 📝 update ci 2024-05-16 19:35:23 +08:00
ValK
4f0afb31db Merge pull request #41 from ValKmjolnir/develop
📝 adjust CI & improve maintainability
2024-05-16 00:17:18 +08:00
ValKmjolnir
ba6f48c991 📝 adjust ci 2024-05-16 00:13:48 +08:00
ValKmjolnir
250667268a 📝 adjust ci 2024-05-16 00:10:10 +08:00
ValKmjolnir
2bb7ebcb58 🐛 fix syntax error in yaml 2024-05-16 00:08:09 +08:00
ValKmjolnir
7ab40e57e3 📝 try new release action 2024-05-16 00:06:45 +08:00
ValKmjolnir
939257d684 📝 try using action created by myself 2024-05-15 23:49:08 +08:00
ValKmjolnir
4550478caf 📝 test new action 2024-05-15 23:26:45 +08:00
ValKmjolnir
bdedd0301d 📝 test CI 2024-05-15 23:16:50 +08:00
ValKmjolnir
5a02a5bd99 📝 test new release 2024-05-15 23:14:42 +08:00
ValKmjolnir
1f43eae4fa 🔥 expand stack depth to 65535 2024-05-15 22:59:36 +08:00
ValKmjolnir
290ed122ba 🔥 change action/checkout to v3 2024-05-15 00:33:23 +08:00
ValKmjolnir
00a655a9fc 📝 change opcode dump format 2024-05-15 00:27:31 +08:00
ValKmjolnir
8759d12eda 🐛 delete omp.h header 2024-05-14 00:23:00 +08:00
ValKmjolnir
9b168b5d52 📝 move andy_gc_test to test dir 2024-05-14 00:16:02 +08:00
ValKmjolnir
ef09946fd6 str() can be used on complex types 2024-05-13 21:59:05 +08:00
ValKmjolnir
230848a6e1 📝 improve maintainability 2024-05-13 00:06:37 +08:00
ValKmjolnir
a11e0726bb 🔥 add check for imported files' quantity 2024-05-12 23:29:48 +08:00
ValKmjolnir
96731d180f append code size from u32 to u64 2024-05-12 22:33:51 +08:00
ValKmjolnir
8e38764df0 📝 change span data from u32 to u64 2024-05-12 19:34:05 +08:00
ValKmjolnir
eed59cfe07 📝 update README: add download links 2024-05-12 12:11:31 +08:00
ValKmjolnir
ac1960ea27 📝 adjust CI 2024-05-12 11:51:28 +08:00
ValKmjolnir
a34f90cbd1 📝 fix typo: unmutable -> immutable 2024-05-12 00:21:18 +08:00
ValK
1df1ae4a43 Merge pull request #40 from ValKmjolnir/develop
🐛 fix typo in cli help message
2024-04-12 00:08:41 +08:00
ValKmjolnir
1c011f0aad 📝 adjust size of pic in README 2024-04-11 23:45:48 +08:00
ValKmjolnir
cc6ad76eaa 🐛 fix typo in help info 2024-04-11 21:48:42 +08:00
ValKmjolnir
dbbcb134f2 📝 improve visual effect of test/langtons-ant 2024-03-28 23:21:57 +08:00
ValK
539e4c4964 Merge pull request #39 from ValKmjolnir/develop
👍 add regex lib & CI will pack executable in nightly build release
2024-03-03 23:40:59 +08:00
ValKmjolnir
b7bc36bfde 🔥 trigger CI 2024-03-03 23:37:08 +08:00
ValKmjolnir
974d413537 🐛 fix CI 2024-03-03 23:29:31 +08:00
ValKmjolnir
7cd249f049 📝 change CI config 2024-03-03 23:27:44 +08:00
ValKmjolnir
cc04720f12 🔥 change file structure & add pack.py 2024-03-03 23:12:16 +08:00
ValKmjolnir
4da8bbbd40 📝 add action badge in README 2024-03-01 23:05:04 +08:00
ValKmjolnir
22b9bce298 add regex lib 2024-03-01 22:39:43 +08:00
ValK
2e321fc4d6 Merge pull request #38 from ValKmjolnir/develop
 optimize import module & add limited execution mode (disable unsafe system api)
2024-02-21 19:59:42 +08:00
ValKmjolnir
b3e6b5784a add limited execution mode 2024-02-21 19:55:46 +08:00
ValKmjolnir
38c6fe2c5c add test file langtons-ant.nas 2024-01-16 23:02:48 +08:00
ValKmjolnir
4cceb63053 📝 optimize format in nasal_err.h 2023-12-24 16:20:45 +08:00
ValKmjolnir
1831fc4245 📝 update link in welcome info 2023-12-24 00:00:46 +08:00
ValKmjolnir
d70864fb2e 📝 add os and arch info in welcome info 2023-12-23 12:36:13 +08:00
ValKmjolnir
7ce8d3af25 optimize import module 2023-12-22 00:15:31 +08:00
ValKmjolnir
d3840edd73 avoid infinite recursion when loading module 2023-12-18 23:43:37 +08:00
ValK
a63bb6c35a Merge pull request #37 from ValKmjolnir/develop
 add json cpp lib & argparse module
2023-12-12 00:14:55 +08:00
ValKmjolnir
6e819391aa 🐛 avoid stackoverflow in json stringify 2023-12-12 00:05:38 +08:00
ValKmjolnir
c59743b2ed merge module/json.cpp into builtins 2023-12-10 23:55:18 +08:00
ValKmjolnir
49ebdc8e19 finish argparse module 2023-12-10 17:25:16 +08:00
ValKmjolnir
734aec1bc2 finish parse trigger command 2023-12-10 00:18:46 +08:00
ValKmjolnir
43ff63dc83 📝 update README 2023-12-09 00:07:44 +08:00
ValKmjolnir
ca7666c220 finish parse in argparse.nas 2023-12-08 00:07:14 +08:00
ValKmjolnir
8e0d1e18e1 🐛 fix bug in json.cpp & add std/argparse.nas 2023-12-07 00:04:41 +08:00
ValKmjolnir
a2c738d4c7 🐛 delete dlclose in module_test.nas 2023-12-06 21:17:53 +08:00
ValKmjolnir
2a36db1cf5 🐛 try fixing segfault in module_test.nas 2023-12-06 21:02:56 +08:00
ValKmjolnir
65dbe51c05 🐛 trigger CI 2023-12-06 20:53:25 +08:00
ValKmjolnir
3e226b6f73 🐛 close test for module_test 2023-12-06 20:48:28 +08:00
ValKmjolnir
fea52d9c38 🐛 trigger CI 2023-12-06 20:47:28 +08:00
ValKmjolnir
eb753c56b8 🐛 trigger CI 2023-12-06 20:38:20 +08:00
ValKmjolnir
f70ebc86b2 🐛 trigger CI 2023-12-06 20:31:01 +08:00
ValKmjolnir
8666580f14 🐛 trigger CI 2023-12-06 20:21:11 +08:00
ValKmjolnir
d56434ae28 optimize lib.nas 2023-12-06 20:10:07 +08:00
ValKmjolnir
f747d91efe optimize code 2023-12-05 23:54:26 +08:00
ValKmjolnir
3b1659e7ee add namespace fs 2023-12-03 21:14:14 +08:00
ValKmjolnir
2c03d59b6f 🐛 fix error loading bug in MSVC version 2023-12-02 21:48:20 +08:00
ValKmjolnir
d42e4a5897 📝 change CRLF to LF 2023-12-02 19:42:21 +08:00
ValKmjolnir
476fbdb859 show function entry file location
in call trace info
2023-12-01 19:31:47 +08:00
ValKmjolnir
14ec9d2a34 Merge branch 'master' into develop 2023-12-01 18:48:13 +08:00
ValK
1148ef1a08 Merge pull request #36 from sidi762/master
📝 Update contact info in README.md
2023-12-01 18:46:12 +08:00
Sidi Liang
aca8d7a011 Update contact info in README.md 2023-12-01 14:35:26 +08:00
ValKmjolnir
bd3ae8c440 optimize error handling in json module 2023-11-29 23:24:29 +08:00
ValKmjolnir
135665a5df 🐛 fix generation bug in json module 2023-11-28 23:54:58 +08:00
ValKmjolnir
f2ae974e1f change CMakeLists for json module 2023-11-28 00:19:14 +08:00
ValKmjolnir
4f44559a9d add cpp module json 2023-11-28 00:18:13 +08:00
ValKmjolnir
d55ba26c83 add cpp module json 2023-11-28 00:17:48 +08:00
ValKmjolnir
8c3c8d3d62 add ghost type timestamp 2023-11-27 20:25:02 +08:00
ValKmjolnir
2bb9655422 add recursive file searcher methods 2023-11-24 00:17:47 +08:00
ValKmjolnir
cacf0ae86a avoid compilation warnings 2023-11-23 21:35:11 +08:00
ValKmjolnir
01ceaf7e66 replace vm_type comparison with .is_xxx 2023-11-23 00:20:52 +08:00
ValKmjolnir
be84388f5b change enum vm_type to enum class 2023-11-22 22:23:15 +08:00
ValKmjolnir
c453cca0a6 add gc mark function pointer in ghost 2023-11-22 00:36:51 +08:00
ValKmjolnir
56e93e703e add test/juliaset.nas 2023-11-20 00:09:18 +08:00
ValKmjolnir
5a0d8dec20 add to_num & to_char in std.string 2023-11-18 00:12:05 +08:00
ValK
7e5c935356 Merge pull request #35 from ValKmjolnir/develop
📝 updatedocuments and scripts
2023-11-17 00:19:35 +08:00
ValKmjolnir
8582c0f221 📝 update readme 2023-11-17 00:13:17 +08:00
ValKmjolnir
28a42346b7 📝 update scripts 2023-11-16 23:19:03 +08:00
ValKmjolnir
a03739ebb2 📝 update documents and pics 2023-11-15 00:36:25 +08:00
ValKmjolnir
7d35e5bbed 📝 update readme 2023-11-08 22:23:09 +08:00
ValKmjolnir
0b66227667 add new std file phi.nas 2023-11-08 22:15:33 +08:00
ValKmjolnir
fee646965e 📝 add notes in tools/fgfs_props_getter.nas 2023-11-08 00:18:30 +08:00
ValKmjolnir
d461cbb05c add POST method to edit prop tree 2023-11-07 23:55:35 +08:00
ValKmjolnir
7b05a0a244 package getprop using socket prop getter 2023-11-07 19:13:38 +08:00
ValKmjolnir
433743f790 add tool for getting property tree from fgfs
by using --httpd in fgfs
2023-11-06 22:18:48 +08:00
ValKmjolnir
2ea9e03522 🐛 fix bug in recvfrom 2023-11-06 00:22:11 +08:00
ValK
97adfc9ea4 Merge pull request #34 from ValKmjolnir/develop
📝 code improvement
2023-11-05 21:38:34 +08:00
ValKmjolnir
e8c8a6446b use reinterpret_cast and static_cast 2023-11-05 21:25:20 +08:00
ValKmjolnir
336139dcae 🐛 fix segfault in multi-assign/multi-def codegen 2023-11-05 20:30:54 +08:00
ValKmjolnir
8a160dc7f2 use reinterpret_cast as pointer cast 2023-11-05 00:10:26 +08:00
ValKmjolnir
c946e9debd 📝 update documents 2023-11-04 00:09:59 +08:00
ValKmjolnir
2f58a7c223 📝 update documents 2023-11-02 23:01:47 +08:00
ValKmjolnir
5174551aa1 improve import 2023-11-02 00:12:45 +08:00
ValKmjolnir
49f8cefca0 avoid repeatedly importing the same modules 2023-11-01 23:49:15 +08:00
ValKmjolnir
ccbe341dc5 add import logic for use statement 2023-11-01 00:37:02 +08:00
ValKmjolnir
4757dd220a 📝 add check for use statement 2023-10-31 19:53:01 +08:00
ValKmjolnir
88e4b86ccb add new module import syntax rule 2023-10-31 00:36:49 +08:00
ValKmjolnir
c35ad2e6fa add stdin, stdout, stderr in io module 2023-10-30 23:20:49 +08:00
ValKmjolnir
4c8e1dfe91 add new test file burningship.nas 2023-10-29 00:01:14 +08:00
ValK
d6e1408edb Merge pull request #33 from ValKmjolnir/develop
🐛 fix bug in debugger and symbol_finder & code improvement
2023-10-27 00:16:01 +08:00
ValKmjolnir
ef4af8f195 🐛 fix error codegen in foreach/forindex loop
undefined symbol was recognized as defined in symbol_finder, now it is
fixed :)
2023-10-26 22:40:20 +08:00
ValKmjolnir
bbed29eb65 add native function ceil 2023-10-26 00:04:20 +08:00
ValKmjolnir
d56e1bff2c change json.JSON to json 2023-10-25 00:32:42 +08:00
ValKmjolnir
9f7484596a 🐛 fix error report bug in import.cpp 2023-10-24 00:16:48 +08:00
ValKmjolnir
f7f4a38b47 🐛 fix abnormal debugger output on windows 2023-10-23 00:02:36 +08:00
ValKmjolnir
07a652cc37 🐛 fix compilation error using CMake 2023-10-22 23:52:39 +08:00
ValKmjolnir
9629108a1e improve readability of some codes 2023-10-22 23:45:10 +08:00
ValKmjolnir
1e1ab37e83 values can get data from namespace type 2023-10-21 18:13:52 +08:00
ValKmjolnir
dfcccd4523 🐛 fix invalid debug mode problem 2023-10-21 18:00:11 +08:00
ValKmjolnir
820c05c986 change way of calling native functions 2023-10-21 00:31:39 +08:00
ValKmjolnir
7f8a0b6445 improve code & add new test file 2023-10-20 00:24:17 +08:00
ValKmjolnir
54317a39a7 improve error info of out-of-range 2023-10-18 00:29:53 +08:00
ValKmjolnir
a298aa3a63 add detail error info in callh 2023-10-17 00:44:45 +08:00
ValKmjolnir
1580b31122 add function call trace info 2023-10-15 23:49:11 +08:00
ValKmjolnir
aab7decd70 split type definition from gc.h 2023-10-15 21:46:53 +08:00
ValKmjolnir
8290b7df9f 🐛 fix mingw make error 2023-10-14 21:30:33 +08:00
ValKmjolnir
ecfb679218 improve error info when lack arguments
in function call
2023-10-14 00:39:25 +08:00
ValKmjolnir
80f9fc5842 can convert minimum double from string 2023-10-11 00:20:02 +08:00
Li Haokun
c8c233d1d4 Merge pull request #32 from ValKmjolnir/develop
📝 update release notes and tutorials
2023-10-07 22:53:52 +08:00
ValKmjolnir
0b179bf5ff 📝 update release notes and tutorials 2023-10-07 22:51:30 +08:00
Li Haokun
d108e40e22 Merge pull request #31 from ValKmjolnir/develop
📝 update docs and makefile
2023-10-07 21:49:09 +08:00
ValKmjolnir
63b97fc5ea Merge branch 'master' into develop 2023-10-07 17:35:22 +08:00
Li Haokun
d661a6b92d Merge pull request #30 from sidi762/master
Updated makefile to ensure the macOS binaries build by GitHub action are backward compatible to macOS 10.15
2023-10-07 17:13:11 +08:00
Sidi Liang
5233cef16e Update makefile 2023-10-07 14:41:46 +08:00
Sidi Liang
3874856f2b Specify mmacosx-version-min in makefile
So apparently the static linking isn't working
2023-10-07 14:34:12 +08:00
Sidi Liang
56e3e4353b Update makefile 2023-10-07 14:25:37 +08:00
Sidi Liang
e3e501ab6a Update makefile
Updated makefile to have the C++ standard libraries statically linked, so that older macOSs can still execute the program
2023-10-07 13:45:44 +08:00
ValKmjolnir
d14b816fa0 📝 add notes 2023-10-07 00:55:20 +08:00
ValKmjolnir
63b0112b9d 📝 update README 2023-10-04 11:52:09 +08:00
Li Haokun
982c5750ef Merge pull request #29 from ValKmjolnir/develop
 improve codegen and repl output
2023-10-02 00:52:05 +08:00
ValKmjolnir
d69dd0b03f change intg to repl output operand 2023-10-02 00:46:12 +08:00
ValKmjolnir
7a10392bea 📝 improve note and code in codegen 2023-09-27 00:26:13 +08:00
ValKmjolnir
5e2bb411e5 optimize codegen 2023-09-24 00:41:29 +08:00
ValKmjolnir
f8ecd2ae53 🔥 change codegen::gen to codegen::emit 2023-09-23 01:08:45 +08:00
ValKmjolnir
0a404694f6 🐛 fix memory leak in ast destructor 2023-09-20 21:11:13 +08:00
ValKmjolnir
6c8b4c6a87 use incremental gc const initialization 2023-09-20 00:46:23 +08:00
ValKmjolnir
85bc699905 add command history in repl 2023-09-19 00:24:45 +08:00
Li Haokun
38920382ea Merge pull request #28 from ValKmjolnir/develop
 finish simple repl interpreter (experimental)
2023-09-18 00:50:54 +08:00
ValKmjolnir
d0d9ce3b80 🔥 delete tools/repl.nas 2023-09-17 22:49:06 +08:00
ValKmjolnir
3e01239722 finish basic function of repl 2023-09-17 17:56:59 +08:00
ValKmjolnir
b77d7fafb1 using c++ cast 2023-09-15 00:04:19 +08:00
ValKmjolnir
36a2aa67ef using c++ cast (wip) 2023-09-14 00:34:31 +08:00
ValKmjolnir
c157d8a9b1 🎨 add namespace 2023-09-08 18:17:07 +08:00
Li Haokun
0c99eb15f0 Merge pull request #27 from ValKmjolnir/develop
🎨 bug fix & add experimental REPL & optimize framework of vm
2023-09-08 00:39:14 +08:00
ValKmjolnir
cd4e0c1716 🎨 add namespace 2023-09-08 00:33:57 +08:00
ValKmjolnir
d89f290a8a fix out of bound bug & delete fatal in lexer 2023-09-07 23:14:17 +08:00
ValKmjolnir
298b54c9ec 🐛 fix out of bound bug 2023-09-06 19:29:59 +08:00
ValKmjolnir
ec03f0aee0 repl does not write temp file now 2023-09-06 00:12:01 +08:00
ValKmjolnir
a0e6047296 update test/datalog.nas 2023-08-31 00:32:01 +08:00
ValKmjolnir
a801669888 add experimental REPL (cpp) 2023-08-27 17:46:10 +08:00
ValKmjolnir
ae5c76ecd5 maybe do not need o_intg anymore 2023-08-27 00:25:49 +08:00
ValKmjolnir
93528babdb optimize framework of vm 2023-08-24 23:24:04 +08:00
ValKmjolnir
ba629e57d7 optimize lexer 2023-08-23 00:59:35 +08:00
ValKmjolnir
00449c2bfb optimize code 2023-08-21 21:35:34 +08:00
ValKmjolnir
939b4c4a65 Merge branch 'master' into develop 2023-08-20 22:37:41 +08:00
ValKmjolnir
6f259dc1ea fix old ambiguous syntax 2023-08-20 22:35:24 +08:00
ValKmjolnir
98b0086656 🐛 fix segfault in var x = (var a,b) = (1,2) 2023-08-20 16:48:53 +08:00
ValKmjolnir
88b039a82e 📝 add experimental repl (for fun) 2023-08-19 21:36:12 +08:00
ValKmjolnir
fc2be49621 add check for symbol definition 2023-08-13 22:12:07 +08:00
ValKmjolnir
7d81246695 📝 optimize code format 2023-08-13 00:31:11 +08:00
Li Haokun
afaef55c68 Merge pull request #26 from ValKmjolnir/develop
 simpler ghost type check mechanism & split lib
2023-08-12 11:54:13 +08:00
ValKmjolnir
e131c8da4b split dylib and unix lib 2023-08-11 00:48:57 +08:00
ValKmjolnir
527cb5277b optimize vm 2023-08-10 00:40:00 +08:00
ValKmjolnir
d690c779b8 split io lib 2023-08-09 00:31:23 +08:00
ValKmjolnir
52e6adfe28 🔥 delete ghost type table 2023-08-08 20:50:47 +08:00
Li Haokun
18d59dfa94 Merge pull request #25 from ValKmjolnir/develop
 support `globals` `arg` and module imports
2023-08-07 19:06:30 +08:00
ValKmjolnir
044ccb4bc6 add profiling tools in debug mode 2023-08-06 20:02:12 +08:00
ValKmjolnir
70a8be8e4c symbol begins with '_' will not be exported 2023-08-05 00:08:28 +08:00
ValKmjolnir
56a26b6ab6 update std/math 2023-08-03 22:55:59 +08:00
ValKmjolnir
5cc9824a62 add new functions in bits & math lib 2023-08-02 19:22:45 +08:00
ValKmjolnir
56762d719d split bits lib 2023-08-01 00:36:25 +08:00
ValKmjolnir
29b15b89f7 split coroutine lib from builtin 2023-07-31 00:25:36 +08:00
ValKmjolnir
654a37ab88 split math lib into new file 2023-07-30 00:19:38 +08:00
ValKmjolnir
227197996d native functions can be loaded from differen tables 2023-07-29 23:45:44 +08:00
ValKmjolnir
fd7472b737 📝 add bits.nas & dylib.nas & unix.nas 2023-07-25 00:22:11 +08:00
ValKmjolnir
26a62dde9b 🐛 fix 2 bugs.
1. unary optimized number not generated bug
2. self-referenced module path not displayed correctly
2023-07-24 19:50:30 +08:00
ValKmjolnir
788e0026c2 optimize location info gen in codegen
and refactor lib.nas, add multiple native modules
2023-07-23 23:57:25 +08:00
ValKmjolnir
51a0ef6b0c 📝 add logprint & change libs 2023-07-23 18:30:32 +08:00
ValKmjolnir
31113ff69f 📝 delete std/sort.nas & update test file 2023-07-23 00:30:14 +08:00
ValKmjolnir
fe65085a7a add check for self-imported module 2023-07-21 23:40:59 +08:00
ValKmjolnir
569d423772 📝 update 2023-07-21 00:33:04 +08:00
ValKmjolnir
25e202fbaf add check for generated module name 2023-07-20 19:56:27 +08:00
ValKmjolnir
d479f13a5c support nasal module 2023-07-20 00:54:57 +08:00
ValKmjolnir
d9cf9ff49e update 2023-07-19 00:43:00 +08:00
ValKmjolnir
4b597000ba optimize error report info 2023-07-17 00:38:33 +08:00
ValKmjolnir
fe3847d69c add experimental namespace table 2023-07-16 21:31:51 +08:00
ValKmjolnir
eee30b7d8e add concurrent mark for experiment 2023-07-16 00:37:11 +08:00
ValKmjolnir
0c183b5984 optimize gc 2023-07-13 23:59:41 +08:00
ValKmjolnir
48d1d8ddef 🐛 fix compilation error in vs 2022 2023-07-12 00:16:27 +08:00
Li Haokun
ea33a5ed81 Merge pull request #24 from ValKmjolnir/develop
🐛 fix bug of argument "arg"
2023-07-11 19:13:42 +08:00
ValKmjolnir
4a7a2ce11e 🐛 fix bug in special argument "arg"
if defining a parameter named "arg", this will cause
error value of the last local variable of this function
2023-07-11 19:09:33 +08:00
ValKmjolnir
94b6e84693 📝 optimize code 2023-07-11 00:05:20 +08:00
ValKmjolnir
eab84d5c0d optimize gc 2023-07-10 20:34:21 +08:00
ValKmjolnir
bce8148f60 update 2023-07-10 00:11:46 +08:00
ValKmjolnir
32e23741eb add concat for vector 2023-07-09 16:45:10 +08:00
ValKmjolnir
7e72661332 🐛 complete function of arg in all scopes 2023-07-09 16:21:09 +08:00
Li Haokun
e2d8c5c495 Merge pull request #23 from ValKmjolnir/develop: add special variable "arg"
 add special variable "arg"
2023-07-09 01:06:04 +08:00
ValKmjolnir
3509655424 add special variable "arg" 2023-07-09 00:59:17 +08:00
ValKmjolnir
fd614a132c 📝 add new test file feigenbaum.nas 2023-07-07 19:55:29 +08:00
Li Haokun
bff0d78d59 🧑‍💻 Merge pull request #22 from ValKmjolnir/develop: add important symbol "globals"
🧑‍💻 add important symbol "globals"
2023-07-06 22:14:37 +08:00
ValKmjolnir
b4482792c8 🧑‍💻 add important symbol "globals" 2023-07-06 22:10:07 +08:00
Li Haokun
b122afd1cd Merge pull request #21 from ValKmjolnir/develop
👾 Update docs & optimize codes
2023-07-05 00:29:30 +08:00
ValKmjolnir
eca6141408 optimize codegen 2023-07-05 00:24:34 +08:00
ValKmjolnir
5c714d26aa update 2023-07-04 00:33:57 +08:00
Li Haokun
8ca7ee1d90 Merge pull request #20 from ValKmjolnir/develop
🎇 refactor ast structure & build process
2023-07-02 22:16:09 +08:00
ValKmjolnir
27bc544bbe 📝 rename files 2023-07-02 22:02:31 +08:00
ValKmjolnir
45f87925ae 📝 change license to GPLv2 2023-07-02 21:37:14 +08:00
ValKmjolnir
da43ec5193 📝 update docks 2023-07-02 20:39:28 +08:00
ValKmjolnir
917b4f6568 🐛 fix compile error in visual studio 2023-07-02 20:29:41 +08:00
ValKmjolnir
ac2744e24f change build scripts 2023-07-02 19:48:36 +08:00
ValKmjolnir
ba6b7cd05c change dir stl -> std 2023-07-02 16:17:56 +08:00
ValKmjolnir
a7a2f47d1e 🚀 switch build script to new nasal 2023-07-02 00:32:13 +08:00
ValKmjolnir
597c0388cb 🚀 update 2023-07-01 23:21:52 +08:00
ValKmjolnir
af3ade5a41 🐛 fix codegen bug in condition & mcall 2023-07-01 18:41:55 +08:00
ValKmjolnir
b6886cc957 finish new codegen 2023-07-01 16:38:37 +08:00
ValKmjolnir
90ad1a53d7 update 2023-07-01 00:55:00 +08:00
ValKmjolnir
8e4e4bfe89 update 2023-06-30 00:25:58 +08:00
ValKmjolnir
5d9267d3b9 add symbol_finder & codegen 2023-06-29 00:30:50 +08:00
ValKmjolnir
15f63210b4 add codegen/opcode/optimizer 2023-06-28 00:34:54 +08:00
ValKmjolnir
e2ec8cb9a0 🚀 add function ghosttype 2023-06-27 22:54:13 +08:00
ValKmjolnir
c516c0c3bf add new import & use c++17 2023-06-26 23:59:09 +08:00
ValKmjolnir
89f96d407b 🐛 fix bug 2023-06-26 19:35:56 +08:00
ValKmjolnir
8a10f07ce7 change parameter::name to std::string 2023-06-26 19:35:21 +08:00
ValKmjolnir
fb3d9ed2f3 update 2023-06-25 22:47:36 +08:00
ValKmjolnir
fe40bd7e3f finish ast dumper 2023-06-25 22:25:13 +08:00
ValKmjolnir
9f66960244 add ast dumper 2023-06-25 00:31:11 +08:00
ValKmjolnir
2ce13e2134 change ast used in parser 2023-06-24 16:50:43 +08:00
ValKmjolnir
0132ca0870 update 2023-06-24 00:30:50 +08:00
ValKmjolnir
ac49c0d676 update 2023-06-21 00:03:46 +08:00
ValKmjolnir
c5d6a6694b update new parser 2023-06-20 00:01:50 +08:00
ValKmjolnir
bcf274aed4 update 2023-06-19 00:10:14 +08:00
ValKmjolnir
78b7bec786 update 2023-06-18 00:14:21 +08:00
ValKmjolnir
7d8f199c8e update 2023-06-17 00:15:07 +08:00
ValKmjolnir
c95810b46c add nasal_new_lexer 2023-06-16 22:23:46 +08:00
ValKmjolnir
ec1985b3cc update 2023-06-14 00:01:15 +08:00
ValKmjolnir
da22dd03c0 add nasal_new_parse 2023-06-12 00:03:18 +08:00
ValKmjolnir
19480f7f26 add new ast nodes 2023-06-07 00:10:00 +08:00
ValKmjolnir
9bf4eacc62 prepare to use new ast 2023-06-05 00:11:37 +08:00
ValKmjolnir
afc8c54487 gc::extend uses std::nothrow 2023-05-18 18:45:56 +08:00
Li Haokun
36f6dd6c96 Merge pull request #19 from ValKmjolnir/develop
 add ghost_type_table for type registration & add cmake for VS to build
2023-05-11 20:59:49 +08:00
ValKmjolnir
63ec070d15 📝 update doc for vs build 2023-05-11 20:12:59 +08:00
ValKmjolnir
7f258dd17e vs could build with cmakelists.txt 2023-05-11 19:58:25 +08:00
ValKmjolnir
dfb0c6ab52 🐛 fix header in module for vs to build 2023-05-11 19:41:39 +08:00
ValKmjolnir
d954a3fc5e optimize ghost table structure 2023-05-11 19:26:34 +08:00
ValKmjolnir
7cc9ef436e improve function of ghost_register_table 2023-05-11 00:24:35 +08:00
ValKmjolnir
774ad60c42 add global_ghost_type_table for test 2023-05-10 00:48:04 +08:00
ValKmjolnir
a69d05233b prepare for ghost type 2023-05-09 00:09:15 +08:00
ValKmjolnir
666fb8b25f 🐛 fix codegen skip or/and expr bug 2023-05-08 19:23:50 +08:00
ValKmjolnir
92abad3384 add cmakelists.txt 2023-05-06 21:12:40 +08:00
ValKmjolnir
f914678311 add readline & os.arch
delete test file mandel.nas

change io.fin => io.readfile
2023-04-28 23:11:17 +08:00
ValKmjolnir
c858afbb76 🐛 fix compilation error
in windows mingw-w64 win32 thread model,
but we do not suggest you to use this,
please use posix thread model
2023-04-23 00:02:39 +08:00
ValKmjolnir
94f7c941f6 🐛 fix compilation error in VS 2023-04-12 19:44:13 +08:00
ValKmjolnir
cd21540710 🎨 improve code style 2023-04-12 01:11:31 +08:00
ValKmjolnir
a37ce6aadd 🐛 fix compilation warnings on macOS 2023-04-11 00:28:12 +08:00
ValKmjolnir
db47ab4445 🎨 improve gc code structure 2023-04-10 00:24:50 +08:00
ValKmjolnir
5519dc7a29 merge runtime regs into struct context 2023-04-08 00:18:58 +08:00
ValKmjolnir
3852ce23a3 split opcode to nasal_opcode.h 2023-04-04 00:29:17 +08:00
ValKmjolnir
4e17de909a update test/jsonrpc.nas 2023-03-25 21:26:49 +08:00
ValKmjolnir
73450092f2 add test/jsonrpc.nas and fix bug
bug: in nasocket.cpp, recv and recvfrom do not place 0 after reciving
data into the buffer.
2023-03-23 00:09:51 +08:00
ValKmjolnir
f8bac92548 📝 update test file datalog.nas 2023-03-14 00:16:54 +08:00
ValKmjolnir
7cdc5e40af visual update 2023-03-11 18:15:27 +08:00
ValKmjolnir
bd12fe917a add new operand calculate const and pop 2023-03-10 22:27:06 +08:00
ValKmjolnir
8161f3c514 optimize codegen 2023-03-09 23:00:25 +08:00
ValKmjolnir
dbc2c365b4 update error report for parser&codegen 2023-03-08 23:53:02 +08:00
ValKmjolnir
1678567c5d add multi-line error report and span 2023-03-08 01:12:01 +08:00
ValKmjolnir
abe2464a67 📝 shorten the running time of test process 2023-03-02 00:36:48 +08:00
ValKmjolnir
f9cbd0b426 add error report info in stl/mat 2023-03-02 00:26:52 +08:00
ValKmjolnir
99434df819 🚀 add more matrix operation in stl/mat.nas
and bp example test
2023-03-01 23:37:13 +08:00
ValKmjolnir
cbee5f8705 add std lib mat.nas 2023-03-01 00:41:34 +08:00
ValKmjolnir
e11793d340 🚀 add operator ^= &= |=
fix bug of parsing expressions beginning with floater
2023-02-28 00:30:27 +08:00
ValKmjolnir
461e5ac647 🔥 remove i32 bitwise native functions
now we use bitwise operators instead
2023-02-17 00:42:57 +08:00
ValKmjolnir
c8eb1f1d16 🚀 add bitwise operators or, xor, and 2023-02-16 00:24:32 +08:00
ValKmjolnir
068184f451 🐛 fix codegen for binary not on some ast nodes 2023-02-12 15:33:38 +08:00
ValKmjolnir
368b057a91 🚀 add binary negation 2023-02-12 15:22:05 +08:00
Li Haokun
f4deca4427 Merge pull request #18 from sidi762/master
Update CI to include tests
2023-02-12 00:28:31 +08:00
Sidi Liang
681f04b0d3 Update CI to include tests 2023-02-11 13:10:59 +08:00
ValKmjolnir
79c1e27027 🐛 fix bug in test/calc.nas 2023-02-03 19:01:00 +08:00
ValKmjolnir
84f9280cb4 🚀 add test file auto-push.nas 2023-02-02 21:28:11 +08:00
ValKmjolnir
e454b315fc 🚀 add file::find_all_files_with_extension 2023-02-02 21:24:21 +08:00
ValKmjolnir
1259b43389 🐛 fix bug that cannot parse return true/false 2023-02-02 19:35:42 +08:00
ValKmjolnir
acb2e3664c 🐛 fix bugs in stl/json, now it runs correctly 2023-01-31 19:39:22 +08:00
ValKmjolnir
fdd7794a83 📝 update readme 2023-01-31 19:01:36 +08:00
ValKmjolnir
22b1841664 update test/ascii-art.nas 2023-01-30 19:13:08 +08:00
ValKmjolnir
ac1ee4ddef change sprintf to snprintf in logtime() 2023-01-24 23:10:01 +08:00
ValKmjolnir
aef7936292 optimize test files
httptest.nas: when clicking mandel.nas link, this may cause memory overflow in new gc
2023-01-23 23:14:34 +08:00
ValKmjolnir
d03273b4e1 add keyword support 'true' and 'false'
true is now number 1.0

false is number 0.0
2023-01-16 17:44:56 +08:00
ValKmjolnir
8df02d25ad add detail location info in tokens
prepare for multi-line error reporter module
2023-01-10 22:22:27 +08:00
ValKmjolnir
268a03864e use new way of constructing var objects 2023-01-05 22:42:17 +08:00
ValKmjolnir
48611c50f7 🚀 coroutine.resume can pass arguments 2023-01-02 18:53:58 +08:00
ValKmjolnir
e6e89039b8 optimize code & add comments 2023-01-02 01:00:16 +08:00
ValKmjolnir
9d7f799ad3 update test file 'life.nas' 2022-12-27 18:25:58 +08:00
ValKmjolnir
dc6f3347b7 🎨 change clean command in makefile 2022-12-25 17:59:14 +08:00
ValKmjolnir
d5996d6a1d 🎨 change clean command in makefiles 2022-12-25 17:58:05 +08:00
ValKmjolnir
0d1069a724 optimize test file 2022-12-10 01:24:46 +08:00
ValKmjolnir
175706126b 🐛 fix dynamic arguments load bug in nasal_vm 2022-12-09 00:56:53 +08:00
ValKmjolnir
cb547499ac optimize gc 2022-12-07 01:17:40 +08:00
ValKmjolnir
de524938cf add runtime.gc.extend 2022-12-04 20:28:51 +08:00
ValKmjolnir
9c6996b54e update test files 2022-12-03 01:24:56 +08:00
ValKmjolnir
eca978e2b3 little optimization 2022-11-30 01:11:26 +08:00
ValKmjolnir
9455a83df0 optimize lexer 2022-11-28 21:16:39 +08:00
ValKmjolnir
be318abb2e change enum tok to enum class tok 2022-11-27 23:47:58 +08:00
ValKmjolnir
7e91c273f3 🎨 add header pic 2022-11-26 23:36:12 +08:00
ValKmjolnir
4c5ffb0240 🎨 change code format 2022-11-26 22:49:22 +08:00
ValKmjolnir
54969681fc move lvalue check from parse to codegen 2022-11-20 17:06:13 +08:00
ValKmjolnir
9196d7815f 🎨 improve error report 2022-11-19 22:47:19 +08:00
ValKmjolnir
309a57070c 🚀 change module loading 2022-11-15 21:23:34 +08:00
ValKmjolnir
7e4faed4a6 🚀 add new native function char 2022-11-12 17:03:56 +08:00
ValKmjolnir
f9179188a5 optimize test/console3D.nas 2022-11-11 00:37:14 +08:00
ValKmjolnir
97b3cefe75 add new ways of calling dylib function 2022-11-11 00:11:01 +08:00
ValKmjolnir
ca527ec931 🚀 add new module matrix.cpp (in dev) 2022-11-06 23:30:18 +08:00
ValKmjolnir
4fd69c6ce4 🚀 optimize code 2022-11-06 18:20:10 +08:00
ValKmjolnir
1ced201cb5 🚀 add gc time info 2022-11-04 21:36:54 +08:00
ValKmjolnir
cedb5e12f5 🚀 add test file console3D.nas 2022-11-04 01:18:38 +08:00
ValKmjolnir
aaccfbda11 🐛 fix ast print bug & delete some macros 2022-10-30 18:40:03 +08:00
ValKmjolnir
0c216e5f16 🐛 safer builtin_logtime 2022-10-30 01:30:05 +08:00
ValKmjolnir
feea901e4d 🎨 improve code format
delete option -v, --version, -l, --lex
2022-10-30 01:26:26 +08:00
Li Haokun
b0f080070a Merge pull request #17 from sidi762/master
[Review needed] Added linux nightly build
2022-10-28 23:39:37 +08:00
Sidi Liang
8aec98a2b3 Update c-cpp.yml 2022-10-24 01:32:05 +08:00
Sidi Liang
2c36f722bb Merge branch 'ValKmjolnir:master' into master 2022-10-24 01:29:46 +08:00
Sidi Liang
74b925715f Unable to place two builds under one releases, thus dividing them 2022-10-07 01:30:17 +08:00
Sidi Liang
b0c5dfada7 I'm stupid 2022-10-07 01:23:15 +08:00
Sidi Liang
8ae5cc0071 fix tar failing on linux (again) 2022-10-07 01:21:40 +08:00
Sidi Liang
8b1eebb310 Fix tar failing on linux 2022-10-07 01:20:14 +08:00
Sidi Liang
568d821186 fix file name 2022-10-07 01:14:06 +08:00
Sidi Liang
5b70449aee Added linux nightly build 2022-10-07 01:13:24 +08:00
219 changed files with 25423 additions and 17750 deletions

View File

@@ -1,39 +1,69 @@
name: C/C++ CI
name: Build/Test/Package CI
on:
schedule:
- cron: "0 16 * * *"
push:
branches: [ master ]
branches: [ master, develop ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
mac-aarch64-build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: make
- uses: actions/checkout@v4
- name: Update Tag
run: |
make
cd module
make all
cd ..
tar -czf nasal-mac-nightly.tgz .
- name: Release file
# You may pin to the exact commit or the version.
# uses: djnicholson/release-action@e9a535b3eced09c460e07a84118fb74ae9b53236
uses: marvinpinto/action-automatic-releases@v1.2.1
git fetch --tags origin
git tag -f next_macOS
git push -f origin next_macOS
- name: Build
run: |
make -j4
cd module
make all -j4
cd ..
- name: Test
run: make test
- name: Package
run: python3 tools/pack.py
- name: Release
uses: softprops/action-gh-release@v2.0.5
with:
# GitHub auth token
repo_token: ${{ secrets.GITHUB_TOKEN }}
# Name of Release to add file to
title: macOS Nightly build
# Name of the tag for the release (will be associated with current branch)
automatic_release_tag: next
# File to release
files: nasal-mac-nightly.tgz
name: macOS nightly build
tag_name: next_macOS
prerelease: true
draft: false
files: |
nasal-Darwin.tar
linux-x86_64-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Update Tag
run: |
git fetch --tags origin
git tag -f next_linux_x86_64
git push -f origin next_linux_x86_64
- name: Build
run: |
make -j4
cd module
make all -j4
cd ..
- name: Test
run: make test
- name: Package
run: python3 tools/pack.py
- name: Release
uses: softprops/action-gh-release@v2.0.5
with:
name: linux nightly build
tag_name: next_linux_x86_64
prerelease: true
draft: false
files: |
nasal-Linux.tar

7
.gitignore vendored
View File

@@ -38,6 +38,7 @@
*.vcxproj.user
.vs
x64
CMakePresents.json
# nasal executable
nasal
@@ -46,6 +47,12 @@ nasal.exe
# misc
.vscode
dump
fgfs.log
.temp.*
# build dir
build
out
# macOS special cache directory
.DS_Store

99
CMakeLists.txt Normal file
View File

@@ -0,0 +1,99 @@
cmake_minimum_required(VERSION 3.10)
project(nasal VERSION 11.2)
message("CMAKE_HOST_SYSTEM_NAME: ${CMAKE_HOST_SYSTEM_NAME}")
# -std=c++17 -Wshadow -Wall
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_RELEASE_INIT "-Wshadow -Wall")
add_compile_options(-fPIC)
# MSVC needs this command option to really enable utf-8 output
if(MSVC)
add_compile_options(/utf-8)
endif()
# generate release executables
set(CMAKE_BUILD_TYPE "Release")
# build nasal used object
set(NASAL_OBJECT_SOURCE_FILE
${CMAKE_SOURCE_DIR}/src/cli/cli.cpp
${CMAKE_SOURCE_DIR}/src/natives/builtin.cpp
${CMAKE_SOURCE_DIR}/src/natives/coroutine.cpp
${CMAKE_SOURCE_DIR}/src/natives/fg_props.cpp
${CMAKE_SOURCE_DIR}/src/natives/bits_lib.cpp
${CMAKE_SOURCE_DIR}/src/natives/io_lib.cpp
${CMAKE_SOURCE_DIR}/src/natives/json_lib.cpp
${CMAKE_SOURCE_DIR}/src/natives/math_lib.cpp
${CMAKE_SOURCE_DIR}/src/natives/dylib_lib.cpp
${CMAKE_SOURCE_DIR}/src/natives/regex_lib.cpp
${CMAKE_SOURCE_DIR}/src/natives/unix_lib.cpp
${CMAKE_SOURCE_DIR}/src/repl/repl.cpp
${CMAKE_SOURCE_DIR}/src/util/fs.cpp
${CMAKE_SOURCE_DIR}/src/util/util.cpp
${CMAKE_SOURCE_DIR}/src/ast_dumper.cpp
${CMAKE_SOURCE_DIR}/src/ast_visitor.cpp
${CMAKE_SOURCE_DIR}/src/nasal_ast.cpp
${CMAKE_SOURCE_DIR}/src/nasal_codegen.cpp
${CMAKE_SOURCE_DIR}/src/nasal_dbg.cpp
${CMAKE_SOURCE_DIR}/src/nasal_err.cpp
${CMAKE_SOURCE_DIR}/src/nasal_gc.cpp
${CMAKE_SOURCE_DIR}/src/nasal_import.cpp
${CMAKE_SOURCE_DIR}/src/nasal_lexer.cpp
${CMAKE_SOURCE_DIR}/src/nasal_opcode.cpp
${CMAKE_SOURCE_DIR}/src/nasal_parse.cpp
${CMAKE_SOURCE_DIR}/src/nasal_type.cpp
${CMAKE_SOURCE_DIR}/src/nasal_vm.cpp
${CMAKE_SOURCE_DIR}/src/optimizer.cpp
${CMAKE_SOURCE_DIR}/src/symbol_finder.cpp)
add_library(nasal-object STATIC ${NASAL_OBJECT_SOURCE_FILE})
target_include_directories(nasal-object PRIVATE ${CMAKE_SOURCE_DIR}/src)
# build nasal
add_executable(nasal ${CMAKE_SOURCE_DIR}/src/main.cpp)
target_link_libraries(nasal nasal-object)
# link ldl and lpthread
if(NOT CMAKE_HOST_SYSTEM_NAME MATCHES "Windows")
target_link_libraries(nasal dl)
target_link_libraries(nasal pthread)
endif()
target_include_directories(nasal PRIVATE ${CMAKE_SOURCE_DIR}/src)
# copy nasal from build dir to the outside dir
if(NOT CMAKE_HOST_SYSTEM_NAME MATCHES "Windows")
add_custom_command(
TARGET nasal POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/build/nasal
${CMAKE_SOURCE_DIR}/nasal
)
endif()
# build module
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/module)
set(MODULE_USED_OBJECT_SOURCE_FILE
${CMAKE_SOURCE_DIR}/src/util/util.cpp
${CMAKE_SOURCE_DIR}/src/util/fs.cpp
${CMAKE_SOURCE_DIR}/src/nasal_type.cpp
${CMAKE_SOURCE_DIR}/src/nasal_gc.cpp)
add_library(module-used-object STATIC ${MODULE_USED_OBJECT_SOURCE_FILE})
target_include_directories(module-used-object PRIVATE ${CMAKE_SOURCE_DIR}/src)
add_library(fib SHARED ${CMAKE_SOURCE_DIR}/module/fib.cpp)
target_include_directories(fib PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(fib module-used-object)
add_library(key SHARED ${CMAKE_SOURCE_DIR}/module/keyboard.cpp)
target_include_directories(key PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(key module-used-object)
add_library(mat SHARED ${CMAKE_SOURCE_DIR}/module/matrix.cpp)
target_include_directories(mat PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(mat module-used-object)
add_library(nasock SHARED ${CMAKE_SOURCE_DIR}/module/nasocket.cpp)
target_include_directories(nasock PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(nasock module-used-object)

352
LICENSE
View File

@@ -1,21 +1,339 @@
MIT License
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (c) 2021 ValKmjolnir
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

1001
README.md

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -110,3 +110,13 @@ And we use this bf interpreter to draw a mandelbrot set.
In 2022/2/17 update we added `\e` into the lexer. And the `bfcolored.nas` uses this special ASCII code. Here is the result:
![mandelbrot](../doc/pic/mandelbrot.png)
## More Nasal Generated Pictures
|Mandelbrot Set|Mandelbrot Set|Julia Set|
|:----:|:----:|:----:|
|[mandelbrotset.nas](../test/mandelbrotset.nas)|[mandelbrotset.nas](../test/mandelbrotset.nas)|[juliaset.nas](../test/juliaset.nas)|
|![mandelbrotset](../doc/pic/mandelbrotset.png)|![mandelbrotset_reverse](../doc/pic/mandelbrotset_reverse.png)|![juliaset](../doc/pic/juliaset.png)|
|__Burning Ship__|__Burning Ship__|__Feigenbaum__|
|[burningship.nas](../test/burningship.nas)|[burningship.nas](../test/burningship.nas)|[feigenbaum.nas](../test/feigenbaum.nas)|
|![burningship](../doc/pic/burningship.png)|![burningship_reverse](../doc/pic/burningship_reverse.png)|![feigenbaum](../doc/pic/feigenbaum.png)|

View File

@@ -1,5 +1,7 @@
# __Development History__
![buringship](./pic/burningship.png)
## __Contents__
* [__Parser__](#parser)
@@ -9,6 +11,7 @@
* [v2.0](#version-20-ast-last-update-2020831)
* [v3.0](#version-30-ast-last-update-20201023)
* [v5.0](#version-50-ast-last-update-202137)
* [v11.0](#version-110-ast-latest)
* [__Bytecode VM__](#bytecode-virtual-machine)
* [v4.0](#version-40-vm-last-update-20201217)
* [v5.0](#version-50-vm-last-update-202137)
@@ -17,8 +20,11 @@
* [v7.0](#version-70-vm-last-update-2021108)
* [v8.0](#version-80-vm-last-update-2022212)
* [v9.0](#version-90-vm-last-update-2022518)
* [v10.0](#version-100-vm-latest)
* [v10.0](#version-100-vm-last-update-2022816)
* [__Release Notes__](#release-notes)
* [v8.0](#version-80-release)
* [v11.0](#version-110-release)
* [v11.1](#version-111-release)
## __Parser__
@@ -97,6 +103,10 @@ AST interpreter leaves me too much things to do.
If i continue saving this interpreter,
it will be harder for me to make the bytecode vm become more efficient.
### version 11.0 ast (latest)
Change ast framework. Now we use visitor pattern.
## __Bytecode Virtual Machine__
![op](../doc/gif/opcode.gif)
@@ -513,7 +523,7 @@ func <0x2a3>:
0x000002aa: 0c 00 00 00 6a happ 0x6a ("dlsym")
```
### version 10.0 vm (latest)
### version 10.0 vm (last update 2022/8/16)
2022/5/19 update:
@@ -587,7 +597,7 @@ Then we call `resume`, this function will change stack.
As we can see, coroutine stack already has some values on it,
but if we first enter it, the stack top will be `vm_ret`, and the return `pc` is `0`.
So for safe running, `resume` will return `gc.top[0]`.
So for safe running, when first calling the coroutine, `resume` will return `gc.top[0]`.
`op_callb` will do `top[0]=resume()`, so the value does not change.
```C++
@@ -664,7 +674,37 @@ If do not change this line, only the debugger runs abnormally. this bug is fixed
Another bug is that in `nasal_err.h:class nasal_err`, we should add a constructor for this class:
```C++
nasal_err():error(0){}
nasal_err(): error(0) {}
```
This bug is fixed in `v9.0`. So we suggest that do not use `v8.0`.
### __version 11.0 release__
1. Use C++ `std=c++17`.
2. Change framework of ast, using visitor pattern.
3. New ast structure dump info format.
4. Change the way of module export, split library into different modules. Symbols begin with `_` will not be exported.
5. Change `stl` to `std`.
6. Add REPL interpreter.
7. Improve structure of virtual machine, split global symbol stack(stores global symbols' values) and value stack(using in process).
8. Delete operand `op_intg`, add operand `op_repl`.
9. Add `CMakeLists.txt` for cmake user(including `Visual Studio`).
10. New ghost type register process.
### __version 11.1 release__
1. Bug fix: debugger in v11.0 is malfunctional.
2. Bug fix: symbol_finder does not check definition in foreach/forindex loop.
3. Change extension syntax `import.xx.xx` to `use xx.xx`.

View File

@@ -1,4 +1,6 @@
# __开发历史记录__
# __开发日志__
![buringship](./pic/burningship.png)
## __目录__
@@ -9,6 +11,7 @@
* [v2.0](#version-20-ast-last-update-2020831)
* [v3.0](#version-30-ast-last-update-20201023)
* [v5.0](#version-50-ast-last-update-202137)
* [v11.0](#version-110-ast-latest)
* [__字节码虚拟机__](#字节码虚拟机)
* [v4.0](#version-40-vm-last-update-20201217)
* [v5.0](#version-50-vm-last-update-202137)
@@ -17,8 +20,11 @@
* [v7.0](#version-70-vm-last-update-2021108)
* [v8.0](#version-80-vm-last-update-2022212)
* [v9.0](#version-90-vm-last-update-2022518)
* [v10.0](#version-100-vm-latest)
* [v10.0](#version-100-vm-last-update-2022816)
* [__发行日志__](#发行日志)
* [v8.0](#version-80-release)
* [v11.0](#version-110-release)
* [v11.1](#version-111-release)
## __语法分析__
@@ -89,6 +95,10 @@ __该项目于2019/7/25正式开始__。
我改变想法了,树解释器给维护带来了太大的麻烦。如果想继续保留这个解释器,那么为了兼容性,字节码虚拟机的优化工作会更难推进。
### version 11.0 ast (latest)
改变了语法树的设计模式,采用访问者模式。
## __字节码虚拟机__
![op](../doc/gif/opcode.gif)
@@ -458,7 +468,7 @@ func <0x2a3>:
0x000002aa: 0c 00 00 00 6a happ 0x6a ("dlsym")
```
### version 10.0 vm (latest)
### version 10.0 vm (last update 2022/8/16)
2022/5/19 update:
@@ -526,7 +536,7 @@ __接下来我们解释这个协程的运行原理:__
接着我们调用`resume`,这个函数会替换操作数栈。我们会看到,协程的操作数栈上已经保存了一些数据,但是我们首次进入协程执行时,这个操作数栈的栈顶将会是`vm_ret`,并且返回的`pc`值是`0`。
为了保证栈顶的数据不会被破坏,`resume`会返回`gc.top[0]`。`op_callb`将会执行`top[0]=resume()`,所以栈顶的数据虽然被覆盖了一次,但是实际上还是原来的数据。
首次调用时,为了保证栈顶的数据不会被破坏,`resume`会返回`gc.top[0]`。`op_callb`将会执行`top[0]=resume()`,所以栈顶的数据虽然被覆盖了一次,但是实际上还是原来的数据。
```C++
+----------------------+(协程操作数栈)
@@ -597,7 +607,37 @@ in __`nasal_dbg.h:215`__: `auto canary=gc.stack+STACK_MAX_DEPTH-1;`
另外一个bug在 `nasal_err.h:class nasal_err`这边,要给这个类添加一个构造函数来进行初始化,否则会出问题:
```C++
nasal_err():error(0){}
nasal_err(): error(0) {}
```
同样这个也在`v9.0`中修复了。所以我们建议不要使用`v8.0`。
### __version 11.0 release__
1. 使用C++标准 `std=c++17`。
2. 改变语法树设计模式,采用访问者模式。
3. 全新的语法树结构输出格式。
4. 改变了导出模块的方式,把主要的库分成了多个模块。以`_`开头的变量不会被导出。
5. 文件夹`stl`更名为`std`。
6. 添加交互式解释器 (REPL)。
7. 优化虚拟机结构, 将全局数据栈 (存储全局变量的数据) 和操作数据栈 (用于运算) 分离。
8. 删除`op_intg`指令,添加`op_repl`指令。
9. 添加`CMakeLists.txt` (可在`Visual Studio`中使用)。
10. 全新的自定义类型注册流程。
### __version 11.1 release__
1. Bug 修复: 修复 v11.0 的 debugger 无法启动的问题。
2. Bug 修复: symbol_finder 不检查 foreach/forindex 中的迭代变量声明的问题。
3. 扩展语法 `import.xx.xx` 改为 `use xx.xx`。

93
doc/namespace.md Normal file
View File

@@ -0,0 +1,93 @@
# Nasal Namespace
![feigenbaum](./pic/feigenbaum.png)
## Introduction
In this nasal interpreter,
we use this way below to construct namespaces:
- library is linked directly with the script
- module is wraped by a function generated by linker, and return a hash
## Library
Library file is linked with script file directly, like this:
In `std/lib.nas`:
```nasal
var a = 1;
```
In `example.nas`:
```nasal
var b = 1;
```
At the link stage,
in fact we put the ast of two files together to make a new ast,
so the result is equal to:
```nasal
var a = 1;
var b = 1;
```
## Module
Modules is wraped up by a function,
and return a hash, for example:
In `std/example_module.nas`:
```nasal
var a = 1;
var _a = 1;
```
We analysed this file and generated the ast.
Then we find all the global symbols.
At last we use the information of all the globals symbols in this file to generate a hash to return.
So the result is equal to:
```nasal
var example_module = func {
# source code begin
var a = 1;
var _a = 1;
# source code end
return {
a: a
# _a begins with underscore so do not export
};
}();
```
## Import a Module
Here is a module named `std/example_module.nas`:
```nasal
var a = 1;
```
Then there's a script file named `test.nas`, import module in this file using this way:
```nasal
use std.example_module;
println(example_module.a); # 1
```
Or this way:
```nasal
import("std/example_module.nas");
println(example_module.a); # 1
```

View File

@@ -33,11 +33,12 @@
</head>
<body>
<h1>&nbsp;Nasal | Not another scripting language!</h1>
<img src="/doc/pic/social.png" width="900" height="400" style="margin-left: 15px;"><br /></img>
<div class="badges">
<a href="https://github.com/ValKmjolnir/Nasal-Interpreter"><img src="https://img.shields.io/github/languages/code-size/ValKmjolnir/Nasal-Interpreter?style=flat-square&logo=github"></img></a>
<a href="https://github.com/ValKmjolnir/Nasal-Interpreter"><img src="https://img.shields.io/github/v/release/ValKmjolnir/Nasal-Interpreter?style=flat-square&logo=github"></img></a>
<a href="https://github.com/ValKmjolnir/Nasal-Interpreter"><img src="https://img.shields.io/badge/dev-v10.0-blue?style=flat-square&logo=github"></img></a>
<a href="https://github.com/ValKmjolnir/Nasal-Interpreter"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square&logo=github"><br/></img></a>
<a href="https://github.com/ValKmjolnir/Nasal-Interpreter"><img src="https://img.shields.io/badge/dev-v11.2-blue?style=flat-square&logo=github"></img></a>
<a href="https://github.com/ValKmjolnir/Nasal-Interpreter"><img src="https://img.shields.io/badge/license-GPLv2-green?style=flat-square&logo=github"><br/></img></a>
</div>
<h2>&nbsp;Introduction | 介绍</h2>
<text>
@@ -51,12 +52,11 @@
<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.
But we really appreciate that Andy created this amazing programming language and his interpreter project.
Now this project uses <a href="/license">MIT license</a> (2021/5/4). Edit it if you want, use this project to learn or create more interesting things(But don't forget me XD).<br/>
Now this project uses <a href="/license">GPL-2.0 license</a>.<br/>
</p>
<p>
这个解释器是由<a href="https://github.com/ValKmjolnir">ValKmjolnir</a>用C++11编写的完全没有复用Andy Ross版解释器的代码。但是我们仍然非常感谢Andy为我们带来了这么一款有趣的编程语言。
现在这个项目使用<a href="/license">MIT协议</a>开源 (2021/5/4)
如果你有需求,可以自行修改,用这个解释器项目来学习或者创造更有意思的东西吧!(不过别忘了在项目协议中留下原作者名字哦)
现在这个项目使用<a href="/license">GPL-2.0协议</a>开源。
</p>
</text>
<h2>&nbsp;Benchmark | 执行效率</h2>
@@ -72,14 +72,22 @@
nasal运行brainfuck绘制的曼德勃罗集合(右)。
</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 .
</p>
<p>
Nasal现在可以在220秒内运行该文件(test/bfcolored.nas)并绘制出这张图。
Nasal现在可以在220秒内运行该文件(test/bf.nas)并绘制出这张图。
在8.0版本中这个解释器需要跑超过2200秒来绘制这张图。
</p>
<p>
The figure below is the feigenbaum-figure and burningship-figure generated by ppm script written in nasal.
</p>
<p>
下方是使用 nasal 的 ppm 生成脚本生成的 feigenbaum 和 burningship 图形。
</p>
</text>
<img src="/doc/pic/feigenbaum.png" width="900" height="550" style="margin-left: 15px;"><br /></img>
<img src="/doc/pic/burningship.png" width="900" height="550" style="margin-left: 15px;"><br /></img>
<h2>&nbsp;Example | 样例代码</h2>
<form method="get">
<text style="margin-left: 15px;"> </text>
@@ -120,10 +128,10 @@
<li><a href="/lexer.nas">lexer.nas</a></li>
<li><a href="/life.nas">life.nas</a></li>
<li><a href="/loop.nas">loop.nas</a></li>
<li><a href="/mandel.nas">mandel.nas</a></li>
<li><a href="/mandelbrot.nas">mandelbrot.nas</a></li>
<li><a href="/mandelbrotset.nas">mandelbrotset.nas</a></li>
<li><a href="/mcpu.nas">mcpu.nas</a></li>
<li><a href="/md5.nas">md5.nas</a></li>
<li><a href="/md5_self.nas">md5_self.nas</a></li>
<li><a href="/md5compare.nas">md5compare.nas</a></li>
</ul>
</td>

View File

@@ -10,6 +10,7 @@ nil ::= nil;
id ::= identifier;
number::= number;
string::= string;
bool ::= true | false;
vector::=
'[' {calculation ','} ']'
;
@@ -40,8 +41,17 @@ exprs::=
;
calculation::=
calculation '?' calculation ':' calculation
|or_expr
|calculation ('=' | '+=' | '-=' | '*=' | '/=' | '~=') calculation
|bitwise_or
|calculation ('=' | '+=' | '-=' | '*=' | '/=' | '~=' | '^=' | '&=' | '|=') calculation
;
bitwise_or::=
bitwise_xor '|' bitwise_xor
;
bitwise_xor::=
bitwise_and '^' bitwise_and
;
bitwise_and::=
or_expr '&' or_expr
;
or_expr::=
and_expr or and_expr
@@ -59,7 +69,7 @@ multive_expr::=
(unary|scalar) ('*' | '/') (unary|scalar)
;
unary::=
('-'|'!') (unary|scalar)
('-'|'!'|'~') (unary|scalar)
;
scalar::=
function {call_scalar}
@@ -68,6 +78,7 @@ scalar::=
|number
|string
|nil
|bool
|'(' calculation ')' {call_scalar}
;
call_scalar::=

BIN
doc/pic/burningship.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
doc/pic/feigenbaum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
doc/pic/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

BIN
doc/pic/juliaset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
doc/pic/mandelbrotset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

BIN
doc/pic/social.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

766
doc/tutorial.md Normal file
View File

@@ -0,0 +1,766 @@
# __Tutorial__
![mandelbrotset](../doc/pic/mandelbrotset.png)
Nasal is __easy__ to learn.
After reading this tutorial about 15 minutes,
You could totally use nasal.
## __Contents__
* [__Basic Type__](#basic-type)
* [__Operators__](#operators)
* [__Definition__](#definition)
* [__Multi-Assignment__](#multi-assignment)
* [__Conditional Expression__](#conditional-expression)
* [__Loop__](#loop)
* [__Subvec__](#subvec)
* [__Special function call__](#special-function-call)
* [__Lambda__](#lambda)
* [__Closure__](#closure)
* [__Trait__](#trait)
* [__Multi-Files/Modules Import__](#multi-filesmodules-import)
* [__Native Functions and Module Import__](#native-functions-and-module-import)
* [__C++ Modules (for lib developers)__](#c-modules-for-lib-developers)
* [__Ghost Type (for lib developers)__](#ghost-type-for-lib-developers)
## Basic Type
__`none`__ is error type used to interrupt the execution.
This type is not created by user program.
__`nil`__ is a null type. Just like `null`.
```javascript
var spc = nil;
```
__`num`__ has 3 formats: `dec`, `hex` and `oct`. Using IEEE754 `double` to store.
```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
```
__`str`__ has 3 formats. The third one is used to declare a character.
```javascript
var s = 'str';
var s = "another string";
var s = `c`;
# some special characters is allowed in this language:
'\a'; '\b'; '\e'; '\f';
'\n'; '\r'; '\t'; '\v';
'\0'; '\\'; '\?'; '\'';
'\"';
```
__`vec`__ has unlimited length and can store all types of values.
```javascript
var vec = [];
var vec = [0, nil, {}, [], func(){return 0}];
append(vec, 0, 1, 2);
```
__`hash`__ is a hashmap (or like a `dict` in `python`) 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",
funct: func() {
return me.member2~me.member3;
}
};
```
__`func`__ is a function type (in fact it is `lambda`).
```javascript
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;
}
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.
__`ghost`__ 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.
## Operators
Nasal has basic math operators `+` `-` `*` `/` and a special operator `~` that joints strings.
```javascript
1+2-(1+3)*(2+4)/(16-9);
"str1"~"str2";
```
For conditional expressions, operators `==` `!=` `<` `>` `<=` `>=` are used to compare two values.
`and` `or` have the same function as C/C++ `&&` `||`.
```javascript
1+1 and (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;
```
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.
```javascript
a = b = c = d = 1;
a += 1;
a -= 1;
a *= 1;
a /= 1;
a ~= "string";
a ^= 0xff;
a &= 0xca;
a |= 0xba;
```
Operator `??` is used to check left hand side value is `nil` or not, if not,
return the right hand side value:
```javascript
print(nil??"should get this string");
```
The example above will print `should get this string`.
Operator `?.` is used to get hash member if left hand side value is not `nil`.
And if left hand side value is not `nil` and not `hash`,
will cause error and exit.
```javascript
var a = nil;
print(a?.try_get); # nil
var a = {try_get: "congrats!"};
print(a?.try_get); # "congrats!"
```
## Definition
As follows.
```javascript
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
```
Nasal has many special global symbols:
```javascript
globals; # hashmap including all global symbols and their values
arg; # in global scope, arg is the command line arguments
# in local scope, arg is the dynamic arguments of this function call
```
For example:
```javascript
var a = 1;
println(globals); # will print {a:1}
```
```javascript
# nasal a b c
println(arg); # will print ["a", "b", "c"]
func() {
println(arg);
}(1, 2, 3); # will print [1, 2, 3]
```
## Multi-assignment
The last one is often used to swap two variables.
```javascript
(a, b[0], c.d) = [0, 1, 2];
(a, b[1], c.e) = (0, 1, 2);
(a, b) = (b, a);
```
## Conditional expression
In nasal there's a new key word `elsif`.
It has the same functions as `else if`.
```javascript
if (1) {
;
} elsif (2) {
;
} else if (3) {
;
} else {
;
}
```
## 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. Index will be `0` to `size(elem)-1`.
```javascript
forindex(var i; elem) {
print(elem[i]);
}
```
`foreach` will get the element of a vector. Element will be `elem[0]` to `elem[size(elem)-1]`.
```javascript
foreach(var i; elem) {
print(i);
}
```
## Subvec
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.
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()`.
```javascript
a[0];
a[-1, 1, 0:2, 0:, :3, :, nil:8, 3:nil, nil:nil];
"hello world"[0];
```
## Special function call
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:[]);
```
## Lambda
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);
```
There's an interesting test file `y-combinator.nas`,
try it for fun:
```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);
}
}
);
```
## 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()());
```
Using closure makes it easier to OOP.
```javascript
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;}
};
}
```
## Trait
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());
```
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:
```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();
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());
```
You will get this result now:
```bash
114
514
514
514
514
514
```
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.
## Multi-Files/Modules Import</summary>
See more details in [namespace.md](./namespace.md)
## Native functions and module import
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, 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++
// you could also use a macro to define one.
var builtin_print(context*, gc*);
```
Then complete this function using C++:
```C++
var builtin_print(context* ctx, gc* ngc) {
// find value with index begin from 1
// because local[0] is reserved for value 'me'
for(auto& i : ctx->localr[1].vec().elems) {
std::cout << i;
}
std::cout << std::flush;
// generate return value,
// use ngc::alloc(type) to make a new value
// or use reserved reference nil/one/zero
return nil;
}
```
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++
var builtin_keys(context* ctx, gc* ngc) {
auto hash = ctx->localr[1];
if (hash.type!=vm_hash && hash.type!=vm_map) {
return nas_err("keys", "\"hash\" must be hash");
}
// use gc.temp to store the gc-managed-value, to avoid being sweeped
auto res = ngc->temp = ngc->alloc(vm_vec);
auto& vec = res.vec().elems;
if (hash.type==vm_hash) {
for(const auto& iter : hash.hash().elems) {
vec.push_back(ngc->newstr(iter.first));
}
} else {
for(const auto& iter : hash.map().mapper) {
vec.push_back(ngc->newstr(iter.first));
}
}
ngc->temp = nil;
return res;
}
```
After that, register the built-in function's name(in nasal) and the function's pointer in this table:
```C++
nasal_builtin_table builtin[] = {
{"__print", builtin_print},
{nullptr, nullptr}
};
```
At last,warp the `__print` in a nasal file:
```javascript
var print = func(elems...) {
return __print(elems);
};
```
In fact the arguments that `__print` uses are not necessary.
So writting it like this is also right:
```javascript
var print = func(elems...) {
return __print;
};
```
If you don't warp built-in function in a normal nasal function,
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
use dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
```
## C++ Modules (for lib developers)
If there is only one way to add your own functions into nasal,
that is really inconvenient.
Luckily, we have developed some useful native-functions to help you add modules that created by you.
Functions used to load dynamic libraries are added to `std/dylib.nas`:
```javascript
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.
Let's see how they work.
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`):
```C++
// add header file nasal.h to get api
#include "nasal.h"
double fibonaci(double x) {
if (x<=2) {
return x;
}
return fibonaci(x-1)+fibonaci(x-2);
}
// module functions' parameter list example
var fib(var* args, usize size, gc* ngc) {
if (!size) {
return nas_err("fib", "lack arguments");
}
// the arguments are generated into a vm_vec: args
// get values from the vector that must be used here
var num = args[0];
// if you want your function safer, try this
// nas_err will print the error info on screen
// and return vm_null for runtime to interrupt
if(num.type!=vm_num) {
return nas_err("extern_fib", "\"num\" must be number");
}
// ok, you must know that vm_num now is not managed by gc
// if want to return a gc object, use ngc->alloc(type)
// usage of gc is the same as adding a native function
return var::num(fibonaci(num.tonum()));
}
// then put function name and address into this table
// make sure the end of the table is {nullptr,nullptr}
module_func_info func_tbl[] = {
{"fib", fib},
{nullptr, nullptr}
};
// 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
NASAL_EXTERN module_func_info* get() {
return func_tbl;
}
```
Next, compile this `fib.cpp` into dynamic lib.
Linux(`.so`):
`clang++ -c -O3 fib.cpp -fPIC -o fib.o`
`clang++ -shared -o libfib.so fib.o`
Mac(`.so` & `.dylib`): same as Linux.
Windows(`.dll`):
`g++ -c -O3 fib.cpp -fPIC -o fib.o`
`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 cross-platform program:
```javascript
use 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));
dylib.dlclose(dlhandle.lib);
```
`dylib.dlopen` is used to load dynamic library and get the function address.
`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`.
`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.
`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
use 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));
dylib.dlclose(dlhandle.lib);
```
If get this, Congratulations!
```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
```
## Ghost Type (for lib developers)
It's quite easy to create a new ghost by yourself now.
Look at the example below:
```c++
const auto ghost_for_test = "ghost_for_test";
// declare destructor for ghost type
void ghost_for_test_destructor(void* ptr) {
std::cout << "ghost_for_test::destructor (0x";
std::cout << std::hex << reinterpret_cast<u64>(ptr) << std::dec << ") {\n";
delete static_cast<u32*>(ptr);
std::cout << " delete 0x" << std::hex;
std::cout << reinterpret_cast<u64>(ptr) << std::dec << ";\n";
std::cout << "}\n";
}
var create_new_ghost(var* args, usize size, gc* ngc) {
var res = ngc->alloc(vm_obj);
// create ghost type
res.ghost().set(ghost_for_test, ghost_for_test_destructor, new u32);
return res;
}
var set_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
if (!res.object_check(ghost_for_test)) {
std::cout << "set_new_ghost: not ghost for test type.\n";
return nil;
}
f64 num = args[1].num();
*(reinterpret_cast<u32*>(res.ghost().pointer)) = static_cast<u32>(num);
std::cout << "set_new_ghost: successfully set ghost = " << num << "\n";
return nil;
}
var print_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
// check ghost type by the type name
if (!res.object_check(ghost_for_test)) {
std::cout << "print_new_ghost: not ghost for test type.\n";
return nil;
}
std::cout << "print_new_ghost: " << res.ghost() << " result = "
<< *((u32*)res.ghost().pointer) << "\n";
return nil;
}
```
We use this function to create a new ghost type:
`void nas_ghost::set(const std::string&, nasal::nas_ghost::destructor, void*);`
`const std::string&` is the name of the ghost type.
`nasal::nas_ghost::destructor` is the pointer of the destructor of the ghost type.
`void*` is the pointer of the ghost type instance.
And we use this function to check if value is the correct ghost type:
`bool var::object_check(const std::string&);`
The parameter is the name of the ghost type.

745
doc/tutorial_zh.md Normal file
View File

@@ -0,0 +1,745 @@
# __教程__
![mandelbrotset](../doc/pic/mandelbrotset.png)
Nasal非常容易上手你可以在15分钟之内看完基本教程并直接开始编写程序。
## __目录__
* [__基本类型__](#基本类型)
* [__运算符__](#运算符)
* [__定义变量__](#定义变量)
* [__多变量赋值__](#多变量赋值)
* [__条件语句__](#条件语句)
* [__循环语句__](#循环语句)
* [__生成子列表(subvec)__](#生成子列表subvec)
* [__特殊函数调用语法__](#特殊函数调用语法)
* [__Lambda 表达式__](#lambda表达式)
* [__闭包__](#闭包)
* [__特性与继承__](#特性与继承)
* [__多文件/模块导入__](#多文件模块导入)
* [__原生内置函数以及模块导入__](#原生内置函数以及模块导入)
* [__C++ 模块(开发者教程)__](#c-模块开发者教程)
* [__自定义类型(开发者教程)__](#自定义类型开发者教程)
## 基本类型
__`none`__ 是特殊的错误类型。这个类型用于终止虚拟机的执行,该类型只能由虚拟机在抛出错误时产生。
__`nil`__ 是空类型。类似于null。
```javascript
var spc = nil;
```
__`num`__ 有三种形式:十进制十六进制以及八进制。并且该类型使用IEEE754标准的浮点数`double`格式来存储。
```javascript
# 该语言用 '#' 来作为注释的开头
var n = 2.71828; # dec 十进制
var n = 2.147e16; # dec 十进制
var n = 1e-10; # dec 十进制
var n = 0xAA55; # hex 十六进制
var n = 0o170001; # oct 八进制
# 注意: true false 关键字在现在的 nasal 里也是可用的
var n = true; # n 实际上是数字 1.0
var n = false; # n 实际上是数字 0.0
```
__`str`__ 也有三种不同的格式。第三种只允许包含一个的字符。
```javascript
var s = 'str';
var s = "another string";
var s = `c`;
# 该语言也支持一些特别的转义字符:
'\a'; '\b'; '\e'; '\f';
'\n'; '\r'; '\t'; '\v';
'\0'; '\\'; '\?'; '\'';
'\"';
```
__`vec`__ 有不受限制的长度并且可以存储所有类型的数据。(当然不能超过可分配内存空间的长度)
```javascript
var vec = [];
var vec = [0, nil, {}, [], func(){return 0}];
append(vec, 0, 1, 2);
```
__`hash`__ 使用哈希表 (类似于`python`中的`dict`)通过键值对来存储数据。key可以是一个字符串也可以是一个标识符。
```javascript
var hash = {
member1: nil,
member2: "str",
"member3": "member\'s name can also be a string constant",
funct: func() {
return me.member2~me.member3;
}
};
```
__`func`__ 函数类型。(实际上在这个语言里函数是一种`lambda`表达式)
```javascript
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;
}
var f = func(args...) {
var sum = 0;
foreach(var i; args) {
sum += i;
}
return sum;
}
```
__`upval`__ 是存储闭包数据的特殊类型, 在 __`vm`__ 中使用,以确保闭包功能正常。
__`ghost`__ 是用来存储`C/C++`的一些复杂数据结构。这种类型的数据由内置函数生成。如果想为nasal添加新的数据结构, 可以看下文如何通过修改本项目来添加内置函数。
## 运算符
Nasal拥有基本的四种数学运算符 `+` `-` `*` `/`以及一个特别的运算符 `~`,用于拼接字符串。
```javascript
1+2-(1+3)*(2+4)/(16-9);
"str1"~"str2";
```
对于条件语句,可以使用`==` `!=` `<` `>` `<=` `>=`来比较数据。`and` `or` 与C/C++中 `&&` `||`运算符一致。
```javascript
1+1 and (1<0 or 1>0);
1<=0 and 1>=0;
1==0 or 1!=0;
```
单目运算符`-` `!`与C/C++中的运算符功能类似。
```javascript
-1;
!0;
```
位运算符`~` `|` `&` `^`与C/C++中的运算符功能类似。
```javascript
# 运行过程:
# 1. f64 强转为 i32 (static_cast<int32_t>)
# 2. 执行位运算符
~0x80000000; # 按位取反 2147483647
0x8|0x1; # 按位或
0x1&0x2; # 按位与
0x8^0x1; # 按位异或
```
赋值运算符`=` `+=` `-=` `*=` `/=` `~=` `^=` `&=` `|=`正如其名,用于进行赋值。
```javascript
a = b = c = d = 1;
a += 1;
a -= 1;
a *= 1;
a /= 1;
a ~= "string";
a ^= 0xff;
a &= 0xca;
a |= 0xba;
```
`??` 运算符用于检查左侧值是否为 `nil`,如果不是则返回右侧的值:
```javascript
print(nil??"should get this string");
```
上面的例子会输出 `should get this string`
`?.` 运算符用于先检查左侧不为 `nil`,如果左侧不是 `nil` 则尝试获取 hash 的字段。
当然如果左侧此时也不是 `hash` 类型,则报错退出。
```javascript
var a = nil;
print(a?.try_get); # nil
var a = {try_get: "congrats!"};
print(a?.try_get); # "congrats!"
```
## 定义变量
如下所示。
```javascript
var a = 1; # 定义单个变量
var (a, b, c) = [0, 1, 2]; # 从数组中初始化多个变量
var (a, b, c) = (0, 1, 2); # 从元组中初始化多个变量
```
Nasal 有很多特别的全局变量:
```javascript
globals; # 包含所有全局声明变量名和对应数据的哈希表
arg; # 在全局作用域arg 是包含命令行参数的数组
# 在局部作用域arg 是函数调用时的动态参数数组
```
具体实例:
```javascript
var a = 1;
println(globals); # 输出 {a:1}
```
```javascript
# nasal a b c
println(arg); # 输出 ["a", "b", "c"]
func() {
println(arg);
}(1, 2, 3); # 输出 [1, 2, 3]
```
## 多变量赋值
最后这个语句通常用于交换两个变量的数据类似于Python中的操作。
```javascript
(a, b[0], c.d) = [0, 1, 2];
(a, b[1], c.e) = (0, 1, 2);
(a, b) = (b, a);
```
## 条件语句
nasal在提供`else if`的同时还有另外一个关键字`elsif`。该关键字与`else if`有相同的功能。
```javascript
if (1) {
;
} elsif (2) {
;
} else if (3) {
;
} else {
;
}
```
## 循环语句
while循环和for循环大体上与C/C++是一致的。
```javascript
while(condition) {
continue;
}
for(var i = 0; i<10; i += 1) {
break;
}
```
同时nasal还有另外两种直接遍历列表的循环方式:
`forindex` 会获取列表的下标,依次递增. 下标会从`0`递增到`size(elem)-1`结束。
```javascript
forindex(var i; elem) {
print(elem[i]);
}
```
`foreach`会依次直接获取列表中的数据. 这些数据会从`elem[0]`依次获取到`elem[size(elem)-1]`.
```javascript
foreach(var i; elem) {
print(i);
}
```
## 生成子列表(subvec)
nasal提供了下面第一句的类似语法来从列表中随机或者按照一个区间获取数据并且拼接生成一个新的列表。当然如果中括号内只有一个下标的话你会直接获得这个下标对应的数据而不是一个子列表。如果直接对string使用下标来获取内容的话会得到对应字符的 __ascii值__。如果你想进一步获得这个字符串,可以尝试使用内置函数`chr()`
```javascript
a[0];
a[-1, 1, 0:2, 0:, :3, :, nil:8, 3:nil, nil:nil];
"hello world"[0];
```
## 特殊函数调用语法
这种调用方式不是很高效,因为哈希表会使用字符串比对来找到数据存放的位置。
然而如果它用起来非常舒适,那效率也显得不是非常重要了……
```javascript
f(x:0, y:nil, z:[]);
```
## lambda表达式
函数有这样一种直接编写函数体并且立即调用的方式:
```javascript
func(x, y) {
return x+y;
}(0, 1);
func(x) {
return 1/(1+math.exp(-x));
}(0.5);
```
测试文件中有一个非常有趣的文件`y-combinator.nas`,可以试一试:
```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);
}
}
);
```
## 闭包
闭包是一种特别的作用域,你可以从这个作用域中获取其保存的所有变量,
而这些变量原本不是你当前运行的函数的局部作用域中的。
下面这个例子里,结果是`1`:
```javascript
var f = func() {
var a = 1;
return func() {return a;};
}
print(f()());
```
如果善用闭包,你可以使用它来进行面向对象编程。
```javascript
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;}
};
}
```
## 特性与继承
当然,也有另外一种办法来面向对象编程,那就是利用`trait`
当一个hash类型中有一个成员的key是`parents`,并且该成员是一个数组的话,
那么当你试图从这个hash中寻找一个它自己没有的成员名时虚拟机会进一步搜索`parents`数组。
如果该数组中有一个hash类型有一个成员的key与当前你搜索的成员名一致
那么你会得到这个成员对应的值。
使用这个机制,我们可以进行面向对象编程,下面样例的结果是`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());
```
首先虚拟机会发现在`a`中找不到成员`set`,但是在`a.parents`中有个hash类型`trait`存在该成员,所以返回了这个成员的值。
成员`me`指向的是`a`自身,类似于一些语言中的`this`,所以我们通过这个函数,实际上修改了`a.val``get`函数的调用实际上也经过了相同的过程。
不过我们必须提醒你一点如果你在这个地方使用该优化来减少hash的搜索开销:
```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();
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());
```
那么你会发现现在虚拟机会输出这个结果:
```bash
114
514
514
514
514
514
```
因为执行`a.get`时在`trait.get`函数的属性中进行了`me=a`的操作。而`b.get`则执行了`me=b`的操作。所以在运行`var d=b.get`后实际上c也变成`b.get`了。
如果你想要用这种小技巧来让程序运行更高效的话,最好是要知道这里存在这样一个机制。
## 多文件/模块导入
详情可见 [namespace.md](./namespace.md)
## 原生内置函数以及模块导入
这个部分对于纯粹的使用者来说是不需要了解的,
它将告诉你我们是如何为解释器添加新的内置函数的。
如果你对此很感兴趣,那么这个部分可能会帮到你,并且……
__警告:__ 如果你 __不想__ 通过直接修改解释器源码来添加你自定义的函数,那么你应该看下一个节 __`模块`__ 的内容。
如果你确实是想修改源码来搞一个自己私人订制的解释器 ———— “我他妈就是想自己私人订制,你们他妈的管得着吗?”,
参考源码中关于内置函数的部分,以及`lib.nas`中是如何包装这些函数的,下面是其中一个样例:
定义新的内置函数:
```C++
// 你可以使用这个宏来直接定义一个新的内置函数
var builtin_print(context*, gc*);
```
然后用C++完成这个函数的函数体:
```C++
var builtin_print(context* ctx, gc* ngc) {
// 局部变量的下标其实是从 1 开始的
// 因为 local[0] 是保留给 'me' 的空间
for(auto& i : ctx->localr[1].vec().elems) {
std::cout << i;
}
std::cout << std::flush;
// 最后生成返回值,返回值必须是一个内置的类型,
// 可以使用ngc::alloc(type)来申请一个需要内存管理的复杂数据结构
// 或者用我们已经定义好的nil/one/zero这些可以直接使用
return nil;
}
```
当运行内置函数的时候内存分配器如果运行超过一次那么会有更大可能性多次触发垃圾收集器的mark-sweep。这个操作会在`gc::alloc`中触发。
如果先前获取的数值没有被正确存到可以被垃圾收集器索引到的地方,那么它会被错误地回收,这会导致严重的错误。
可以使用`gc::temp`来暂时存储一个会被返回的需要gc管理的变量这样可以防止内部所有的申请错误触发垃圾回收。如下所示
```C++
var builtin_keys(context* ctx, gc* ngc) {
auto hash = ctx->localr[1];
if (hash.type!=vm_hash && hash.type!=vm_map) {
return nas_err("keys", "\"hash\" must be hash");
}
// 使用gc.temp来存储gc管理的变量防止错误的回收
auto res = ngc->temp = ngc->alloc(vm_vec);
auto& vec = res.vec().elems;
if (hash.type==vm_hash) {
for(const auto& iter : hash.hash().elems) {
vec.push_back(ngc->newstr(iter.first));
}
} else {
for(const auto& iter : hash.map().mapper) {
vec.push_back(ngc->newstr(iter.first));
}
}
ngc->temp = nil;
return res;
}
```
这些工作都完成之后在内置函数注册表中填写它在nasal中的别名并且在表中填对这个函数的函数指针:
```C++
nasal_builtin_table 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
use dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
```
## C++ 模块(开发者教程)
如果只有上文中那种方式来添加你自定义的函数到nasal中这肯定是非常麻烦的。
因此,我们实现了一组实用的内置函数来帮助你添加你自己创建的模块。
用于加载动态库的函数在`std/dylib.nas`中:
```javascript
var dlopen = func(libname) {
...
}
var dlclose = func(lib) {
...
}
var dlcall = func(ptr, args...) {
...
}
var limitcall = func(arg_size = 0) {
...
}
```
这些函数是用来加载动态库的这样nasal解释器可以根据用户需求灵活加载动态库来执行。让我们看看这些函数该如何使用。
首先用C++写个项目,并且编译成动态库。我们就拿`fib.cpp`作为例子来说明(样例代码可以在`./module`中找到):
```C++
// 这个头文件得加上因为我们需要拿到nasal的api
#include "nasal.h"
double fibonaci(double x) {
if (x<=2) {
return x;
}
return fibonaci(x-1)+fibonaci(x-2);
}
// 模块函数的参数列表一律以这个为准
var fib(var* args, usize size, gc* ngc) {
if (!size) {
return nas_err("fib", "lack arguments");
}
// 传参会给予一个var指针指向一个vm_vec的data()
var num = args[0];
// 如果你想让这个函数有更强的稳定性,那么一定要进行合法性检查
// nas_err会输出错误信息并返回错误类型让虚拟机终止执行
if(num.type!=vm_num) {
return nas_err("extern_fib", "\"num\" must be number");
}
// vm_num作为普通的数字类型不是内存管理的对象所以无需申请
// 如果需要返回内存管理的对象请使用ngc->alloc(type)
return var::num(fibonaci(num.tonum()));
}
// 然后将函数名字和函数地址放到一个表里,一定要记住表尾是{nullptr,nullptr}
module_func_info func_tbl[] = {
{"fib", fib},
{nullptr, nullptr}
};
// 必须实现这个函数, 这样nasal可以通过字符串名字获得函数指针
// 之所以用这种方式来获取函数指针, 是因为`var`是有构造函数的
// 有构造函数的类型作为返回值, 和C是不兼容的, 这导致
// 类似 "extern "C" var fib" 的写法会得到编译错误
NASAL_EXTERN module_func_info* get() {
return func_tbl;
}
```
接着我们把`fib.cpp`编译成动态库。
Linux(`.so`):
`clang++ -c -O3 fib.cpp -fPIC -o fib.o`
`clang++ -shared -o libfib.so fib.o`
Mac(`.so` & `.dylib`): 和Linux下操作相同。
Windows(`.dll`):
`g++ -c -O3 fib.cpp -fPIC -o fib.o`
`g++ -shared -o libfib.dll fib.o`
好了那么我们可以写一个测试用的nasal代码来运行这个斐波那契函数了。
下面例子中`os.platform()`是用来检测当前运行的系统环境的,这样可以实现跨平台:
```javascript
use 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));
dylib.dlclose(dlhandle.lib);
```
`dylib.dlopen`用于加载动态库并从动态库中获得函数地址。
`dylib.dlcall`用于调用函数,第一个参数是动态库函数的地址,这是个特殊类型,一定要保证这个参数是`vm_obj`类型并且`type=obj_extern`。
`dylib.dlclose`用于卸载动态库,当然,在这个函数调用之后,所有从该库中获取的函数都作废。
`dylib.limitcall`用于获取使用固定长度传参的 `dlcall` 函数,这种函数可以提高你的程序运行效率,因为它不需要用 `vm_vec` 来存储传入参数,而是使用局部作用域来直接存储,从而避免了频繁调用可能导致的频繁垃圾收集。所以上面展示的代码同样可以这样写:
```javascript
use 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));
dylib.dlclose(dlhandle.lib);
```
如果得到如下运行结果,恭喜你!
```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
```
## 自定义类型(开发者教程)
创建一个自定义类型很容易。下面是使用示例:
```c++
const auto ghost_for_test = "ghost_for_test";
// 声明自定义类型的析构函数
void ghost_for_test_destructor(void* ptr) {
std::cout << "ghost_for_test::destructor (0x";
std::cout << std::hex << reinterpret_cast<u64>(ptr) << std::dec << ") {\n";
delete static_cast<u32*>(ptr);
std::cout << " delete 0x" << std::hex;
std::cout << reinterpret_cast<u64>(ptr) << std::dec << ";\n";
std::cout << "}\n";
}
var create_new_ghost(var* args, usize size, gc* ngc) {
var res = ngc->alloc(vm_obj);
// 创建自定义类型
res.ghost().set(ghost_for_test, ghost_for_test_destructor, new u32);
return res;
}
var set_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
if (!res.object_check(ghost_for_test)) {
std::cout << "set_new_ghost: not ghost for test type.\n";
return nil;
}
f64 num = args[1].num();
*(reinterpret_cast<u32*>(res.ghost().pointer)) = static_cast<u32>(num);
std::cout << "set_new_ghost: successfully set ghost = " << num << "\n";
return nil;
}
var print_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
// 用自定义类型的名字来检查是否是正确的自定义类型
if (!res.object_check(ghost_for_test)) {
std::cout << "print_new_ghost: not ghost for test type.\n";
return nil;
}
std::cout << "print_new_ghost: " << res.ghost() << " result = "
<< *((u32*)res.ghost().pointer) << "\n";
return nil;
}
```
我们使用下面这个函数来创建一个自定义类型:
`void nas_ghost::set(const std::string&, nasal::nas_ghost::destructor, void*);`
`const std::string&` 是自定义类型的类型名。
`nasal::nas_ghost::destructor` 是自定义类型的析构函数指针。
`void*` 是指向自定义类型实例的指针。
我们使用下面的这个函数检测是否是正确的自定义类型:
`bool var::object_check(const std::string&);`
参数是自定义类型的类型名。

View File

@@ -1,31 +0,0 @@
# Create VS project | 创建 VS 工程
## First | 首先
Make sure you are using VS 2022.
确保你使用的是 VS 2022。
## How to Create VS project
1. Get code from this repo using `git`.
2. In Visual Studio, click `File`->`New`->`Project From Existing Code...`.
3. Select `Visual C++`->`Next`->choose `project file location`->write the `project name` at ease->`Finish`.
4. Remove cpp files in `module` to avoid compilation problems.(they should be compiled to dynamic libraries)
5. Click `Source Files` in `Search Solution Explorer` at left, right click `main.cpp`, compile.
## 如何创建VS工程
1.`git`从这个仓库获取代码。
2. 在VS的界面点击文件(F)->新建(N)->从现有代码创建项目(E)。
3. 选择创建`Visual C++`项目->下一步->项目文件位置选择你下载的代码存放的文件夹->填项目名称,随便写->完成。
4. 从项目中去掉 `module` 里的cpp文件以防止编译错误。(那些本应该编译到动态库)
5. 点开左侧解决方案资源管理器中的`Source Files`,右键点击`main.cpp`,编译。

504
lib.nas
View File

@@ -1,504 +0,0 @@
# lib.nas
# 2019 ValKmjolnir
# print is used to print all things in nasal, try and see how it works.
# this function uses std::cout to output logs.
var print=func(elems...){
return __print(elems);
}
# append is used to add values into a vector.
var append=func(vec,elems...){
return __append(vec,elems);
}
# setsize is used to change the size of vector.
# if the size is larger than before,
# this function will fill vm_nil into uninitialized space.
var setsize=func(vec,size){
return __setsize(vec,size);
}
# system has the same use in C.
var system=func(str){
return __system(str);
}
# input uses std::cin and returns what we input.
var input=func(end=nil){
return __input(end);
}
# split a string by separator for example:
# split("ll","hello world") -> ["he","o world"]
# this function will return a vector.
var split=func(separator,str){
return __split(separator,str);
}
# rand has the same function as the rand in C
# if seed is nil, it will return the random number.
# if seed is not nil, it will be initialized by this seed.
var rand=func(seed=nil){
return __rand(seed);
}
# id will return the pointer of an gc-object.
# if this object is not managed by gc, it will return 0.
var id=func(object){
return __id(object);
}
# int will get the integer of input number/string.
# but carefully use it, because int has range between -2147483648~2147483647
var int=func(val){
return __int(val);
}
# floor will get the integral number of input argument
# which is less than or equal to this argument
var floor=func(val){
return __floor(val);
}
# exit using std::exit
var exit=func(val=-1){
return __exit(val);
}
# abort using std::abort
var abort=func(){
__abort();
}
# abs gets absolute number.
var abs=func(n){
return n>0?n:-n;
}
# num will change all the other types into number.
# mostly used to change a numerable string.
var num=func(val){
return __num(val);
}
# pop used to pop the last element in a vector.
# this function will return the value that poped if vector has element(s).
# if the vector is empty, it will return nil.
var pop=func(vec){
return __pop(vec);
}
# str is used to change number into string.
var str=func(num){
return __str(num);
}
# size can get the size of a string/vector/hashmap.
# in fact it can also get the size of number, and the result is the number itself.
# so don't do useless things, though it really works.
var size=func(object){
return __size(object);
}
# contains is used to check if a key exists in a hashmap/dict.
var contains=func(hash,key){
return __contains(hash,key);
}
# delete is used to delete a pair in a hashmap/dict by key.
var delete=func(hash,key){
return __delete(hash,key);
}
# keys is used to get all keys in a hashmap/dict.
# this function will return a vector.
var keys=func(hash){
return __keys(hash);
}
# time has the same function in C.
var time=func(begin){
return __time(begin);
}
var systime=func(){
return time(0);
}
# die is a special native function.
# use it at where you want the program to crash immediately.
var die=func(str){
return __die(str);
}
# find will give the first position of the needle in haystack
var find=func(needle,haystack){
return __find(needle,haystack);
}
# typeof is used to get the type of an object.
# this function returns a string.
var typeof=func(object){
return __type(object);
}
# subvec is used to get part of a vector
var subvec=func(vec,begin,length=nil){
return vec[begin:(length==nil?nil:begin+length-1)];
}
# substr will get the sub-string.
# it gets the string, the begin index and sub-string's length as arguments.
var substr=func(str,begin,len){
return __substr(str,begin,len);
}
# streq is used to compare if two strings are the same.
var streq=func(a,b){
return __streq(a,b);
}
# left is used to get the sub-string like substr.
# but the begin index is 0.
var left=func(str,len){
return __left(str,len);
}
# right i used to get the sub-string like substr.
# but the begin index is strlen-len.
var right=func(str,len){
return __right(str,len);
}
# cmp is used to compare two strings.
# normal string will not be correctly compared by operators < > <= >=
# because these operators will turn strings into numbers then compare.
var cmp=func(a,b){
return __cmp(a,b);
}
# chr is used to get the character by ascii-number.
# for example chr(65) -> 'A'
var chr=func(code){
return __chr(code);
}
# mut is used to change unmutable strings to mutable.
var mut=func(str){
return str~"";
}
# srand wraps up rand, using time(0) as the seed.
var srand=func(){
rand(time(0));
return 0;
}
# values() gets all values in a hash.
var values=func(hash){
return __values(hash);
}
# println has the same function as print.
# but it will output a '\n' after using print.
var println=func(elems...){
return __println(elems);
}
var isfunc=func(f){
return typeof(f)=="func";
}
var isghost=func(g){
die("this runtime has no ghost object");
return 0;
}
var ishash=func(h){
return typeof(h)=="hash";
}
var isint=func(x){
return x==floor(x);
}
var isnum=func(x){
return typeof(x)=="num" or !math.isnan(num(x));
}
var isscalar=func(s){
var t=typeof(s);
return (t=="num" or t=="str")?1:0;
}
var isstr=func(s){
return typeof(s)=="str";
}
var isvec=func(v){
return typeof(v)=="vec";
}
# get the index of val in the vec
var vecindex=func(vec,val){
forindex(var i;vec)
if(val==vec[i])
return i;
return nil;
}
# check if the object is an instance of the class
var isa=func(object,class){
if(!contains(object,"parents") or typeof(object.parents)!="vec")
return 0;
foreach(var elem;object.parents)
if(elem==class)
return 1;
return 0;
}
# assert aborts when condition is not true
var assert=func(condition,message="assertion failed!"){
if(condition)
return 1;
die(message);
}
# get time stamp, this will return a timestamp object
var maketimestamp=func(){
var t=0;
var millisec=func(){
return __millisec;
}
return {
stamp:func(){t=millisec();},
elapsedMSec:func(){return millisec()-t;},
elapsedUSec:func(){return (millisec()-t)*1000;}
};
}
# md5
var md5=func(str){
return __md5(str);
}
var io=
{
SEEK_SET:0,
SEEK_CUR:1,
SEEK_END:2,
# get content of a file by filename. returns a string.
fin: func(filename){return __fin(filename);},
# input a string as the content of a file.
fout: func(filename,str){return __fout(filename,str);},
# use C access
exists:func(filename){return __exists(filename);},
# same as C fopen. open file and get the FILE*.
open: func(filename,mode="r"){return __open(filename,mode);},
# same as C fclose. close file by FILE*.
close: func(filehandle){return __close(filehandle);},
# same as C fread. read file by FILE*.
# caution: buf must be a mutable string.use mut("") to get an empty mutable string.
read: func(filehandle,buf,len){return __read(filehandle,buf,len);},
# same as C fwrite. write file by FILE*.
write: func(filehandle,str){return __write(filehandle,str);},
# same as C fseek. seek place by FILE*.
seek: func(filehandle,pos,whence){return __seek(filehandle,pos,whence);},
# same as C ftell.
tell: func(filehandle){return __tell(filehandle);},
# read file by lines. use FILE*.
# get nil if EOF
readln:func(filehandle){return __readln(filehandle);},
# same as C stat.
stat: func(filename){return __stat(filename);},
# same as C feof. check if FILE* gets the end of file(EOF).
eof: func(filehandle){return __eof(filehandle);}
};
# get file status. using data from io.stat
var fstat=func(filename){
var s=io.stat(filename);
return {
st_dev: s[0],
st_ino: s[1],
st_mode: s[2],
st_nlink:s[3],
st_uid: s[4],
st_gid: s[5],
st_rdev: s[6],
st_size: s[7],
st_atime:s[8],
st_mtime:s[9],
st_ctime:s[10]
};
}
# functions that do bitwise calculation.
# carefully use it, all the calculations are based on integer.
var bits=
{
# i32 xor
i32_xor: func(a,b){return __i32xor(a,b); },
# i32 and
i32_and: func(a,b){return __i32and(a,b); },
# i32 or
i32_or: func(a,b){return __i32or(a,b); },
# i32 nand
i32_nand:func(a,b){return __i32nand(a,b);},
# i32 not
i32_not: func(a) {return __i32not(a); },
# u32 xor
u32_xor: func(a,b){return __u32xor(a,b); },
# u32 and
u32_and: func(a,b){return __u32and(a,b); },
# u32 or
u32_or: func(a,b){return __u32or(a,b); },
# u32 nand
u32_nand:func(a,b){return __u32nand(a,b);},
# u32 not
u32_not: func(a) {return __u32not(a); },
# get bit data from a special string. for example:
# bits.fld(s,0,3);
# if s stores 10100010(162)
# will get 101(5).
fld: func(str,startbit,len){return __fld;},
# get sign-extended data from a special string. for example:
# bits.sfld(s,0,3);
# if s stores 10100010(162)
# will get 101(5) then this will be signed extended to
# 11111101(-3).
sfld: func(str,startbit,len){return __sfld;},
# set value into a special string to store it. little-endian, for example:
# bits.setfld(s,0,8,69);
# set 01000101(69) to string will get this:
# 10100010(162)
# so s[0]=162.
setfld: func(str,startbit,len,val){return __setfld;},
# get a special string filled by '\0' to use in setfld.
buf: func(len){return __buf;}
};
# mostly used math functions and special constants, you know.
var math=
{
e: 2.7182818284590452354,
pi: 3.14159265358979323846264338327950288,
D2R: 2.7182818284590452354/180,
R2D: 180/2.7182818284590452354,
inf: 1/0,
nan: 0/0,
abs: func(x) {return x>0?x:-x; },
floor: func(x) {return __floor(x); },
pow: func(x,y){return __pow(x,y); },
sin: func(x) {return __sin(x); },
cos: func(x) {return __cos(x); },
tan: func(x) {return __tan(x); },
exp: func(x) {return __exp(x); },
lg: func(x) {return __lg(x); },
ln: func(x) {return __ln(x); },
sqrt: func(x) {return __sqrt(x); },
atan2: func(x,y){return __atan2(x,y);},
isnan: func(x) {return __isnan(x); },
max: func(x,y){return x>y?x:y; },
min: func(x,y){return x<y?x:y; }
};
var unix=
{
pipe: func(){return __pipe;},
fork: func(){return __fork;},
dup2: func(fd0,fd1){die("not supported yet");},
exec: func(filename,argv,envp){die("not supported yet");},
waitpid: func(pid,nohang=0){return __waitpid;},
isdir: func(path){return !!bits.u32_and(io.stat(path)[2],0x4000);}, # S_IFDIR 0x4000
isfile: func(path){return !!bits.u32_and(io.stat(path)[2],0x8000);}, # S_IFREG 0x8000
opendir: func(path){return __opendir;},
readdir: func(handle){return __readdir;},
closedir: func(handle){return __closedir;},
time: func(){return time(0);},
sleep: func(secs){return __sleep(secs);},
chdir: func(path){return __chdir(path);},
environ: func(){return __environ();},
getcwd: func(){return __getcwd();},
getenv: func(envvar){return __getenv(envvar);},
getpath: func(){return split(os.platform()=="windows"?";":":",unix.getenv("PATH"));}
};
# dylib is the core hashmap for developers to load their own library.
# for safe using dynamic library, you could use 'module' in stl/module.nas
var dylib=
{
# open dynamic lib.
dlopen: func(libname){
# find dynamic lib from local dir first
libname=(os.platform()=="windows"?".\\":"./")~libname;
if(io.exists(libname))
return __dlopen(libname);
# find dynamic lib through PATH
var envpath=split(os.platform()=="windows"?";":":",unix.getenv("PATH"));
# first find ./module
append(envpath,".");
var path=os.platform()=="windows"?"\\module\\":"/module/";
foreach(var p;envpath){
p~=path~libname;
if(io.exists(p)){
libname=p;
break;
}
}
return __dlopen(libname);
},
# load symbol from an open dynamic lib.
dlsym: func(lib,sym){return __dlsym; },
# close dynamic lib, this operation will make all the symbols loaded from it invalid.
dlclose: func(lib){return __dlclose; },
# call the loaded symbol.
dlcall: func(funcptr,args...){return __dlcall}
};
# os is used to use or get some os-related info/functions.
# windows/macOS/linux are supported.
var os=
{
# get a string that tell which os it runs on.
platform: func(){return __platform;},
time: func(){return __logtime; }
};
# runtime gives us some functions that we could manage it manually.
var runtime=
{
# command line arguments
argv: func(){return __sysargv;}
};
# functions that not supported in this runtime:
var bind=func(function,locals,outer_scope=nil){
die("this runtime does not support bind");
}
var call=func(function,args=nil,_me=nil,locals=nil,error=nil){
die("this runtime does not support call");
}
var caller=func(level=1){
die("this runtime does not support caller");
}
var closure=func(function,level=1){
die("this runtime uses \"vm_upval\" instead of \"vm_hash\" as the closure");
}
var compile=func(code,filename="<compile>"){
die("this runtime uses static code generator");
}
var coroutine={
create: func(function){return __cocreate;},
resume: func(co) {return __coresume;},
yield: func(args...) {return __coyield; },
status: func(co) {return __costatus;},
running:func() {return __corun; }
};

174
main.cpp
View File

@@ -1,174 +0,0 @@
#include "nasal.h"
#include "nasal_err.h"
#include "nasal_lexer.h"
#include "nasal_ast.h"
#include "nasal_parse.h"
#include "nasal_import.h"
#include "nasal_opt.h"
#include "nasal_gc.h"
#include "nasal_builtin.h"
#include "nasal_codegen.h"
#include "nasal_vm.h"
#include "nasal_dbg.h"
#include <unordered_map>
using ch_clk=std::chrono::high_resolution_clock;
const u32 VM_TOKEN =0x01;
const u32 VM_AST =0x02;
const u32 VM_CODE =0x04;
const u32 VM_TIME =0x08;
const u32 VM_EXEC =0x10;
const u32 VM_DETAIL=0x20;
const u32 VM_DEBUG =0x40;
const u32 VM_OPT =0x80;
void help()
{
std::clog
<<" ,--#-,\n"
<<"<3 / \\____\\ <3\n"
<<" |_|__A_|\n"
#ifdef _WIN32
<<"use command <chcp 65001> if want to use unicode.\n"
#endif
<<"nasal <option>\n"
<<"option:\n"
<<" -h, --help | get this help and exit.\n"
<<" -v, --version | get version of nasal interpreter.\n\n"
<<"nasal [option...] <file> [argv...]\n"
<<"option:\n"
<<" -l, --lex | view token info.\n"
<<" -a, --ast | view abstract syntax tree.\n"
<<" -c, --code | view bytecode.\n"
<<" -e, --exec | execute.\n"
<<" -t, --time | get the running time.\n"
<<" -d, --detail | get detail runtime crash info.\n"
<<" | get detail linker path-not-found info.\n"
<<" | get garbage collector info if didn't crash.\n"
<<" -o, --optimize | use optimizer(beta).\n"
<<" | if want to use -op and run, please use -op -e/-t/-d.\n"
<<" -dbg, --debug | debug mode (this will ignore -t -d).\n"
<<"file:\n"
<<" input file name to execute.\n"
<<"argv:\n"
<<" command line arguments used in program.\n";
}
void logo()
{
std::clog
<<" __ _\n"
<<" /\\ \\ \\__ _ ___ __ _| |\n"
<<" / \\/ / _` / __|/ _` | |\n"
<<" / /\\ / (_| \\__ \\ (_| | |\n"
<<" \\_\\ \\/ \\__,_|___/\\__,_|_|\n"
<<"version : "<<__nasver<<" ("<<__DATE__<<" "<<__TIME__<<")\n"
<<"c++ std : "<<__cplusplus<<"\n"
<<"thanks to : https://github.com/andyross/nasal\n"
<<"code repo : https://github.com/ValKmjolnir/Nasal-Interpreter\n"
<<"code repo : https://gitee.com/valkmjolnir/Nasal-Interpreter\n"
<<"lang info : http://wiki.flightgear.org/Nasal_scripting_language\n"
<<"input <nasal -h> to get help .\n";
}
[[noreturn]]
void err()
{
std::cerr
<<"invalid argument(s).\n"
<<"use <nasal -h> to get help.\n";
std::exit(1);
}
void execute(const string& file,const std::vector<string>& argv,const u32 cmd)
{
error err;
lexer lex(err);
parse parse(err);
linker ld(err);
codegen gen(err);
vm ctx;
// lexer scans file to get tokens
lex.scan(file);
if(cmd&VM_TOKEN)
lex.print();
// parser gets lexer's token list to compile
parse.compile(lex);
// linker gets parser's ast and load import files to this ast
ld.link(parse,file,cmd&VM_DETAIL);
// optimizer does simple optimization on ast
if(cmd&VM_OPT)
optimize(parse.tree());
if(cmd&VM_AST)
parse.print();
// code generator gets parser's ast and linker's import file list to generate code
gen.compile(parse,ld);
if(cmd&VM_CODE)
gen.print();
// run
if(cmd&VM_DEBUG)
debugger(err).run(gen,ld,argv);
else if(cmd&VM_TIME)
{
auto start=ch_clk::now();
ctx.run(gen,ld,argv,cmd&VM_DETAIL);
auto end=ch_clk::now();
std::clog<<"process exited after "<<(end-start).count()*1.0/ch_clk::duration::period::den<<"s.\n";
}
else if(cmd&VM_EXEC)
ctx.run(gen,ld,argv,cmd&VM_DETAIL);
}
i32 main(i32 argc,const char* argv[])
{
if(argc<=1)
{
logo();
return 0;
}
if(argc==2)
{
string s(argv[1]);
if(s=="-v" || s=="--version")
std::clog<<"nasal "<<__nasver<<" ("<<__DATE__<<" "<<__TIME__<<")\n";
else if(s=="-h" || s=="--help")
help();
else if(s[0]!='-')
execute(s,{},VM_EXEC);
else
err();
return 0;
}
std::unordered_map<string,u32> cmdlst={
{"--lex",VM_TOKEN},{"-l",VM_TOKEN},
{"--ast",VM_AST},{"-a",VM_AST},
{"--code",VM_CODE},{"-c",VM_CODE},
{"--exec",VM_EXEC},{"-e",VM_EXEC},
{"--time",VM_TIME|VM_EXEC},{"-t",VM_TIME|VM_EXEC},
{"--detail",VM_DETAIL|VM_EXEC},{"-d",VM_DETAIL|VM_EXEC},
{"--optimize",VM_OPT},{"-o",VM_OPT},
{"--debug",VM_DEBUG},{"-dbg",VM_DEBUG}
};
u32 cmd=0;
string filename;
std::vector<string> vm_argv;
for(i32 i=1;i<argc;++i)
{
if(cmdlst.count(argv[i]))
cmd|=cmdlst[argv[i]];
else if(!filename.length())
filename=argv[i];
else
vm_argv.push_back(argv[i]);
}
if(!filename.length())
err();
execute(filename,vm_argv,cmd?cmd:VM_EXEC);
return 0;
}

399
makefile
View File

@@ -1,71 +1,328 @@
.PHONY:test
SRC=\
main.cpp\
nasal_ast.h\
nasal_err.h\
nasal_builtin.h\
nasal_opt.h\
nasal_codegen.h\
nasal_gc.h\
nasal_import.h\
nasal_lexer.h\
nasal_parse.h\
nasal_vm.h\
nasal_dbg.h\
nasal.h
STD=14
nasal:$(SRC)
$(CXX) -std=c++$(STD) -O3 main.cpp -o nasal -fno-exceptions -ldl -Wshadow -Wall
nasal.exe:$(SRC)
$(CXX) -std=c++$(STD) -O3 main.cpp -o nasal.exe -fno-exceptions -Wshadow -Wall -static
stable-release:$(SRC)
$(CXX) -std=c++$(STD) -O2 main.cpp -o nasal -fno-exceptions -ldl -Wshadow -Wall
stable-release-mingw:$(SRC)
$(CXX) -std=c++$(STD) -O2 main.cpp -o nasal.exe -fno-exceptions -Wshadow -Wall -static
test:nasal
@ ./nasal -o -e test/ascii-art.nas
@ ./nasal -o -c test/auto_crash.nas
@ ./nasal -o -a -c test/bf.nas
@ ./nasal -o -a -c test/bfconvertor.nas
@ ./nasal -o -e -d test/bfs.nas
@ ./nasal -o -t test/bigloop.nas
@ ./nasal -o -e test/bp.nas
@ ./nasal -o -e -d test/calc.nas
@ ./nasal -o -e test/choice.nas
@ ./nasal -o -e test/class.nas
@ ./nasal -o -d test/coroutine.nas
@ ./nasal -o -e test/diff.nas
-@ ./nasal -o -d test/exception.nas
@ ./nasal -o -t -d test/fib.nas
@ ./nasal -o -e test/filesystem.nas
@ ./nasal -o -e -d test/hexdump.nas
@ ./nasal -o -c test/httptest.nas
@ ./nasal -o -e test/json.nas
@ ./nasal -o -e test/leetcode1319.nas
@ ./nasal -o -e -d test/lexer.nas
@ ./nasal -o -e -d test/life.nas
@ ./nasal -o -t test/loop.nas
@ ./nasal -o -t -d test/mandel.nas
@ ./nasal -o -t -d test/mandelbrot.nas
@ ./nasal -o -t -d test/md5.nas
@ ./nasal -o -t -d test/md5compare.nas
-@ ./nasal -o -d test/module_test.nas
@ ./nasal -o -e test/nasal_test.nas
@ ./nasal -o -c test/occupation.nas
@ ./nasal -o -t -d test/pi.nas
@ ./nasal -o -c test/ppmgen.nas
@ ./nasal -o -t -d test/prime.nas
@ ./nasal -o -e test/qrcode.nas
@ ./nasal -o -t -d test/quick_sort.nas
@ ./nasal -o -e test/scalar.nas hello world
-@ ./nasal -o -c -t test/snake.nas
@ ./nasal -o -c -e test/trait.nas
-@ ./nasal -o -c -t test/tetris.nas
@ ./nasal -o -c -t -d test/turingmachine.nas
@ ./nasal -o -c -t -d test/ycombinator.nas
@ ./nasal -o -d test/wavecollapse.nas
STD = c++17
ifndef OS
OS = $(shell uname)
endif
ifeq ($(OS), Darwin)
CXXFLAGS = -std=$(STD) -c -O3 -fPIC -mmacosx-version-min=10.15 -I src
else
CXXFLAGS = -std=$(STD) -c -O3 -fPIC -I src
endif
NASAL_HEADER = \
src/ast_dumper.h\
src/ast_visitor.h\
src/nasal_ast.h\
src/natives/builtin.h\
src/nasal_codegen.h\
src/nasal_dbg.h\
src/nasal_err.h\
src/nasal_gc.h\
src/nasal_import.h\
src/nasal_lexer.h\
src/nasal_opcode.h\
src/nasal_parse.h\
src/nasal_vm.h\
src/nasal_type.h\
src/nasal.h\
src/optimizer.h\
src/symbol_finder.h\
src/cli/cli.h\
src/natives/fg_props.h\
src/natives/bits_lib.h\
src/natives/io_lib.h\
src/natives/math_lib.h\
src/natives/dylib_lib.h\
src/natives/json_lib.h\
src/natives/unix_lib.h\
src/natives/coroutine.h\
src/natives/regex_lib.h\
src/repl/repl.h\
src/util/fs.h\
src/util/util.h
NASAL_OBJECT = \
build/nasal_err.o\
build/nasal_ast.o\
build/ast_visitor.o\
build/bits_lib.o\
build/ast_dumper.o\
build/nasal_lexer.o\
build/nasal_parse.o\
build/nasal_import.o\
build/optimizer.o\
build/nasal_opcode.o\
build/symbol_finder.o\
build/nasal_codegen.o\
build/nasal_gc.o\
build/builtin.o\
build/fg_props.o\
build/io_lib.o\
build/math_lib.o\
build/unix_lib.o\
build/dylib_lib.o\
build/json_lib.o\
build/coroutine.o\
build/nasal_type.o\
build/nasal_vm.o\
build/nasal_dbg.o\
build/regex_lib.o\
build/repl.o\
build/cli.o\
build/fs.o\
build/util.o\
build/main.o
# for test
nasal: $(NASAL_OBJECT) | build
@if [ OS = "Darwin" ]; then\
$(CXX) $(NASAL_OBJECT) -O3 -o nasal -ldl -lpthread -stdlib=libc++ -static-libstdc++;\
else\
$(CXX) $(NASAL_OBJECT) -O3 -o nasal -ldl -lpthread;\
fi
nasal.exe: $(NASAL_OBJECT) | build
$(CXX) $(NASAL_OBJECT) -O3 -o nasal.exe
build:
@ if [ ! -d build ]; then mkdir build; fi
build/main.o: $(NASAL_HEADER) src/main.cpp | build
$(CXX) $(CXXFLAGS) src/main.cpp -o build/main.o
build/cli.o: src/cli/cli.h src/cli/cli.cpp | build
$(CXX) $(CXXFLAGS) src/cli/cli.cpp -o build/cli.o
build/util.o: src/util/util.h src/util/util.cpp | build
$(CXX) $(CXXFLAGS) src/util/util.cpp -o build/util.o
build/fs.o: src/nasal.h src/util/util.h src/util/fs.h src/util/fs.cpp | build
$(CXX) $(CXXFLAGS) src/util/fs.cpp -o build/fs.o
build/repl.o: $(NASAL_HEADER) src/repl/repl.h src/repl/repl.cpp | build
$(CXX) $(CXXFLAGS) src/repl/repl.cpp -o build/repl.o
build/nasal_err.o: src/nasal.h src/repl/repl.h src/nasal_err.h src/nasal_err.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_err.cpp -o build/nasal_err.o
build/nasal_type.o:\
src/nasal.h\
src/util/util.h\
src/nasal_type.h src/nasal_type.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_type.cpp -o build/nasal_type.o
build/nasal_gc.o:\
src/nasal.h\
src/util/util.h\
src/nasal_type.h\
src/nasal_gc.h\
src/nasal_gc.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_gc.cpp -o build/nasal_gc.o
build/nasal_import.o: \
src/nasal.h\
src/nasal_ast.h\
src/nasal_lexer.h\
src/nasal_parse.h\
src/util/util.h\
src/util/fs.h\
src/nasal_import.h src/nasal_import.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_import.cpp -o build/nasal_import.o
build/nasal_lexer.o: \
src/nasal.h\
src/repl/repl.h\
src/util/util.h\
src/util/fs.h\
src/nasal_err.h\
src/nasal_lexer.h src/nasal_lexer.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_lexer.cpp -o build/nasal_lexer.o
build/nasal_ast.o: \
src/nasal.h\
src/nasal_err.h\
src/nasal_ast.h src/nasal_ast.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_ast.cpp -o build/nasal_ast.o
build/builtin.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/util/util.h\
src/natives/builtin.h\
src/natives/builtin.cpp | build
$(CXX) $(CXXFLAGS) src/natives/builtin.cpp -o build/builtin.o
build/coroutine.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/coroutine.h src/natives/coroutine.cpp | build
$(CXX) $(CXXFLAGS) src/natives/coroutine.cpp -o build/coroutine.o
build/bits_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/bits_lib.h src/natives/bits_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/bits_lib.cpp -o build/bits_lib.o
build/math_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/math_lib.h src/natives/math_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/math_lib.cpp -o build/math_lib.o
build/io_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/util/fs.h\
src/natives/io_lib.h src/natives/io_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/io_lib.cpp -o build/io_lib.o
build/dylib_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/dylib_lib.h src/natives/dylib_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/dylib_lib.cpp -o build/dylib_lib.o
build/json_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/util/util.h\
src/natives/json_lib.h src/natives/json_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/json_lib.cpp -o build/json_lib.o
build/unix_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/unix_lib.h src/natives/unix_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/unix_lib.cpp -o build/unix_lib.o
build/regex_lib.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/regex_lib.h src/natives/regex_lib.cpp | build
$(CXX) $(CXXFLAGS) src/natives/regex_lib.cpp -o build/regex_lib.o
build/fg_props.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/fg_props.h src/natives/fg_props.cpp | build
$(CXX) $(CXXFLAGS) src/natives/fg_props.cpp -o build/fg_props.o
build/nasal_codegen.o: $(NASAL_HEADER) src/nasal_codegen.h src/nasal_codegen.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_codegen.cpp -o build/nasal_codegen.o
build/nasal_opcode.o: \
src/nasal.h\
src/natives/builtin.h\
src/util/util.h\
src/nasal_opcode.h src/nasal_opcode.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_opcode.cpp -o build/nasal_opcode.o
build/nasal_parse.o: \
src/nasal.h\
src/nasal_ast.h\
src/nasal_lexer.h\
src/nasal_err.h\
src/util/util.h\
src/nasal_parse.h src/nasal_parse.cpp src/nasal_ast.h | build
$(CXX) $(CXXFLAGS) src/nasal_parse.cpp -o build/nasal_parse.o
build/optimizer.o: \
src/nasal.h\
src/nasal_err.h\
src/nasal_ast.h\
src/ast_visitor.h\
src/optimizer.h src/optimizer.cpp src/nasal_ast.h | build
$(CXX) $(CXXFLAGS) src/optimizer.cpp -o build/optimizer.o
build/symbol_finder.o: \
src/nasal.h\
src/nasal_err.h\
src/nasal_ast.h\
src/ast_visitor.h\
src/symbol_finder.h src/symbol_finder.cpp src/nasal_ast.h | build
$(CXX) $(CXXFLAGS) src/symbol_finder.cpp -o build/symbol_finder.o
build/ast_visitor.o: \
src/nasal.h\
src/nasal_err.h\
src/nasal_ast.h\
src/ast_visitor.h src/ast_visitor.cpp | build
$(CXX) $(CXXFLAGS) src/ast_visitor.cpp -o build/ast_visitor.o
build/ast_dumper.o: \
src/nasal.h\
src/nasal_err.h\
src/nasal_ast.h\
src/ast_visitor.h\
src/util/util.h\
src/ast_dumper.h src/ast_dumper.cpp | build
$(CXX) $(CXXFLAGS) src/ast_dumper.cpp -o build/ast_dumper.o
build/nasal_vm.o: $(NASAL_HEADER) src/nasal_vm.h src/nasal_vm.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_vm.cpp -o build/nasal_vm.o
build/nasal_dbg.o: $(NASAL_HEADER) src/nasal_dbg.h src/nasal_dbg.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_dbg.cpp -o build/nasal_dbg.o
.PHONY: clean
clean:
@ echo "[clean] nasal" && if [ -e nasal ]; then rm nasal; fi
@ echo "[clean] nasal.exe" && if [ -e nasal.exe ]; then rm nasal.exe; fi
@ rm $(NASAL_OBJECT)
.PHONY: test
test:nasal
@ ./nasal -t -d test/andy_gc_test.nas
@ ./nasal test/argparse_test.nas
@ ./nasal -e test/ascii-art.nas
@ ./nasal -t -d test/bfs.nas
@ ./nasal -t test/bigloop.nas
@ ./nasal -t test/bp.nas
@ ./nasal -d test/calc.nas
@ ./nasal -e test/choice.nas
@ ./nasal -e test/class.nas
@ ./nasal -t -d test/console3D.nas 20
@ ./nasal -e test/coroutine.nas
@ ./nasal -t -d test/datalog.nas
@ ./nasal -e test/diff.nas
@ ./nasal -e test/donuts.nas 15
-@ ./nasal -d test/exception.nas
@ ./nasal -t -d test/fib.nas
@ ./nasal -e test/filesystem.nas
@ ./nasal -t -d test/globals_test.nas
@ ./nasal -d test/hexdump.nas
@ ./nasal -e test/json.nas
@ ./nasal -e test/leetcode1319.nas
@ ./nasal -d test/lexer.nas
@ ./nasal -d test/life.nas
@ ./nasal -t test/loop.nas
@ ./nasal -t test/mandelbrot.nas
@ ./nasal -t test/md5_self.nas
@ ./nasal -t -d test/md5compare.nas
@ ./nasal -d test/module_test.nas
@ ./nasal -e test/nasal_test.nas
@ ./nasal -t -d test/occupation.nas 2
@ ./nasal -t -d test/pi.nas
@ ./nasal -t -d test/prime.nas
@ ./nasal -e test/qrcode.nas
@ ./nasal -t -d test/quick_sort.nas
@ ./nasal -t -d test/regex_test.nas
@ ./nasal -e test/scalar.nas hello world
@ ./nasal -e test/trait.nas
@ ./nasal -t -d test/turingmachine.nas
@ ./nasal -d test/wavecollapse.nas
@ ./nasal -d test/wavecity.nas
@ ./nasal test/word_collector.nas test/md5compare.nas
@ ./nasal -t -d test/ycombinator.nas

View File

@@ -1,45 +1,131 @@
#include <iostream>
#include "../nasal.h"
double fibonaci(double x){
if(x<=2)
return x;
return fibonaci(x-1)+fibonaci(x-2);
}
var fib(var* args,usize size,gc* ngc){
std::cout<<"[mod] this is the first test module of nasal\n";
if(!size)
return nas_err("fib","lack arguments");
var num=args[0];
if(num.type!=vm_num)
return nas_err("extern_fib","\"num\" must be number");
return {vm_num,fibonaci(num.tonum())};
}
var quick_fib(var* args,usize size,gc* ngc){
std::cout<<"[mod] this is the first test module of nasal\n";
if(!size)
return nas_err("fib","lack arguments");
var num=args[0];
if(num.type!=vm_num)
return nas_err("extern_quick_fib","\"num\" must be number");
if(num.num()<2)
return num;
double a=1,b=1,res=0;
for(double i=1;i<num.num();i+=1){
res=a+b;
a=b;
b=res;
}
return {vm_num,res};
}
extern "C" mod get(const char* n){
string name=n;
if(name=="fib")
return fib;
else if(name=="quick_fib")
return quick_fib;
return nullptr;
// module for test
#include <iostream>
#include "../src/nasal.h"
#include "../src/nasal_type.h"
#include "../src/nasal_gc.h"
namespace nasal {
namespace fib_module {
double fibonaci(double x) {
if (x<=2) {
return x;
}
return fibonaci(x-1)+fibonaci(x-2);
}
var fib(var* args, usize size, gc* ngc) {
if (!size) {
return nas_err("fib", "lack arguments");
}
var num = args[0];
return var::num(fibonaci(num.to_num()));
}
var quick_fib(var* args, usize size, gc* ngc) {
if (!size) {
return nas_err("quick_fib", "lack arguments");
}
double num = args[0].to_num();
if (num<2) {
return var::num(num);
}
double a = 1, b = 1, res = 0;
for(double i = 1; i<num; ++i) {
res = a+b;
a = b;
b = res;
}
return var::num(res);
}
const auto ghost_for_test = "ghost_for_test";
struct ghost_obj {
u32 number = 0;
var test_string = nil;
};
// if the dynamic library is closed, the pointer of this function will be unsafe
// make sure gc deletes the object before closing the dynamic library
// or just do not close the dynamic library...
void ghost_for_test_destructor(void* ptr) {
std::cout << "ghost_for_test::destructor (0x";
std::cout << std::hex << reinterpret_cast<u64>(ptr) << std::dec << ") {\n";
delete static_cast<ghost_obj*>(ptr);
std::cout << " delete 0x" << std::hex;
std::cout << reinterpret_cast<u64>(ptr) << std::dec << ";\n";
std::cout << "}\n";
}
void ghost_for_test_gc_marker(void* ptr, std::vector<var>* bfs_queue) {
std::cout << "ghost_for_test::mark (0x";
std::cout << std::hex << reinterpret_cast<u64>(ptr) << std::dec << ") {\n";
bfs_queue->push_back(static_cast<ghost_obj*>(ptr)->test_string);
std::cout << " mark 0x" << std::hex;
std::cout << reinterpret_cast<u64>(ptr) << std::dec << "->test_string;\n";
std::cout << "}\n";
}
var create_new_ghost(var* args, usize size, gc* ngc) {
var res = ngc->alloc(vm_type::vm_ghost);
res.ghost().set(
ghost_for_test,
ghost_for_test_destructor,
ghost_for_test_gc_marker,
new ghost_obj
);
return res;
}
var set_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
if (!res.object_check(ghost_for_test)) {
std::cout << "set_new_ghost: not ghost for test type.\n";
return nil;
}
f64 num = args[1].num();
reinterpret_cast<ghost_obj*>(res.ghost().pointer)->number = static_cast<u32>(num);
std::cout << "set_new_ghost: successfully set ghost.number = " << num << "\n";
reinterpret_cast<ghost_obj*>(res.ghost().pointer)->test_string = ngc->newstr("just for test");
std::cout << "set_new_ghost: successfully set ghost.test_string = just for test\n";
return nil;
}
var print_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
if (!res.object_check(ghost_for_test)) {
std::cout << "print_new_ghost: not ghost for test type.\n";
return nil;
}
std::cout << "print_new_ghost: " << res.ghost() << " number = "
<< reinterpret_cast<ghost_obj*>(res.ghost().pointer)->number
<< " test_string = "
<< reinterpret_cast<ghost_obj*>(res.ghost().pointer)->test_string
<< "\n";
return nil;
}
module_func_info func_tbl[] = {
{"fib", fib},
{"quick_fib", quick_fib},
{"create_ghost", create_new_ghost},
{"set_ghost", set_new_ghost},
{"print_ghost", print_new_ghost},
{nullptr, nullptr}
};
}
NASAL_EXTERN module_func_info* get() {
return fib_module::func_tbl;
}
}

View File

@@ -1,95 +1,118 @@
#include "../nasal.h"
#include <unistd.h>
#include <iostream>
#ifdef _WIN32
#include <conio.h>
#else
#include <fcntl.h>
#include <termios.h>
#endif
class noecho_input{
private:
#ifndef _WIN32
struct termios init_termios;
struct termios new_termios;
int peek_char=-1;
#endif
public:
noecho_input(){
#ifndef _WIN32
tcflush(0,TCIOFLUSH);
tcgetattr(0,&init_termios);
new_termios=init_termios;
new_termios.c_lflag&=~(ICANON|ECHO|ECHONL|ECHOE);
// vmin=0 is nonblock input, but in wsl there is a bug that will block input
// so we use fcntl to write the nonblock input
new_termios.c_cc[VMIN]=1;
new_termios.c_cc[VTIME]=0;
tcsetattr(0,TCSANOW,&new_termios);
#endif
}
~noecho_input(){
#ifndef _WIN32
tcflush(0,TCIOFLUSH);
tcsetattr(0,TCSANOW,&init_termios);
#endif
}
int noecho_kbhit(){
#ifndef _WIN32
unsigned char ch=0;
int nread=0;
if(peek_char!=-1)
return 1;
int flag=fcntl(0,F_GETFL);
fcntl(0,F_SETFL,flag|O_NONBLOCK);
nread=read(0,&ch,1);
fcntl(0,F_SETFL,flag);
if(nread==1){
peek_char=ch;
return 1;
}
return 0;
#else
return kbhit();
#endif
}
int noecho_getch(){
#ifndef _WIN32
int ch=0;
if(peek_char!=-1){
ch=peek_char;
peek_char=-1;
return ch;
}
ssize_t tmp=read(0,&ch,1);
return ch;
#else
return getch();
#endif
}
};
noecho_input this_window;
var nas_getch(var* args,usize size,gc* ngc){
return {vm_num,(double)this_window.noecho_getch()};
}
var nas_kbhit(var* args,usize size,gc* ngc){
return {vm_num,(double)this_window.noecho_kbhit()};
}
var nas_noblock(var* args,usize size,gc* ngc){
if(this_window.noecho_kbhit())
return {vm_num,(double)this_window.noecho_getch()};
return nil;
}
extern "C" mod get(const char* n){
string name=n;
if(name=="nas_getch")
return nas_getch;
else if(name=="nas_kbhit")
return nas_kbhit;
else if(name=="nas_noblock")
return nas_noblock;
return nullptr;
}
#include "../src/nasal.h"
#include "../src/nasal_type.h"
#include "../src/nasal_gc.h"
#include <iostream>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#ifdef _MSC_VER
#pragma warning (disable:4996)
#endif
#ifdef _WIN32
#include <conio.h>
#else
#include <fcntl.h>
#include <termios.h>
#endif
namespace nasal {
class noecho_input {
private:
#ifndef _WIN32
struct termios init_termios;
struct termios new_termios;
int peek_char = -1;
#endif
public:
noecho_input() {
#ifndef _WIN32
tcflush(0, TCIOFLUSH);
tcgetattr(0, &init_termios);
new_termios = init_termios;
new_termios.c_lflag &= ~(ICANON|ECHO|ECHONL|ECHOE);
// vmin = 0 is nonblock input,
// but in wsl1 there is a bug that will block input
// so we use fcntl to write the nonblock input
new_termios.c_cc[VMIN] = 1;
new_termios.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &new_termios);
#endif
}
~noecho_input() {
#ifndef _WIN32
tcflush(0, TCIOFLUSH);
tcsetattr(0, TCSANOW, &init_termios);
#endif
}
int noecho_kbhit() {
#ifndef _WIN32
unsigned char ch = 0;
int nread = 0;
if (peek_char!=-1) {
return 1;
}
int flag = fcntl(0, F_GETFL);
fcntl(0, F_SETFL, flag|O_NONBLOCK);
nread = read(0, &ch, 1);
fcntl(0, F_SETFL, flag);
if (nread==1) {
peek_char = ch;
return 1;
}
return 0;
#else
return kbhit();
#endif
}
int noecho_getch() {
#ifndef _WIN32
int ch = 0;
if (peek_char!=-1) {
ch = peek_char;
peek_char = -1;
return ch;
}
ssize_t tmp = read(0, &ch, 1);
return ch;
#else
return getch();
#endif
}
};
noecho_input this_window;
var nas_getch(var* args, usize size, gc* ngc) {
return var::num(static_cast<double>(this_window.noecho_getch()));
}
var nas_kbhit(var* args, usize size, gc* ngc) {
return var::num(static_cast<double>(this_window.noecho_kbhit()));
}
var nas_noblock(var* args, usize size, gc* ngc) {
if (this_window.noecho_kbhit()) {
return var::num(static_cast<double>(this_window.noecho_getch()));
}
return nil;
}
module_func_info func_tbl[] = {
{"nas_getch", nas_getch},
{"nas_kbhit", nas_kbhit},
{"nas_noblock", nas_noblock},
{nullptr, nullptr}
};
NASAL_EXTERN module_func_info* get() {
return func_tbl;
}
}

View File

@@ -1,10 +1,58 @@
var libfib=func(){
var dl=dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));
var fib=dylib.dlsym(dl,"fib");
var qfib=dylib.dlsym(dl,"quick_fib");
var call=dylib.dlcall;
return {
fib: func(x){return call(fib,x)},
qfib:func(x){return call(qfib,x)}
};
}();
use std.dylib;
use std.os;
var _dl = dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));
var _fib = _dl.fib;
var _qfib = _dl.quick_fib;
var _create_ghost = _dl.create_ghost;
var _set_ghost = _dl.set_ghost;
var _print_ghost = _dl.print_ghost;
var _zero_call = dylib.limitcall(0);
var _call = dylib.limitcall(1);
var _test_call = dylib.limitcall(2);
var fib = func(x) {
return _call(_fib, x)
}
var qfib = func(x) {
return _call(_qfib, x)
}
var create_ghost = func() {
return _zero_call(_create_ghost)
}
var set_ghost = func(object, x) {
return _test_call(_set_ghost, object, x)
}
var print_ghost = func(object) {
return _call(_print_ghost, object)
}
var test_ghost = func() {
var ghost = create_ghost();
print_ghost(nil); # err
print("\n");
print_ghost(ghost); # random
print("\n");
set_ghost(nil, 114); # err
print("\n");
set_ghost(ghost, 114); # success
print("\n");
for(var i = 0; i<256; i+=1) {
var temp = []; # try to trigger gc
}
print("\n");
print_ghost(ghost); # 114
print("\n");
}

View File

@@ -1,12 +1,19 @@
var libkey=func(){
var lib=dylib.dlopen("libkey"~(os.platform()=="windows"?".dll":".so"));
var kb=dylib.dlsym(lib,"nas_kbhit");
var gt=dylib.dlsym(lib,"nas_getch");
var nb=dylib.dlsym(lib,"nas_noblock");
var call=dylib.dlcall;
return {
kbhit:func(){return call(kb);},
getch:func(){return call(gt);},
nonblock:func(){return call(nb);}
}
}();
use std.dylib;
use std.os;
var (
kbhit,
getch,
nonblock
) = func {
var lib = dylib.dlopen("libkey"~(os.platform()=="windows"? ".dll":".so"));
var kb = lib.nas_kbhit;
var gt = lib.nas_getch;
var nb = lib.nas_noblock;
var call = dylib.limitcall(0);
return [
func() {return call(kb);},
func() {return call(gt);},
func() {return call(nb);}
];
}();

67
module/libmat.nas Normal file
View File

@@ -0,0 +1,67 @@
use std.dylib;
use std.os;
var _dl = dylib.dlopen("libmat."~(os.platform()=="windows"?"dll":"so"));
var _vec2 = _dl.nas_vec2;
var _vec3 = _dl.nas_vec3;
var (_vec2add, _vec2sub, _vec2mul, _vec2div, _vec2neg, _vec2norm, _vec2len, _vec2dot)=(
_dl.nas_vec2_add,
_dl.nas_vec2_sub,
_dl.nas_vec2_mult,
_dl.nas_vec2_div,
_dl.nas_vec2_neg,
_dl.nas_vec2_norm,
_dl.nas_vec2_len,
_dl.nas_vec2_dot
);
var (_vec3add, _vec3sub, _vec3mul, _vec3div, _vec3neg, _vec3norm, _vec3len, _vec3dot)=(
_dl.nas_vec3_add,
_dl.nas_vec3_sub,
_dl.nas_vec3_mult,
_dl.nas_vec3_div,
_dl.nas_vec3_neg,
_dl.nas_vec3_norm,
_dl.nas_vec3_len,
_dl.nas_vec3_dot
);
var (_rotate_x, _rotate_y, _rotate_z)=(
_dl.nas_rotate_x,
_dl.nas_rotate_y,
_dl.nas_rotate_z
);
var (_invoke_i, _invoke_ii, _invoke_iii)=(
dylib.limitcall(1),
dylib.limitcall(2),
dylib.limitcall(3)
);
var vec2 = {
new: func(x, y) {return _invoke_ii(_vec2, x, y);},
add: func(v0, v1) {return _invoke_ii(_vec2add, v0, v1);},
sub: func(v0, v1) {return _invoke_ii(_vec2sub, v0, v1);},
mul: func(v0, v1) {return _invoke_ii(_vec2mul, v0, v1);},
div: func(v0, v1) {return _invoke_ii(_vec2div, v0, v1);},
neg: func(v0) {return _invoke_i(_vec2neg, v0);},
norm: func(v0) {return _invoke_i(_vec2norm, v0);},
len: func(v0) {return _invoke_i(_vec2len, v0);},
dot: func(v0, v1) {return _invoke_ii(_vec2dot, v0, v1);}
};
var vec3 = {
new: func(x, y, z) {return _invoke_iii(_vec3, x, y, z);},
add: func(v0, v1) {return _invoke_ii(_vec3add, v0, v1);},
sub: func(v0, v1) {return _invoke_ii(_vec3sub, v0, v1);},
mul: func(v0, v1) {return _invoke_ii(_vec3mul, v0, v1);},
div: func(v0, v1) {return _invoke_ii(_vec3div, v0, v1);},
neg: func(v0) {return _invoke_i(_vec3neg, v0);},
norm: func(v0) {return _invoke_i(_vec3norm, v0);},
len: func(v0) {return _invoke_i(_vec3len, v0);},
rx: func(v0, angle) {return _invoke_ii(_rotate_x, v0, angle);},
ry: func(v0, angle) {return _invoke_ii(_rotate_y, v0, angle);},
rz: func(v0, angle) {return _invoke_ii(_rotate_z, v0, angle);},
dot: func(v0, v1) {return _invoke_ii(_vec3dot, v0, v1);}
};

120
module/libnasock.nas Normal file
View File

@@ -0,0 +1,120 @@
use std.dylib;
use std.os;
var socket = func() {
var lib = dylib.dlopen("libnasock"~(os.platform()=="windows"? ".dll":".so"));
var sock = lib.nas_socket;
var closesocket = lib.nas_closesocket;
var shutdown = lib.nas_shutdown;
var bind = lib.nas_bind;
var listen = lib.nas_listen;
var connect = lib.nas_connect;
var accept = lib.nas_accept;
var send = lib.nas_send;
var sendto = lib.nas_sendto;
var recv = lib.nas_recv;
var recvfrom = lib.nas_recvfrom;
var errno = lib.nas_errno;
var (invoke, invoke_i, invoke_ii, invoke_iii, invoke_iiii, invoke_iiiii) = (
dylib.limitcall(0),
dylib.limitcall(1),
dylib.limitcall(2),
dylib.limitcall(3),
dylib.limitcall(4),
dylib.limitcall(5),
);
return {
AF_UNSPEC:0,
AF_UNIX: 1,
AF_INET: 2,
AF_IMPLINK: 3,
AF_PUP: 4,
AF_CHAOS: 5,
AF_IPX: 6,
AF_NS: 6,
AF_ISO: 7,
AF_OSI: 7,
AF_ECMA: 8,
AF_DATAKIT: 9,
AF_CCITT: 10,
AF_SNA: 11,
AF_DECnet: 12,
AF_DLI: 13,
AF_LAT: 14,
AF_HYLINK: 15,
AF_APPLETALK: 16,
AF_NETBIOS: 17,
AF_VOICEVIEW: 18,
AF_FIREFOX: 19,
AF_UNKNOWN1: 20,
AF_BAN: 21,
AF_MAX: 22,
SOCKET_ERROR: -1,
SOCK_STREAM: 1,
SOCK_DGRAM: 2,
SOCK_RAW: 3,
SOCK_RDM: 4,
SOCK_SEQPACKET: 5,
IPPROTO_IP:0,IPPROTO_ICMP:1,IPPROTO_IGMP:2,IPPROTO_GGP:3,
IPPROTO_TCP:6,IPPROTO_PUP:12,IPPROTO_UDP:17,IPPROTO_IDP:22,
IPPROTO_ND:77,IPPROTO_RAW:255,IPPROTO_MAX:256,
IPPORT_ECHO:7,IPPORT_DISCARD:9,IPPORT_SYSTAT:11,IPPORT_DAYTIME:13,
IPPORT_NETSTAT:15,IPPORT_FTP:21,IPPORT_TELNET:23,IPPORT_SMTP:25,
IPPORT_TIMESERVER:37,IPPORT_NAMESERVER:42,IPPORT_WHOIS:43,IPPORT_MTP:57,
IPPORT_TFTP:69,IPPORT_RJE:77,IPPORT_FINGER:79,IPPORT_TTYLINK:87,
IPPORT_SUPDUP:95,IPPORT_EXECSERVER:512,IPPORT_LOGINSERVER:513,IPPORT_CMDSERVER:514,
IPPORT_EFSSERVER:520,IPPORT_BIFFUDP:512,IPPORT_WHOSERVER:513,IPPORT_ROUTESERVER:520,
IPPORT_RESERVED:1024,
SHUT_RD : 0x00,
SHUT_WR : 0x01,
SHUT_RDWR: 0x02,
MSG_OOB: 0x1,
MSG_PEEK: 0x2,
MSG_DONTROUTE: 0x4,
MSG_DONTWAIT: 0x40,
socket: func(af, type, proto = 0) {
return invoke_iii(sock, af, type, proto);
},
closesocket: func(sd) {
return invoke_i(closesocket, sd);
},
shutdown: func(sd, how) {
return invoke_ii(shutdown, sd, how);
},
bind: func(sd, ip, port) {
return invoke_iii(bind, sd, ip, port);
},
listen: func(sd, backlog) {
return invoke_ii(listen, sd, backlog);
},
connect: func(sd, hostname, port) {
return invoke_iii(connect, sd, hostname, port);
},
accept: func(sd) {
return invoke_i(accept, sd);
},
send: func(sd, buff, flags = 0) {
return invoke_iii(send, sd, buff, flags);
},
sendto: func(sd, hostname, port, buff, flags = 0) {
return invoke_iiiii(sendto, sd, hostname, port, buff, flags);
},
recv: func(sd, len, flags = 0) {
return invoke_iii(recv, sd, len, flags);
},
recvfrom: func(sd, len, flags = 0) {
return invoke_iii(recvfrom, sd, len, flags);
},
errno: func() {
return invoke(errno);
}
};
}();

View File

@@ -1,85 +0,0 @@
var socket=func(){
var lib=dylib.dlopen("libnasock"~(os.platform()=="windows"?".dll":".so"));
var sock=dylib.dlsym(lib,"nas_socket");
var closesocket=dylib.dlsym(lib,"nas_closesocket");
var shutdown=dylib.dlsym(lib,"nas_shutdown");
var bind=dylib.dlsym(lib,"nas_bind");
var listen=dylib.dlsym(lib,"nas_listen");
var connect=dylib.dlsym(lib,"nas_connect");
var accept=dylib.dlsym(lib,"nas_accept");
var send=dylib.dlsym(lib,"nas_send");
var sendto=dylib.dlsym(lib,"nas_sendto");
var recv=dylib.dlsym(lib,"nas_recv");
var recvfrom=dylib.dlsym(lib,"nas_recvfrom");
var errno=dylib.dlsym(lib,"nas_errno");
var call=dylib.dlcall;
return {
AF_UNSPEC:0,AF_UNIX:1,AF_INET:2,AF_IMPLINK:3,
AF_PUP:4,AF_CHAOS:5,AF_IPX:6,AF_NS:6,
AF_ISO:7,AF_OSI:7,AF_ECMA:8,AF_DATAKIT:9,
AF_CCITT:10,AF_SNA:11,AF_DECnet:12,AF_DLI:13,
AF_LAT:14,AF_HYLINK:15,AF_APPLETALK:16,AF_NETBIOS:17,
AF_VOICEVIEW:18,AF_FIREFOX:19,AF_UNKNOWN1:20,AF_BAN:21,
AF_MAX:22,
SOCK_STREAM:1,SOCK_DGRAM:2,SOCK_RAW:3,SOCK_RDM:4,
SOCK_SEQPACKET:5,
IPPROTO_IP:0,IPPROTO_ICMP:1,IPPROTO_IGMP:2,IPPROTO_GGP:3,
IPPROTO_TCP:6,IPPROTO_PUP:12,IPPROTO_UDP:17,IPPROTO_IDP:22,
IPPROTO_ND:77,IPPROTO_RAW:255,IPPROTO_MAX:256,
IPPORT_ECHO:7,IPPORT_DISCARD:9,IPPORT_SYSTAT:11,IPPORT_DAYTIME:13,
IPPORT_NETSTAT:15,IPPORT_FTP:21,IPPORT_TELNET:23,IPPORT_SMTP:25,
IPPORT_TIMESERVER:37,IPPORT_NAMESERVER:42,IPPORT_WHOIS:43,IPPORT_MTP:57,
IPPORT_TFTP:69,IPPORT_RJE:77,IPPORT_FINGER:79,IPPORT_TTYLINK:87,
IPPORT_SUPDUP:95,IPPORT_EXECSERVER:512,IPPORT_LOGINSERVER:513,IPPORT_CMDSERVER:514,
IPPORT_EFSSERVER:520,IPPORT_BIFFUDP:512,IPPORT_WHOSERVER:513,IPPORT_ROUTESERVER:520,
IPPORT_RESERVED:1024,
SHUT_RD :0x00,
SHUT_WR :0x01,
SHUT_RDWR:0x02,
MSG_OOB:0x1,
MSG_PEEK:0x2,
MSG_DONTROUTE:0x4,
socket:func(af,type,proto){
return call(sock,af,type,proto);
},
closesocket:func(sd){
return call(closesocket,sd);
},
shutdown: func(sd,how){
return call(shutdown,sd,how);
},
bind: func(sd,ip,port){
return call(bind,sd,ip,port);
},
listen: func(sd,backlog){
return call(listen,sd,backlog);
},
connect: func(sd,hostname,port){
return call(connect,sd,hostname,port);
},
accept: func(sd){
return call(accept,sd);
},
send: func(sd,buff,flags=0){
return call(send,sd,buff,flags);
},
sendto: func(sd,hostname,port,buff,flags=0){
return call(sendto,sd,hostname,port,buff,flags);
},
recv: func(sd,len,flags=0){
return call(recv,sd,len,flags);
},
recvfrom: func(sd,len,flags=0){
return call(recvfrom,sd,len,flags);
},
errno: func(){
return call(errno);
}
};
}();

View File

@@ -1,43 +1,77 @@
.PHONY=clean all mingw-all
.PHONY = clean all winall
STD=14
dynamic_libs_so = libfib.so libkey.so libnasock.so libmat.so
dynamic_libs_dll = libfib.dll libkey.dll libnasock.dll libmat.dll
libfib.so: fib.cpp
@ echo "[build] libfib.so"
@ $(CXX) -std=c++$(STD) -c -O3 fib.cpp -fPIC -o fib.o
@ $(CXX) -shared -o libfib.so fib.o
used_header = ../src/nasal.h ../src/util/util.h ../src/nasal_type.h ../src/nasal_gc.h
used_object = ../build/util.o ../build/nasal_type.o ../build/nasal_gc.o
STD = c++17
ifndef OS
OS = $(shell uname)
endif
ifeq ($(OS), Darwin)
CXXFLAGS = -std=$(STD) -c -O3 -fPIC -mmacosx-version-min=10.15
else
CXXFLAGS = -std=$(STD) -c -O3 -fPIC
endif
all: $(dynamic_libs_so)
@ echo "[Compiling] done"
winall: $(dynamic_libs_dll)
@ echo [Compiling] done
libfib.so: fib.cpp $(used_header) $(used_object)
@ echo "[Compiling] libfib.so"
@ $(CXX) $(CXXFLAGS) fib.cpp -o fib.o
@ $(CXX) -shared -o libfib.so fib.o $(used_object)
@ rm fib.o
libfib.dll: fib.cpp
@ echo [build] libfib.dll
@ $(CXX) -std=c++$(STD) -c -O3 fib.cpp -fPIC -o fib.o
@ $(CXX) -shared -o libfib.dll fib.o
libfib.dll: fib.cpp $(used_header) $(used_object)
@ echo [Compiling] libfib.dll
@ $(CXX) -std=$(STD) -c -O3 fib.cpp -fPIC -o fib.o
@ $(CXX) -shared -o libfib.dll fib.o $(used_object) -static
@ del fib.o
libkey.so: keyboard.cpp
@ echo "[build] libkey.so"
@ $(CXX) -std=c++$(STD) -c -O3 keyboard.cpp -fPIC -o keyboard.o
@ $(CXX) -shared -o libkey.so keyboard.o
libkey.so: keyboard.cpp $(used_header) $(used_object)
@ echo "[Compiling] libkey.so"
@ $(CXX) $(CXXFLAGS) keyboard.cpp -o keyboard.o
@ $(CXX) -shared -o libkey.so keyboard.o $(used_object)
@ rm keyboard.o
libkey.dll: keyboard.cpp
@ echo [build] libkey.dll
@ $(CXX) -std=c++$(STD) -c -O3 keyboard.cpp -fPIC -o keyboard.o -static
@ $(CXX) -shared -o libkey.dll keyboard.o -static
libkey.dll: keyboard.cpp $(used_header) $(used_object)
@ echo [Compiling] libkey.dll
@ $(CXX) -std=$(STD) -c -O3 keyboard.cpp -fPIC -o keyboard.o -static
@ $(CXX) -shared -o libkey.dll keyboard.o $(used_object) -static
@ del keyboard.o
libnasock.so: nasocket.cpp
@ echo "[build] libnasock.so"
@ $(CXX) -std=c++$(STD) -c -O3 nasocket.cpp -fPIC -o nasocket.o
@ $(CXX) -shared -o libnasock.so nasocket.o
libnasock.so: nasocket.cpp $(used_header) $(used_object)
@ echo "[Compiling] libnasock.so"
@ $(CXX) $(CXXFLAGS) nasocket.cpp -o nasocket.o
@ $(CXX) -shared -o libnasock.so nasocket.o $(used_object)
@ rm nasocket.o
libnasock.dll: nasocket.cpp
@ echo [build] libnasock.dll
@ $(CXX) -std=c++$(STD) -c -O3 nasocket.cpp -fPIC -o nasocket.o -lwsock32 -static
@ $(CXX) -shared -o libnasock.dll nasocket.o -lwsock32 -static
libnasock.dll: nasocket.cpp $(used_header) $(used_object)
@ echo [Compiling] libnasock.dll
@ $(CXX) -std=$(STD) -c -O3 nasocket.cpp -fPIC -o nasocket.o -lwsock32 -static
@ $(CXX) -shared -o libnasock.dll nasocket.o $(used_object) -lwsock32 -static
@ del nasocket.o
libmat.so: matrix.cpp $(used_header) $(used_object)
@ echo "[Compiling] libmat.so"
@ $(CXX) $(CXXFLAGS) matrix.cpp -o matrix.o
@ $(CXX) -shared -o libmat.so matrix.o $(used_object)
@ rm matrix.o
libmat.dll: matrix.cpp $(used_header) $(used_object)
@ echo [Compiling] libmat.dll
@ $(CXX) -std=$(STD) -c -O3 matrix.cpp -fPIC -o matrix.o -static
@ $(CXX) -shared -o libmat.dll matrix.o $(used_object) -static
@ del matrix.o
clean:
-@ rm *.so *.dll *.dylib
all: libfib.so libkey.so libnasock.so
@ echo "[build] done"
mingw-all: libfib.dll libkey.dll libnasock.dll
@ echo [build] done
@ echo "[clean] libfib.so" && if [ -e libfib.so ]; then rm libfib.so; fi
@ echo "[clean] libkey.so" && if [ -e libkey.so ]; then rm libkey.so; fi
@ echo "[clean] libnasock.so" && if [ -e libnasock.so ]; then rm libnasock.so; fi
@ echo "[clean] libmat.so" && if [ -e libmat.so ]; then rm libmat.so; fi
@ echo "[clean] libfib.dll" &&if [ -e libfib.dll ]; then rm libfib.dll; fi
@ echo "[clean] libkey.dll" &&if [ -e libkey.dll ]; then rm libkey.dll; fi
@ echo "[clean] libnasock.dll" &&if [ -e libnasock.dll ]; then rm libnasock.dll; fi
@ echo "[clean] libmat.dll" &&if [ -e libmat.dll ]; then rm libmat.dll; fi

302
module/matrix.cpp Normal file
View File

@@ -0,0 +1,302 @@
#include "../src/nasal.h"
#include "../src/nasal_type.h"
#include "../src/nasal_gc.h"
#include <cmath>
namespace nasal {
var nas_vec2(var* args, usize size, gc* ngc) {
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(args[0]);
res.vec().elems.push_back(args[1]);
return res;
}
var nas_vec3(var* args, usize size, gc* ngc) {
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(args[0]);
res.vec().elems.push_back(args[1]);
res.vec().elems.push_back(args[2]);
return res;
}
var nas_vec2_add(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=2 || v1.size()!=2)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()+v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()+v1[1].num()));
return res;
}
var nas_vec2_sub(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=2 || v1.size()!=2)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()-v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()-v1[1].num()));
return res;
}
var nas_vec2_mult(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=2 || v1.size()!=2)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()*v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()*v1[1].num()));
return res;
}
var nas_vec2_div(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=2 || v1.size()!=2)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()/v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()/v1[1].num()));
return res;
}
var nas_vec2_neg(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=2)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(-v0[0].num()));
res.vec().elems.push_back(var::num(-v0[1].num()));
return res;
}
var nas_vec2_norm(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=2)
return nil;
auto x = v0[0].num();
auto y = v0[1].num();
auto t = std::sqrt(x*x+y*y);
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(x/t));
res.vec().elems.push_back(var::num(y/t));
return res;
}
var nas_vec2_len(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=2)
return nil;
auto x = v0[0].num();
auto y = v0[1].num();
return var::num(std::sqrt(x*x+y*y));
}
var nas_vec2_dot(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=2 || v1.size()!=2)
return nil;
return var::num(v0[0].num()*v1[0].num()+v0[1].num()*v1[1].num());
}
var nas_vec3_add(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=3 || v1.size()!=3)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()+v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()+v1[1].num()));
res.vec().elems.push_back(var::num(v0[2].num()+v1[2].num()));
return res;
}
var nas_vec3_sub(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=3 || v1.size()!=3)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()-v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()-v1[1].num()));
res.vec().elems.push_back(var::num(v0[2].num()-v1[2].num()));
return res;
}
var nas_vec3_mult(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=3 || v1.size()!=3)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()*v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()*v1[1].num()));
res.vec().elems.push_back(var::num(v0[2].num()*v1[2].num()));
return res;
}
var nas_vec3_div(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=3 || v1.size()!=3)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()/v1[0].num()));
res.vec().elems.push_back(var::num(v0[1].num()/v1[1].num()));
res.vec().elems.push_back(var::num(v0[2].num()/v1[2].num()));
return res;
}
var nas_vec3_neg(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(-v0[0].num()));
res.vec().elems.push_back(var::num(-v0[1].num()));
res.vec().elems.push_back(var::num(-v0[2].num()));
return res;
}
var nas_vec3_norm(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
auto x = v0[0].num();
auto y = v0[1].num();
auto z = v0[2].num();
auto t = std::sqrt(x*x+y*y+z*z);
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(x/t));
res.vec().elems.push_back(var::num(y/t));
res.vec().elems.push_back(var::num(z/t));
return res;
}
var nas_vec3_len(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
auto x = v0[0].num();
auto y = v0[1].num();
auto z = v0[2].num();
return var::num(std::sqrt(x*x+y*y+z*z));
}
var nas_rotate_x(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
auto angle = args[1].num();
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()));
res.vec().elems.push_back(var::num(v0[2].num()*std::sin(angle)+v0[1].num()*std::cos(angle)));
res.vec().elems.push_back(var::num(v0[2].num()*std::cos(angle)-v0[1].num()*std::sin(angle)));
return res;
}
var nas_rotate_y(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
auto angle = args[1].num();
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()*std::cos(angle)-v0[2].num()*std::sin(angle)));
res.vec().elems.push_back(var::num(v0[1].num()));
res.vec().elems.push_back(var::num(v0[0].num()*std::sin(angle)+v0[2].num()*std::cos(angle)));
return res;
}
var nas_rotate_z(var* args, usize size, gc* ngc) {
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
auto angle = args[1].num();
var res = ngc->alloc(vm_type::vm_vec);
res.vec().elems.push_back(var::num(v0[0].num()*std::cos(angle)-v0[1].num()*std::sin(angle)));
res.vec().elems.push_back(var::num(v0[0].num()*std::sin(angle)+v0[1].num()*std::cos(angle)));
res.vec().elems.push_back(var::num(v0[2].num()));
return res;
}
var nas_vec3_dot(var* args, usize size, gc* ngc) {
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
if (v0.size()!=3 || v1.size()!=3)
return nil;
return var::num(v0[0].num()*v1[0].num()+v0[1].num()*v1[1].num()+v0[2].num()*v1[2].num());
}
module_func_info func_tbl[] = {
{"nas_vec2", nas_vec2},
{"nas_vec2_add", nas_vec2_add},
{"nas_vec2_sub", nas_vec2_sub},
{"nas_vec2_mult", nas_vec2_mult},
{"nas_vec2_div", nas_vec2_div},
{"nas_vec2_neg", nas_vec2_neg},
{"nas_vec2_norm", nas_vec2_norm},
{"nas_vec2_len", nas_vec2_len},
{"nas_vec2_dot", nas_vec2_dot},
{"nas_vec3", nas_vec3},
{"nas_vec3_add", nas_vec3_add},
{"nas_vec3_sub", nas_vec3_sub},
{"nas_vec3_mult", nas_vec3_mult},
{"nas_vec3_div", nas_vec3_div},
{"nas_vec3_neg", nas_vec3_neg},
{"nas_vec3_norm", nas_vec3_norm},
{"nas_vec3_len", nas_vec3_len},
{"nas_rotate_x", nas_rotate_x},
{"nas_rotate_y", nas_rotate_y},
{"nas_rotate_z", nas_rotate_z},
{"nas_vec3_dot", nas_vec3_dot},
{nullptr, nullptr}
};
NASAL_EXTERN module_func_info* get() {
return func_tbl;
}
}

View File

@@ -1,217 +1,269 @@
#include "../nasal.h"
#include <unistd.h>
#ifdef _WIN32
#include <winsock.h>
#pragma comment(lib,"ws2_32")
class WSAmanager{
private:
WSAData data;
public:
WSAmanager(){
WSAStartup(0x1010,&data);
}
~WSAmanager(){
WSACleanup();
}
};
static WSAmanager win;
#else
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
var nas_socket(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num || args[1].type!=vm_num || args[2].type!=vm_num)
return nas_err("socket","\"af\", \"type\", \"protocol\" should be number");
int sd=socket(args[0].num(),args[1].num(),args[2].num());
return {vm_num,(double)sd};
}
var nas_closesocket(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("closesocket","\"\" should be number");
#ifdef _WIN32
return {vm_num,(double)closesocket(args[0].num())};
#else
return {vm_num,(double)close(args[0].num())};
#endif
}
var nas_shutdown(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("shutdown","\"sd\" must be a number");
if(args[1].type!=vm_num)
return nas_err("shutdown","\"how\" must be a number");
return {vm_num,(double)shutdown(args[0].num(),args[1].num())};
}
var nas_bind(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("bind","\"sd\" muse be a number");
if(args[1].type!=vm_str)
return nas_err("bind","\"ip\" should be a string including an ip with correct format");
if(args[2].type!=vm_num)
return nas_err("bind","\"port\" must be a number");
sockaddr_in server;
memset(&server,0,sizeof(sockaddr_in));
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr(args[1].str().c_str());
server.sin_port=htons(args[2].num());
return {vm_num,(double)bind(args[0].num(),(sockaddr*)&server,sizeof(server))};
}
var nas_listen(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("listen","\"sd\" must be a number");
if(args[1].type!=vm_num)
return nas_err("listen","\"backlog\" must be a number");
return{vm_num,(double)listen(args[0].num(),args[1].num())};
}
var nas_connect(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("connect","\"sd\" must be a number");
if(args[1].type!=vm_str)
return nas_err("connect","\"hostname\" must be a string");
if(args[2].type!=vm_num)
return nas_err("connect","\"port\" must be a number");
sockaddr_in addr;
memset(&addr,0,sizeof(sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(args[2].num());
hostent* entry=gethostbyname(args[1].str().c_str());
memcpy(&addr.sin_addr,entry->h_addr,entry->h_length);
return {vm_num,(double)connect(args[0].num(),(sockaddr*)&addr,sizeof(sockaddr_in))};
}
var nas_accept(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("accept","\"sd\" must be a number");
sockaddr_in client;
int socklen=sizeof(sockaddr_in);
#ifdef _WIN32
int client_sd=accept(args[0].num(),(sockaddr*)&client,&socklen);
#else
int client_sd=accept(args[0].num(),(sockaddr*)&client,(socklen_t*)&socklen);
#endif
var res=ngc->temp=ngc->alloc(vm_hash);
auto& hash=res.hash().elems;
hash["sd"]={vm_num,(double)client_sd};
hash["ip"]=ngc->newstr(inet_ntoa(client.sin_addr));
ngc->temp=nil;
return res;
}
var nas_send(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("send","\"sd\" must be a number");
if(args[1].type!=vm_str)
return nas_err("send","\"buff\" must be a string");
if(args[2].type!=vm_num)
return nas_err("send","\"flags\" muse be a number");
return {vm_num,(double)send(args[0].num(),args[1].str().c_str(),args[1].str().length(),args[2].num())};
}
var nas_sendto(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("sendto","\"sd\" must be a number");
if(args[1].type!=vm_str)
return nas_err("sendto","\"hostname\" must be a string");
if(args[2].type!=vm_num)
return nas_err("sendto","\"port\" must be a number");
if(args[3].type!=vm_str)
return nas_err("sendto","\"buff\" must be a string");
if(args[4].type!=vm_num)
return nas_err("sendto","\"flags\" must be a number");
sockaddr_in addr;
memset(&addr,0,sizeof(sockaddr_in));
addr.sin_family=AF_INET;
addr.sin_port=htons(args[2].num());
hostent* entry=gethostbyname(args[1].str().c_str());
memcpy(&addr.sin_addr,entry->h_addr,entry->h_length);
return {vm_num,(double)sendto(args[0].num(),args[3].str().c_str(),args[3].str().length(),args[4].num(),(sockaddr*)&addr,sizeof(sockaddr_in))};
}
var nas_recv(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("recv","\"sd\" must be a number");
if(args[1].type!=vm_num)
return nas_err("recv","\"len\" must be a number");
if(args[1].num()<=0 || args[1].num()>16*1024*1024)
return nas_err("recv","\"len\" out of range");
if(args[2].type!=vm_num)
return nas_err("recv","\"flags\" muse be a number");
var res=ngc->temp=ngc->alloc(vm_hash);
auto& hash=res.hash().elems;
char* buf=new char[(int)args[1].num()];
hash["size"]={vm_num,(double)recv(args[0].num(),buf,args[1].num(),args[2].num())};
hash["str"]=ngc->newstr(buf);
delete[] buf;
ngc->temp=nil;
return res;
}
var nas_recvfrom(var* args,usize size,gc* ngc){
if(args[0].type!=vm_num)
return nas_err("recvfrom","\"sd\" must be a number");
if(args[1].type!=vm_num)
return nas_err("recvfrom","\"len\" must be a number");
if(args[1].num()<=0 || args[1].num()>16*1024*1024)
return nas_err("recvfrom","\"len\" out of range");
if(args[2].type!=vm_num)
return nas_err("recvfrom","\"flags\" muse be a number");
sockaddr_in addr;
int socklen=sizeof(sockaddr_in);
var res=ngc->temp=ngc->alloc(vm_hash);
auto& hash=res.hash().elems;
char* buf=new char[(int)args[1].num()+1];
#ifdef _WIN32
hash["size"]={vm_num,(double)recvfrom(args[0].num(),buf,args[1].num(),args[2].num(),(sockaddr*)&addr,&socklen)};
#else
hash["size"]={vm_num,(double)recvfrom(args[0].num(),buf,args[1].num(),args[2].num(),(sockaddr*)&addr,(socklen_t*)&socklen)};
#endif
buf[(int)hash["size"].num()]=0;
hash["str"]=ngc->newstr(buf);
delete[] buf;
hash["fromip"]=ngc->newstr(inet_ntoa(addr.sin_addr));
ngc->temp=nil;
return res;
}
var nas_errno(var* args,usize size,gc* ngc){
return ngc->newstr(strerror(errno));
}
extern "C" mod get(const char* n){
string name=n;
if(name=="nas_socket")
return nas_socket;
else if(name=="nas_closesocket")
return nas_closesocket;
else if(name=="nas_shutdown")
return nas_shutdown;
else if(name=="nas_bind")
return nas_bind;
else if(name=="nas_listen")
return nas_listen;
else if(name=="nas_connect")
return nas_connect;
else if(name=="nas_accept")
return nas_accept;
else if(name=="nas_send")
return nas_send;
else if(name=="nas_sendto")
return nas_sendto;
else if(name=="nas_recv")
return nas_recv;
else if(name=="nas_recvfrom")
return nas_recvfrom;
else if(name=="nas_errno")
return nas_errno;
return nullptr;
}
#include "../src/nasal.h"
#include "../src/nasal_type.h"
#include "../src/nasal_gc.h"
#ifndef _MSC_VER
#include <unistd.h>
#endif
#ifdef _MSC_VER
#pragma warning (disable:4996)
#endif
#ifdef _WIN32
// load socket library on windows platform
#include <winsock.h>
#pragma comment(lib,"ws2_32")
class WSAmanager {
private:
WSAData data;
public:
WSAmanager() {
WSAStartup(0x1010, &data);
}
~WSAmanager() {
WSACleanup();
}
};
// use static object to do WSAStartup and WSACleanup
static WSAmanager win;
#else
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
namespace nasal {
var nas_socket(var* args, usize size, gc* ngc) {
if (!args[0].is_num() || !args[1].is_num() || !args[2].is_num())
return nas_err("socket", "\"af\", \"type\", \"protocol\" should be number");
int sd = socket(args[0].num(), args[1].num(), args[2].num());
return var::num(static_cast<double>(sd));
}
var nas_closesocket(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("closesocket", "\"sd\" should be number");
#ifdef _WIN32
return var::num(static_cast<double>(closesocket(args[0].num())));
#else
return var::num(static_cast<double>(close(args[0].num())));
#endif
}
var nas_shutdown(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("shutdown", "\"sd\" must be a number");
if (!args[1].is_num())
return nas_err("shutdown", "\"how\" must be a number");
return var::num(static_cast<double>(shutdown(args[0].num(), args[1].num())));
}
var nas_bind(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("bind", "\"sd\" muse be a number");
if (!args[1].is_str())
return nas_err("bind", "\"ip\" should be a string including an ip with correct format");
if (!args[2].is_num())
return nas_err("bind", "\"port\" must be a number");
sockaddr_in server;
memset(&server, 0, sizeof(sockaddr_in));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(args[1].str().c_str());
server.sin_port = htons(args[2].num());
return var::num(static_cast<double>(bind(
args[0].num(),
reinterpret_cast<sockaddr*>(&server),
sizeof(server)
)));
}
var nas_listen(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("listen", "\"sd\" must be a number");
if (!args[1].is_num())
return nas_err("listen", "\"backlog\" must be a number");
return var::num(static_cast<double>(listen(args[0].num(), args[1].num())));
}
var nas_connect(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("connect", "\"sd\" must be a number");
if (!args[1].is_str())
return nas_err("connect", "\"hostname\" must be a string");
if (!args[2].is_num())
return nas_err("connect", "\"port\" must be a number");
sockaddr_in addr;
memset(&addr, 0, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(args[2].num());
hostent* entry = gethostbyname(args[1].str().c_str());
memcpy(&addr.sin_addr, entry->h_addr, entry->h_length);
return var::num(static_cast<double>(connect(
args[0].num(),
reinterpret_cast<sockaddr*>(&addr),
sizeof(sockaddr_in)
)));
}
var nas_accept(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("accept", "\"sd\" must be a number");
sockaddr_in client;
int socklen = sizeof(sockaddr_in);
#ifdef _WIN32
int client_sd = accept(
args[0].num(),
reinterpret_cast<sockaddr*>(&client),
&socklen
);
#else
int client_sd = accept(
args[0].num(),
reinterpret_cast<sockaddr*>(&client),
reinterpret_cast<socklen_t*>(&socklen)
);
#endif
var res = ngc->temp = ngc->alloc(vm_type::vm_hash);
auto& hash = res.hash().elems;
hash["sd"] = var::num(static_cast<double>(client_sd));
hash["ip"] = ngc->newstr(inet_ntoa(client.sin_addr));
ngc->temp = nil;
return res;
}
var nas_send(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("send", "\"sd\" must be a number");
if (!args[1].is_str())
return nas_err("send", "\"buff\" must be a string");
if (!args[2].is_num())
return nas_err("send", "\"flags\" muse be a number");
return var::num(static_cast<double>(send(
args[0].num(),
args[1].str().c_str(),
args[1].str().length(),
args[2].num()
)));
}
var nas_sendto(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("sendto", "\"sd\" must be a number");
if (!args[1].is_str())
return nas_err("sendto", "\"hostname\" must be a string");
if (!args[2].is_num())
return nas_err("sendto", "\"port\" must be a number");
if (!args[3].is_str())
return nas_err("sendto", "\"buff\" must be a string");
if (!args[4].is_num())
return nas_err("sendto", "\"flags\" must be a number");
sockaddr_in addr;
memset(&addr, 0, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(args[2].num());
hostent* entry = gethostbyname(args[1].str().c_str());
memcpy(&addr.sin_addr, entry->h_addr, entry->h_length);
return var::num(static_cast<double>(sendto(
args[0].num(),
args[3].str().c_str(),
args[3].str().length(),
args[4].num(),
reinterpret_cast<sockaddr*>(&addr),
sizeof(sockaddr_in)
)));
}
var nas_recv(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("recv", "\"sd\" must be a number");
if (!args[1].is_num())
return nas_err("recv", "\"len\" must be a number");
if (args[1].num()<=0 || args[1].num()>16*1024*1024)
return nas_err("recv", "\"len\" out of range");
if (!args[2].is_num())
return nas_err("recv", "\"flags\" muse be a number");
var res = ngc->temp = ngc->alloc(vm_type::vm_hash);
auto& hash = res.hash().elems;
char* buf = new char[static_cast<int>(args[1].num())];
auto recvsize = recv(args[0].num(), buf, args[1].num(), args[2].num());
hash["size"] = var::num(static_cast<double>(recvsize));
buf[recvsize>=0? recvsize:0] = 0;
hash["str"] = ngc->newstr(buf);
delete[] buf;
ngc->temp = nil;
return res;
}
var nas_recvfrom(var* args, usize size, gc* ngc) {
if (!args[0].is_num())
return nas_err("recvfrom", "\"sd\" must be a number");
if (!args[1].is_num())
return nas_err("recvfrom", "\"len\" must be a number");
if (args[1].num()<=0 || args[1].num()>16*1024*1024)
return nas_err("recvfrom", "\"len\" out of range");
if (!args[2].is_num())
return nas_err("recvfrom", "\"flags\" muse be a number");
sockaddr_in addr;
int socklen = sizeof(sockaddr_in);
var res = ngc->temp = ngc->alloc(vm_type::vm_hash);
auto& hash = res.hash().elems;
char* buf = new char[static_cast<int>(args[1].num()+1)];
#ifdef _WIN32
auto recvsize = recvfrom(
args[0].num(),
buf,
args[1].num(),
args[2].num(),
reinterpret_cast<sockaddr*>(&addr),
&socklen
);
#else
auto recvsize = recvfrom(
args[0].num(),
buf,
args[1].num(),
args[2].num(),
reinterpret_cast<sockaddr*>(&addr),
reinterpret_cast<socklen_t*>(&socklen)
);
#endif
hash["size"] = var::num(static_cast<double>(recvsize));
buf[recvsize>=0? recvsize:0] = 0;
hash["str"] = ngc->newstr(buf);
delete[] buf;
hash["fromip"] = ngc->newstr(inet_ntoa(addr.sin_addr));
hash["port"] = var::num(ntohs(addr.sin_port));
ngc->temp = nil;
return res;
}
var nas_errno(var* args, usize size, gc* ngc) {
return ngc->newstr(strerror(errno));
}
module_func_info func_tbl[] = {
{"nas_socket", nas_socket},
{"nas_closesocket", nas_closesocket},
{"nas_shutdown", nas_shutdown},
{"nas_bind", nas_bind},
{"nas_listen", nas_listen},
{"nas_connect", nas_connect},
{"nas_accept", nas_accept},
{"nas_send", nas_send},
{"nas_sendto", nas_sendto},
{"nas_recv", nas_recv},
{"nas_recvfrom", nas_recvfrom},
{"nas_errno", nas_errno},
{nullptr, nullptr}
};
NASAL_EXTERN module_func_info* get() {
return func_tbl;
}
}

155
nasal.h
View File

@@ -1,155 +0,0 @@
#pragma once
#ifndef __nasver
#define __nasver "10.1"
#endif
#include <cstdint>
#include <iostream>
#include <fstream>
#include <cstring>
#include <cmath>
#include <vector>
using i32=std::int32_t;
using i64=std::int64_t;
using u8=std::uint8_t;
using u16=std::uint16_t;
using u32=std::uint32_t;
using u64=std::uint64_t;
using usize=std::size_t;
using f64=double;
using std::string;
const u32 STACK_DEPTH=1024;
inline f64 hex2f(const char* str)
{
f64 ret=0;
for(;*str;++str)
{
if('0'<=*str && *str<='9')
ret=ret*16+(*str-'0');
else if('a'<=*str && *str<='f')
ret=ret*16+(*str-'a'+10);
else if('A'<=*str && *str<='F')
ret=ret*16+(*str-'A'+10);
else
return nan("");
}
return ret;
}
inline f64 oct2f(const char* str)
{
f64 ret=0;
while('0'<=*str && *str<'8')
ret=ret*8+(*str++-'0');
if(*str) return nan("");
return ret;
}
// we have the same reason not using atof here just as andy's interpreter does.
// it is not platform independent, and may have strange output.
// so we write a new function here to convert str to number manually.
// but this also makes 0.1+0.2==0.3, not another result that you may get in other languages.
inline f64 dec2f(const char* str)
{
f64 ret=0,negative=1,num_pow=0;
while('0'<=*str && *str<='9')
ret=ret*10+(*str++-'0');
if(!*str) return ret;
if(*str=='.')
{
if(!*++str) return nan("");
num_pow=0.1;
while('0'<=*str && *str<='9')
{
ret+=num_pow*(*str++-'0');
num_pow*=0.1;
}
if(!*str) return ret;
}
if(*str!='e' && *str!='E')
return nan("");
if(!*++str) return nan("");
if(*str=='-' || *str=='+')
negative=(*str++=='-'? -1:1);
if(!*str) return nan("");
num_pow=0;
while('0'<=*str && *str<='9')
num_pow=num_pow*10+(*str++-'0');
if(*str) return nan("");
return ret*std::pow(10,negative*num_pow);
}
f64 str2num(const char* str)
{
bool negative=false;
f64 res=0;
if(*str=='-' || *str=='+')
negative=(*str++=='-');
if(!*str)
return nan("");
if(str[0]=='0' && str[1]=='x')
res=hex2f(str+2);
else if(str[0]=='0' && str[1]=='o')
res=oct2f(str+2);
else
res=dec2f(str);
return negative?-res:res;
}
i32 utf8_hdchk(const char head)
{
// RFC-2279 but now we use RFC-3629 so nbytes is less than 4
const u8 c=(u8)head;
if((c>>5)==0x06) // 110x xxxx (10xx xxxx)^1
return 1;
if((c>>4)==0x0e) // 1110 xxxx (10xx xxxx)^2
return 2;
if((c>>3)==0x1e) // 1111 0xxx (10xx xxxx)^3
return 3;
return 0;
}
string chrhex(const char c)
{
const char hextbl[]="0123456789abcdef";
return {hextbl[(c&0xf0)>>4],hextbl[c&0x0f]};
}
string rawstr(const string& str,const usize maxlen=0)
{
string ret("");
for(auto i:str)
{
#ifdef _WIN32
// windows ps or cmd doesn't output unicode normally
// if 'chcp65001' is not enabled, we output the hex
if(i<=0)
{
ret+="\\x"+chrhex(i);
continue;
}
#endif
switch(i)
{
case '\0': ret+="\\0"; break;
case '\a': ret+="\\a"; break;
case '\b': ret+="\\b"; break;
case '\t': ret+="\\t"; break;
case '\n': ret+="\\n"; break;
case '\v': ret+="\\v"; break;
case '\f': ret+="\\f"; break;
case '\r': ret+="\\r"; break;
case '\033':ret+="\\e"; break;
case '\"': ret+="\\\"";break;
case '\'': ret+="\\\'";break;
case '\\': ret+="\\\\";break;
default: ret+=i; break;
}
}
if(maxlen && ret.length()>maxlen)
ret=ret.substr(0,maxlen)+"...";
return ret;
}
#include "nasal_gc.h" // declarations of var and nasal_gc

View File

@@ -1,256 +0,0 @@
#pragma once
#include <vector>
#include <cstring>
enum ast_node:u32
{
ast_null=0, // null node
ast_root, // mark the root node of ast
ast_block, // expression block
ast_file, // used to store which file the sub-tree is on, only used in main block
ast_nil, // nil keyword
ast_num, // number, basic value type
ast_str, // string, basic value type
ast_id, // identifier
ast_func, // func keyword
ast_hash, // hash, basic value type
ast_vec, // vector, basic value type
ast_pair, // pair of key and value in hashmap
ast_call, // mark a sub-tree of calling an identifier
ast_callh, // id.name
ast_callv, // id[index]
ast_callf, // id()
ast_subvec, // id[index:index]
ast_args, // mark a sub-tree of function parameters
ast_default, // default parameter
ast_dynamic, // dynamic parameter
ast_and, // and keyword
ast_or, // or keyword
ast_equal, // =
ast_addeq, // +=
ast_subeq, // -=
ast_multeq, // *=
ast_diveq, // /=
ast_lnkeq, // ~=
ast_cmpeq, // ==
ast_neq, // !=
ast_less, // <
ast_leq, // <=
ast_grt, // >
ast_geq, // >=
ast_add, // +
ast_sub, // -
ast_mult, // *
ast_div, // /
ast_link, // ~
ast_neg, // -
ast_not, // ~
ast_trino, // ?:
ast_for, // for keyword
ast_forindex, // forindex keyword
ast_foreach, // foreach keyword
ast_while, // while
ast_iter, // iterator, used in forindex/foreach
ast_cond, // mark a sub-tree of conditional expression
ast_if, // if keyword
ast_elsif, // elsif keyword
ast_else, // else keyword
ast_multi_id, // multi identifiers sub-tree
ast_tuple, // tuple, only used in multiple assignment
ast_def, // definition
ast_multi_assign,// multi assignment sub-tree
ast_continue, // continue keyword, only used in loop
ast_break, // break keyword, only used in loop
ast_ret // return keyword, only used in function block
};
const char* ast_name[]=
{
"null",
"root",
"block",
"file",
"nil",
"num",
"str",
"id",
"func",
"hash",
"vec",
"pair",
"call",
"callh",
"callv",
"callf",
"subvec",
"args",
"default",
"dynamic",
"and",
"or",
"=",
"+=",
"-=",
"*=",
"/=",
"~=",
"==",
"!=",
"<",
"<=",
">",
">=",
"+",
"-",
"*",
"/",
"~",
"neg",
"!",
"trino",
"for",
"forindex",
"foreach",
"while",
"iter",
"cond",
"if",
"elsif",
"else",
"multi-id",
"tuple",
"def",
"multi-assign",
"continue",
"break",
"return"
};
class ast
{
private:
u32 nd_line;
u32 nd_col;
u32 nd_type;
f64 nd_num;
string nd_str;
std::vector<ast> nd_child;
public:
ast(const u32 l,const u32 c,const u32 t):
nd_line(l),nd_col(c),nd_type(t),nd_num(0){}
ast(const ast&);
ast(ast&&);
void print_tree();
void print(u32,bool,std::vector<string>&);
void clear();
ast& operator=(const ast&);
ast& operator=(ast&&);
ast& operator[](usize n){return nd_child[n];}
const ast& operator[](usize n) const {return nd_child[n];}
usize size() const {return nd_child.size();}
void add(ast&& node){nd_child.push_back(std::move(node));}
void add(const ast& node){nd_child.push_back(node);}
void set_line(const u32 l){nd_line=l;}
void set_type(const u32 t){nd_type=t;}
void set_str(const string& s){nd_str=s;}
void set_num(const f64 n){nd_num=n;}
inline u32 line() const {return nd_line;}
inline u32 col() const {return nd_col;}
inline u32 type() const {return nd_type;}
inline f64 num() const {return nd_num;}
inline const string& str() const {return nd_str;}
inline const std::vector<ast>& child() const {return nd_child;}
inline std::vector<ast>& child(){return nd_child;}
};
ast::ast(const ast& tmp):
nd_str(tmp.nd_str),nd_child(tmp.nd_child)
{
nd_line=tmp.nd_line;
nd_col=tmp.nd_col;
nd_type=tmp.nd_type;
nd_num =tmp.nd_num;
}
ast::ast(ast&& tmp)
{
nd_line=tmp.nd_line;
nd_col=tmp.nd_col;
nd_type=tmp.nd_type;
nd_num =tmp.nd_num;
nd_str.swap(tmp.nd_str);
nd_child.swap(tmp.nd_child);
}
ast& ast::operator=(const ast& tmp)
{
nd_line=tmp.nd_line;
nd_col=tmp.nd_col;
nd_type=tmp.nd_type;
nd_num=tmp.nd_num;
nd_str=tmp.nd_str;
nd_child=tmp.nd_child;
return *this;
}
ast& ast::operator=(ast&& tmp)
{
nd_line=tmp.nd_line;
nd_col=tmp.nd_col;
nd_type=tmp.nd_type;
nd_num=tmp.nd_num;
nd_str.swap(tmp.nd_str);
nd_child.swap(tmp.nd_child);
return *this;
}
void ast::clear()
{
nd_line=nd_col=0;
nd_num=0;
nd_str.clear();
nd_type=ast_null;
nd_child.clear();
}
void ast::print_tree()
{
std::vector<string> tmp;
print(0,false,tmp);
}
void ast::print(u32 depth,bool last,std::vector<string>& indent)
{
for(auto& i:indent)
std::cout<<i;
std::cout<<ast_name[nd_type];
if(nd_type==ast_str || nd_type==ast_id ||
nd_type==ast_default || nd_type==ast_dynamic ||
nd_type==ast_callh)
std::cout<<":"<<rawstr(nd_str);
else if(nd_type==ast_num || nd_type==ast_file)
std::cout<<":"<<nd_num;
std::cout<<"\n";
if(last && depth)
indent.back()=" ";
else if(!last && depth)
#ifdef _WIN32
indent.back()="| ";
#else
indent.back()="";
#endif
for(u32 i=0;i<nd_child.size();++i)
{
#ifdef _WIN32
indent.push_back(i==nd_child.size()-1?"+-":"|-");
#else
indent.push_back(i==nd_child.size()-1?"└─":"├─");
#endif
nd_child[i].print(depth+1,i==nd_child.size()-1,indent);
indent.pop_back();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,400 +0,0 @@
#pragma once
#include "nasal_err.h"
#include "nasal_vm.h"
#include <algorithm>
class debugger:public vm
{
private:
bool next;
usize fsize;
u16 bk_fidx;
u32 bk_line;
error& src;
std::vector<string> parse(const string&);
u16 fileindex(const string&);
void err();
void help();
void callsort(const u64*);
void stepinfo();
void interact();
public:
debugger(error& err):
next(false),fsize(0),
bk_fidx(0),bk_line(0),
src(err){}
void run(
const codegen&,
const linker&,
const std::vector<string>&
);
};
std::vector<string> debugger::parse(const string& cmd)
{
std::vector<string> res;
usize last=0,pos=cmd.find(" ",0);
while(pos!=string::npos)
{
if(pos>last)
res.push_back(cmd.substr(last,pos-last));
last=pos+1;
pos=cmd.find(" ",last);
}
if(last<cmd.length())
res.push_back(cmd.substr(last));
return res;
}
u16 debugger::fileindex(const string& filename)
{
for(u16 i=0;i<fsize;++i)
if(filename==files[i])
return i;
return 65535;
}
void debugger::err()
{
std::cerr
<<"incorrect command\n"
<<"input \'h\' to get help\n";
}
void debugger::help()
{
std::cout
<<"<option>\n"
<<"\th, help | get help\n"
<<"\tbt, backtrace | get function call trace\n"
<<"\tc, continue | run program until break point or exit\n"
<<"\tf, file | see all the compiled files\n"
<<"\tg, global | see global values\n"
<<"\tl, local | see local values\n"
<<"\tu, upval | see upvalue\n"
<<"\tr, register | show vm register detail\n"
<<"\ta, all | show global,local and upvalue\n"
<<"\tn, next | execute next bytecode\n"
<<"\tq, exit | exit debugger\n"
<<"<option> <filename> <line>\n"
<<"\tbk, break | set break point\n";
}
void debugger::callsort(const u64* arr)
{
typedef std::pair<u32,u64> op;
std::vector<op> opcall;
u64 total=0;
for(u32 i=0;i<op_ret+1;++i)
{
total+=arr[i];
opcall.push_back({i,arr[i]});
}
std::sort(opcall.begin(),opcall.end(),
[](const op& a,const op& b){return a.second>b.second;}
);
std::clog<<"\noperands call info";
for(auto& i:opcall)
{
u64 rate=i.second*100/total;
if(!rate)
{
std::clog<<"\n ...";
break;
}
std::clog<<"\n "<<code_table[i.first]
<<" : "<<i.second<<" ("<<rate<<"%)";
}
std::clog<<"\n total : "<<total<<'\n';
}
void debugger::stepinfo()
{
u32 line=bytecode[pc].line==0?0:bytecode[pc].line-1;
u32 begin=(line>>3)==0?0:((line>>3)<<3);
u32 end=(1+(line>>3))<<3;
src.load(files[bytecode[pc].fidx]);
std::cout<<"\nsource code:\n";
for(u32 i=begin;i<end && i<src.size();++i)
std::cout<<(i==line?back_white:reset)<<(i==line?"--> ":" ")<<src[i]<<reset<<"\n";
std::cout<<"next bytecode:\n";
begin=(pc>>3)==0?0:((pc>>3)<<3);
end=(1+(pc>>3))<<3;
for(u32 i=begin;i<end && bytecode[i].op!=op_exit;++i)
std::cout
<<(i==pc?back_white:reset)<<(i==pc?"--> ":" ")
<<codestream(bytecode[i],i,cnum,cstr,files)
<<reset<<"\n";
stackinfo(10);
}
void debugger::interact()
{
// special operand
if(bytecode[pc].op==op_intg)
{
std::cout
<<bold_cyan<<"[debug] "<<reset
<<"nasal debug mode\n"
<<"input \'h\' to get help\n";
}
else if(bytecode[pc].op==op_exit)
return;
if(
(bytecode[pc].fidx!=bk_fidx || bytecode[pc].line!=bk_line) && // break point
!next // next step
)return;
next=false;
string cmd;
stepinfo();
while(1)
{
std::cout<<">> ";
std::getline(std::cin,cmd);
auto res=parse(cmd);
if(res.size()==1)
{
if(res[0]=="h" || res[0]=="help")
help();
else if(res[0]=="bt" || res[0]=="backtrace")
traceback();
else if(res[0]=="c" || res[0]=="continue")
return;
else if(res[0]=="f" || res[0]=="file")
for(usize i=0;i<fsize;++i)
std::cout<<"["<<i<<"] "<<files[i]<<"\n";
else if(res[0]=="g" || res[0]=="global")
gstate();
else if(res[0]=="l" || res[0]=="local")
lstate();
else if(res[0]=="u" || res[0]=="upval")
ustate();
else if(res[0]=="r" || res[0]=="register")
reginfo();
else if(res[0]=="a" || res[0]=="all")
detail();
else if(res[0]=="n" || res[0]=="next")
{
next=true;
return;
}
else if(res[0]=="q" || res[0]=="exit")
std::exit(0);
else
err();
}
else if(res.size()==3 && (res[0]=="bk" || res[0]=="break"))
{
bk_fidx=fileindex(res[1]);
if(bk_fidx==65535)
{
std::cout<<"cannot find file named `"<<res[1]<<"`\n";
bk_fidx=0;
}
i32 tmp=atoi(res[2].c_str());
if(tmp<=0)
std::cout<<"incorrect line number `"<<res[2]<<"`\n";
else
bk_line=tmp;
}
else
err();
}
}
void debugger::run(
const codegen& gen,
const linker& linker,
const std::vector<string>& argv)
{
detail_info=true;
fsize=linker.filelist().size();
init(gen.strs(),gen.nums(),gen.codes(),linker.filelist(),argv);
u64 count[op_ret+1]={0};
#ifndef _MSC_VER
const void* oprs[]=
{
&&vmexit, &&intg, &&intl, &&loadg,
&&loadl, &&loadu, &&pnum, &&pnil,
&&pstr, &&newv, &&newh, &&newf,
&&happ, &&para, &&deft, &&dyn,
&&unot, &&usub, &&add, &&sub,
&&mul, &&div, &&lnk, &&addc,
&&subc, &&mulc, &&divc, &&lnkc,
&&addeq, &&subeq, &&muleq, &&diveq,
&&lnkeq, &&addeqc, &&subeqc, &&muleqc,
&&diveqc, &&lnkeqc, &&meq, &&eq,
&&neq, &&less, &&leq, &&grt,
&&geq, &&lessc, &&leqc, &&grtc,
&&geqc, &&pop, &&jmp, &&jt,
&&jf, &&cnt, &&findex, &&feach,
&&callg, &&calll, &&upval, &&callv,
&&callvi, &&callh, &&callfv, &&callfh,
&&callb, &&slcbeg, &&slcend, &&slc,
&&slc2, &&mcallg, &&mcalll, &&mupval,
&&mcallv, &&mcallh, &&ret
};
std::vector<const void*> code;
for(auto& i:gen.codes())
{
code.push_back(oprs[i.op]);
imm.push_back(i.num);
}
// goto the first operand
goto *code[pc];
#else
typedef void (debugger::*nafunc)();
const nafunc oprs[]=
{
nullptr, &debugger::o_intg,
&debugger::o_intl, &debugger::o_loadg,
&debugger::o_loadl, &debugger::o_loadu,
&debugger::o_pnum, &debugger::o_pnil,
&debugger::o_pstr, &debugger::o_newv,
&debugger::o_newh, &debugger::o_newf,
&debugger::o_happ, &debugger::o_para,
&debugger::o_deft, &debugger::o_dyn,
&debugger::o_unot, &debugger::o_usub,
&debugger::o_add, &debugger::o_sub,
&debugger::o_mul, &debugger::o_div,
&debugger::o_lnk, &debugger::o_addc,
&debugger::o_subc, &debugger::o_mulc,
&debugger::o_divc, &debugger::o_lnkc,
&debugger::o_addeq, &debugger::o_subeq,
&debugger::o_muleq, &debugger::o_diveq,
&debugger::o_lnkeq, &debugger::o_addeqc,
&debugger::o_subeqc, &debugger::o_muleqc,
&debugger::o_diveqc, &debugger::o_lnkeqc,
&debugger::o_meq, &debugger::o_eq,
&debugger::o_neq, &debugger::o_less,
&debugger::o_leq, &debugger::o_grt,
&debugger::o_geq, &debugger::o_lessc,
&debugger::o_leqc, &debugger::o_grtc,
&debugger::o_geqc, &debugger::o_pop,
&debugger::o_jmp, &debugger::o_jt,
&debugger::o_jf, &debugger::o_cnt,
&debugger::o_findex, &debugger::o_feach,
&debugger::o_callg, &debugger::o_calll,
&debugger::o_upval, &debugger::o_callv,
&debugger::o_callvi, &debugger::o_callh,
&debugger::o_callfv, &debugger::o_callfh,
&debugger::o_callb, &debugger::o_slcbeg,
&debugger::o_slcend, &debugger::o_slc,
&debugger::o_slc2, &debugger::o_mcallg,
&debugger::o_mcalll, &debugger::o_mupval,
&debugger::o_mcallv, &debugger::o_mcallh,
&debugger::o_ret
};
std::vector<u32> code;
for(auto& i:gen.codes())
{
code.push_back(i.op);
imm.push_back(i.num);
}
while(oprs[code[pc]]){
interact();
++count[code[pc]];
(this->*oprs[code[pc]])();
if(top>=canary)
die("stack overflow");
++pc;
}
#endif
vmexit:
callsort(count);
ngc.clear();
imm.clear();
std::cout<<bold_cyan<<"[debug] "<<reset<<"debugger exited\n";
return;
#ifndef _MSC_VER
#define dbg(op,num) {\
interact();\
op();\
++count[num];\
if(top<canary)\
goto *code[++pc];\
die("stack overflow");\
goto *code[++pc];\
}
intg: dbg(o_intg ,op_intg );
intl: dbg(o_intl ,op_intl );
loadg: dbg(o_loadg ,op_loadg );
loadl: dbg(o_loadl ,op_loadl );
loadu: dbg(o_loadu ,op_loadu );
pnum: dbg(o_pnum ,op_pnum );
pnil: dbg(o_pnil ,op_pnil );
pstr: dbg(o_pstr ,op_pstr );
newv: dbg(o_newv ,op_newv );
newh: dbg(o_newh ,op_newh );
newf: dbg(o_newf ,op_newf );
happ: dbg(o_happ ,op_happ );
para: dbg(o_para ,op_para );
deft: dbg(o_deft ,op_deft );
dyn: dbg(o_dyn ,op_dyn );
unot: dbg(o_unot ,op_unot );
usub: dbg(o_usub ,op_usub );
add: dbg(o_add ,op_add );
sub: dbg(o_sub ,op_sub );
mul: dbg(o_mul ,op_mul );
div: dbg(o_div ,op_div );
lnk: dbg(o_lnk ,op_lnk );
addc: dbg(o_addc ,op_addc );
subc: dbg(o_subc ,op_subc );
mulc: dbg(o_mulc ,op_mulc );
divc: dbg(o_divc ,op_divc );
lnkc: dbg(o_lnkc ,op_lnkc );
addeq: dbg(o_addeq ,op_addeq );
subeq: dbg(o_subeq ,op_subeq );
muleq: dbg(o_muleq ,op_muleq );
diveq: dbg(o_diveq ,op_diveq );
lnkeq: dbg(o_lnkeq ,op_lnkeq );
addeqc: dbg(o_addeqc,op_addeqc);
subeqc: dbg(o_subeqc,op_subeqc);
muleqc: dbg(o_muleqc,op_muleqc);
diveqc: dbg(o_diveqc,op_diveqc);
lnkeqc: dbg(o_lnkeqc,op_lnkeqc);
meq: dbg(o_meq ,op_meq );
eq: dbg(o_eq ,op_eq );
neq: dbg(o_neq ,op_neq );
less: dbg(o_less ,op_less );
leq: dbg(o_leq ,op_leq );
grt: dbg(o_grt ,op_grt );
geq: dbg(o_geq ,op_geq );
lessc: dbg(o_lessc ,op_lessc );
leqc: dbg(o_leqc ,op_leqc );
grtc: dbg(o_grtc ,op_grtc );
geqc: dbg(o_geqc ,op_geqc );
pop: dbg(o_pop ,op_pop );
jmp: dbg(o_jmp ,op_jmp );
jt: dbg(o_jt ,op_jt );
jf: dbg(o_jf ,op_jf );
cnt: dbg(o_cnt ,op_cnt );
findex: dbg(o_findex,op_findex);
feach: dbg(o_feach ,op_feach );
callg: dbg(o_callg ,op_callg );
calll: dbg(o_calll ,op_calll );
upval: dbg(o_upval ,op_upval );
callv: dbg(o_callv ,op_callv );
callvi: dbg(o_callvi,op_callvi);
callh: dbg(o_callh ,op_callh );
callfv: dbg(o_callfv,op_callfv);
callfh: dbg(o_callfh,op_callfh);
callb: dbg(o_callb ,op_callb );
slcbeg: dbg(o_slcbeg,op_slcbeg);
slcend: dbg(o_slcend,op_slcend);
slc: dbg(o_slc ,op_slc );
slc2: dbg(o_slc2 ,op_slc2 );
mcallg: dbg(o_mcallg,op_mcallg);
mcalll: dbg(o_mcalll,op_mcalll);
mupval: dbg(o_mupval,op_mupval);
mcallv: dbg(o_mcallv,op_mcallv);
mcallh: dbg(o_mcallh,op_mcallh);
ret: dbg(o_ret ,op_ret );
#endif
}

View File

@@ -1,160 +0,0 @@
#pragma once
#include <iostream>
#include <fstream>
#include <sstream> // MSVC need this to use std::getline
#include <cstring>
#ifdef _WIN32
#include <windows.h> // use SetConsoleTextAttribute
struct for_reset
{
CONSOLE_SCREEN_BUFFER_INFO scr;
for_reset(){
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&scr);
}
}reset_ter_color;
#endif
std::ostream& back_white(std::ostream& s)
{
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0xf0);
#else
s<<"\033[7m";
#endif
return s;
}
std::ostream& bold_red(std::ostream& s)
{
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0c);
#else
s<<"\033[91;1m";
#endif
return s;
}
std::ostream& bold_cyan(std::ostream& s)
{
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x03);
#else
s<<"\033[36;1m";
#endif
return s;
}
std::ostream& bold_orange(std::ostream& s)
{
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0e);
#else
s<<"\033[93;1m";
#endif
return s;
}
std::ostream& bold_white(std::ostream& s)
{
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x0f);
#else
s<<"\033[0m\033[1m";
#endif
return s;
}
std::ostream& reset(std::ostream& s)
{
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),reset_ter_color.scr.wAttributes);
#else
s<<"\033[0m";
#endif
return s;
}
class fstreamline
{
protected:
string file;
std::vector<string> res;
public:
void load(const string& f)
{
if(file==f) return; // don't need to load a loaded file
file=f;
res.clear();
std::ifstream in(f,std::ios::binary);
if(in.fail())
{
std::cerr<<bold_red<<"src: "<<reset<<"cannot open <"<<f<<">\n";
std::exit(1);
}
string line;
while(!in.eof())
{
std::getline(in,line);
res.push_back(line);
}
}
const string& operator[](usize n){return res[n];}
const string& name(){return file;}
usize size(){return res.size();}
};
class error:public fstreamline
{
private:
u32 cnt;
string identation(usize len)
{
string tmp="";
tmp.resize(len,' ');
return tmp;
}
public:
error():cnt(0){}
void err(const char* stage,const string& info)
{
++cnt;
std::cerr<<bold_red<<stage<<": "
<<bold_white<<info<<reset<<"\n\n";
}
void err(const char* stage,u32 line,u32 column,const string& info)
{
++cnt;
const string& code=res[line-1];
const string ident=identation(std::to_string(line).length());
std::cerr<<bold_red<<stage<<": "
<<bold_white<<info<<reset<<"\n"
<<bold_cyan<<" --> "<<reset
<<bold_orange<<file<<":"<<line<<":"<<column<<"\n";
if(!line)
{
std::cerr<<"\n";
return;
}
std::cerr<<bold_cyan<<ident<<" | "<<reset<<"\n"
<<bold_cyan<<line<<" | "<<reset<<code<<"\n"
<<bold_cyan<<ident<<" | "<<reset;
for(i32 i=0;i<(i32)column-1;++i)
std::cerr<<char(" \t"[code[i]=='\t']);
std::cerr<<bold_red<<"^ "<<info<<reset<<"\n\n";
}
void err(const char* stage,u32 line,const string& info)
{
++cnt;
const string ident=identation(std::to_string(line).length());
std::cerr<<bold_red<<stage<<": "
<<bold_white<<info<<reset<<"\n"
<<bold_cyan<<" --> "<<reset
<<bold_orange<<file<<":"<<line<<"\n";
if(!line)
{
std::cerr<<"\n";
return;
}
std::cerr<<bold_cyan<<ident<<" | "<<reset<<"\n"
<<bold_cyan<<line<<" | "<<reset<<res[line-1]<<"\n"
<<bold_cyan<<ident<<" | "<<reset<<"\n\n";
}
void chkerr(){if(cnt)std::exit(1);}
};

View File

@@ -1,726 +0,0 @@
#pragma once
#include <vector>
#include <queue>
#include <unordered_map>
#include "nasal_err.h"
enum vm_type:u8{
/* none-gc object */
vm_none=0,
vm_cnt,
vm_addr,
vm_ret,
vm_nil,
vm_num,
/* gc object */
vm_str,
vm_vec,
vm_hash,
vm_func,
vm_upval,
vm_obj,
vm_co
};
const u32 gc_tsize=vm_co-vm_str+1;
// change parameters here to make your own efficient gc
// better set bigger number on vm_vec
const u32 ini[gc_tsize]=
{
128, // vm_str
128, // vm_vec
32, // vm_hash
128, // vm_func
0, // vm_upval
0, // vm_obj
0 // vm_co
};
const u32 incr[gc_tsize]=
{
1024,// vm_str
512, // vm_vec
512, // vm_hash
512, // vm_func
512, // vm_upval
128, // vm_obj
32 // vm_co
};
struct nas_vec; // vector
struct nas_hash; // hashmap(dict)
struct nas_func; // function(lambda)
struct nas_upval;// upvalue
struct nas_obj; // special objects
struct nas_co; // coroutine
struct nas_val; // nas_val includes gc-managed types
struct var
{
u8 type;
union
{
u32 ret;
i64 cnt;
f64 num;
var* addr;
nas_val* gcobj;
} val;
// vm_none/vm_nil
var(const u8 t=vm_none):type(t){}
// vm_ret
var(const u8 t,const u32 n):type(t){val.ret=n;}
// vm_cnt
var(const u8 t,const i64 n):type(t){val.cnt=n;}
// vm_num
var(const u8 t,const f64 n):type(t){val.num=n;}
// nas_val
var(const u8 t,nas_val* n):type(t){val.gcobj=n;}
// vm_addr
var(const u8 t,var* n):type(t){val.addr=n;}
// copy
var(const var& nr):type(nr.type),val(nr.val){}
bool operator==(const var& nr){return type==nr.type && val.gcobj==nr.val.gcobj;}
bool operator!=(const var& nr){return type!=nr.type || val.gcobj!=nr.val.gcobj;}
// number and string can be translated to each other
f64 tonum();
string tostr();
friend std::ostream& operator<<(std::ostream&,var&);
bool objchk(u32);
inline var* addr();
inline u32 ret ();
inline i64& cnt ();
inline f64 num ();
inline string& str ();
inline nas_vec& vec ();
inline nas_hash& hash();
inline nas_func& func();
inline nas_upval& upval();
inline nas_obj& obj ();
inline nas_co& co ();
};
struct nas_vec
{
bool printed;
std::vector<var> elems;
nas_vec():printed(false){}
friend std::ostream& operator<<(std::ostream&,nas_vec&);
usize size(){return elems.size();}
var get_val(const i32);
var* get_mem(const i32);
};
struct nas_hash
{
bool printed;
std::unordered_map<string,var> elems;
nas_hash():printed(false){}
friend std::ostream& operator<<(std::ostream&,nas_hash&);
usize size(){return elems.size();}
var get_val(const string&);
var* get_mem(const string&);
};
struct nas_func
{
i32 dpara; // dynamic parameter name index in hash.
u32 entry; // pc will set to entry-1 to call this function
u32 psize; // used to load default parameters to a new function
u32 lsize; // used to expand memory space for local values on stack
std::vector<var> local; // local scope with default value(var)
std::vector<var> upval; // closure
std::unordered_map<u32,u32> keys; // parameter table, u32 begins from 1
nas_func():dpara(-1),entry(0),psize(0),lsize(0){}
void clear();
};
struct nas_upval
{
bool onstk;
u32 size;
var* stk;
std::vector<var> elems;
nas_upval(){onstk=true;stk=nullptr;size=0;}
var& operator[](usize n){return onstk?stk[n]:elems[n];}
void clear(){onstk=true;elems.clear();size=0;}
};
struct nas_obj
{
enum obj_t:u32
{
null,
file,
dir,
dylib,
faddr
};
/* RAII constructor */
/* new object is initialized when creating */
u32 type;
void* ptr;
/* RAII destroyer */
/* default destroyer does nothing */
typedef void (*dest)(void*);
dest dtor;
nas_obj():type(obj_t::null),ptr(nullptr),dtor(nullptr){}
~nas_obj(){clear();}
void set(u32 t=obj_t::null,void* p=nullptr,dest d=nullptr)
{
type=t;
ptr=p;
dtor=d;
}
void clear()
{
if(dtor && ptr)
dtor(ptr);
ptr=nullptr;
dtor=nullptr;
}
};
struct nas_co
{
enum costat:u32
{
suspended,
running,
dead
};
var stack[STACK_DEPTH];
u32 pc;
var* top;
var* canary;
var* localr;
var* memr;
var funcr;
var upvalr;
u32 status;
nas_co():
pc(0),top(stack),
canary(stack+STACK_DEPTH-1),
localr(nullptr),
memr(nullptr),
funcr({vm_nil,(f64)0}),
upvalr({vm_nil,(f64)0}),
status(nas_co::suspended)
{
for(u32 i=0;i<STACK_DEPTH;++i)
stack[i]={vm_nil,(f64)0};
}
void clear()
{
for(u32 i=0;i<STACK_DEPTH;++i)
stack[i]={vm_nil,(f64)0};
pc=0;
localr=nullptr;
memr=nullptr;
top=stack;
status=nas_co::suspended;
funcr={vm_nil,(f64)0};
}
};
const u8 GC_UNCOLLECTED=0;
const u8 GC_COLLECTED =1;
const u8 GC_FOUND =2;
struct nas_val
{
u8 mark;
u8 type;
u8 unmut; // used to mark if a string is unmutable
union
{
string* str;
nas_vec* vec;
nas_hash* hash;
nas_func* func;
nas_upval* upval;
nas_obj* obj;
nas_co* co;
} ptr;
nas_val(u8);
~nas_val();
void clear();
};
var nas_vec::get_val(const i32 n)
{
i32 size=elems.size();
if(n<-size || n>=size)
return {vm_none};
return elems[n>=0?n:n+size];
}
var* nas_vec::get_mem(const i32 n)
{
i32 size=elems.size();
if(n<-size || n>=size)
return nullptr;
return &elems[n>=0?n:n+size];
}
std::ostream& operator<<(std::ostream& out,nas_vec& vec)
{
if(!vec.elems.size() || vec.printed)
{
out<<(vec.elems.size()?"[..]":"[]");
return out;
}
vec.printed=true;
usize iter=0,size=vec.elems.size();
out<<'[';
for(auto& i:vec.elems)
out<<i<<",]"[(++iter)==size];
vec.printed=false;
return out;
}
var nas_hash::get_val(const string& key)
{
if(elems.count(key))
return elems[key];
else if(elems.count("parents"))
{
var ret(vm_none);
var val=elems["parents"];
if(val.type==vm_vec)
for(auto& i:val.vec().elems)
{
if(i.type==vm_hash)
ret=i.hash().get_val(key);
if(ret.type!=vm_none)
return ret;
}
}
return {vm_none};
}
var* nas_hash::get_mem(const string& key)
{
if(elems.count(key))
return &elems[key];
else if(elems.count("parents"))
{
var* addr=nullptr;
var val=elems["parents"];
if(val.type==vm_vec)
for(auto& i:val.vec().elems)
{
if(i.type==vm_hash)
addr=i.hash().get_mem(key);
if(addr)
return addr;
}
}
return nullptr;
}
std::ostream& operator<<(std::ostream& out,nas_hash& hash)
{
if(!hash.elems.size() || hash.printed)
{
out<<(hash.elems.size()?"{..}":"{}");
return out;
}
hash.printed=true;
usize iter=0,size=hash.elems.size();
out<<'{';
for(auto& i:hash.elems)
out<<i.first<<':'<<i.second<<",}"[(++iter)==size];
hash.printed=false;
return out;
}
void nas_func::clear()
{
dpara=-1;
local.clear();
upval.clear();
keys.clear();
}
nas_val::nas_val(u8 val_type)
{
mark=GC_COLLECTED;
type=val_type;
unmut=0;
switch(val_type)
{
case vm_str: ptr.str=new string; break;
case vm_vec: ptr.vec=new nas_vec; break;
case vm_hash: ptr.hash=new nas_hash; break;
case vm_func: ptr.func=new nas_func; break;
case vm_upval:ptr.upval=new nas_upval;break;
case vm_obj: ptr.obj=new nas_obj; break;
case vm_co: ptr.co=new nas_co; break;
}
}
nas_val::~nas_val()
{
switch(type)
{
case vm_str: delete ptr.str; break;
case vm_vec: delete ptr.vec; break;
case vm_hash: delete ptr.hash; break;
case vm_func: delete ptr.func; break;
case vm_upval:delete ptr.upval;break;
case vm_obj: delete ptr.obj; break;
case vm_co: delete ptr.co; break;
}
type=vm_nil;
}
void nas_val::clear()
{
switch(type)
{
case vm_str: ptr.str->clear(); break;
case vm_vec: ptr.vec->elems.clear(); break;
case vm_hash: ptr.hash->elems.clear();break;
case vm_func: ptr.func->clear(); break;
case vm_upval:ptr.upval->clear(); break;
case vm_obj: ptr.obj->clear(); break;
case vm_co: ptr.co->clear(); break;
}
}
f64 var::tonum()
{
return type!=vm_str?val.num:str2num(str().c_str());
}
string var::tostr()
{
if(type==vm_str)
return str();
else if(type==vm_num)
{
string tmp=std::to_string(num());
tmp.erase(tmp.find_last_not_of('0')+1,string::npos);
tmp.erase(tmp.find_last_not_of('.')+1,string::npos);
return tmp;
}
return "";
}
std::ostream& operator<<(std::ostream& out,var& ref)
{
switch(ref.type)
{
case vm_none: out<<"undefined"; break;
case vm_nil: out<<"nil"; break;
case vm_num: out<<ref.val.num; break;
case vm_str: out<<ref.str(); break;
case vm_vec: out<<ref.vec(); break;
case vm_hash: out<<ref.hash(); break;
case vm_func: out<<"func(..){..}";break;
case vm_obj: out<<"<object>"; break;
case vm_co: out<<"<coroutine>"; break;
}
return out;
}
bool var::objchk(u32 objtype)
{
return type==vm_obj && obj().type==objtype && obj().ptr;
}
inline var* var::addr (){return val.addr; }
inline u32 var::ret (){return val.ret; }
inline i64& var::cnt (){return val.cnt; }
inline f64 var::num (){return val.num; }
inline string& var::str (){return *val.gcobj->ptr.str; }
inline nas_vec& var::vec (){return *val.gcobj->ptr.vec; }
inline nas_hash& var::hash (){return *val.gcobj->ptr.hash; }
inline nas_func& var::func (){return *val.gcobj->ptr.func; }
inline nas_upval& var::upval(){return *val.gcobj->ptr.upval;}
inline nas_obj& var::obj (){return *val.gcobj->ptr.obj; }
inline nas_co& var::co (){return *val.gcobj->ptr.co; }
const var zero={vm_num,(f64)0};
const var one ={vm_num,(f64)1};
const var nil ={vm_nil,(f64)0};
struct gc
{
/* main context */
struct
{
u32 pc;
var* top;
var* localr;
var* memr;
var funcr;
var upvalr;
var* canary;
var* stack;
} mctx;
/* runtime context */
u32& pc; // program counter
var*& localr; // local scope register
var*& memr; // used for mem_call
var& funcr; // function register
var& upvalr; // upvalue register
var*& canary; // avoid stackoverflow
var*& top; // stack top
var* stack; // stack pointer
nas_co* cort; // running coroutine
/* native function used */
var temp; // temporary place used in builtin/module functions
/* constants and memory pool */
std::vector<var> strs; // reserved address for const vm_str
std::vector<var> env_argv; // command line arguments
std::vector<nas_val*> memory; // gc memory
std::queue<nas_val*> unused[gc_tsize]; // gc free list
/* values for analysis */
u64 size[gc_tsize];
u64 count[gc_tsize];
u64 acnt[gc_tsize];
gc(u32& _pc, var*& _localr, var*& _memr, var& _funcr,
var& _upvalr, var*& _canary, var*& _top, var* _stk):
pc(_pc),localr(_localr),memr(_memr),funcr(_funcr),upvalr(_upvalr),
canary(_canary),top(_top),stack(_stk),cort(nullptr),temp(nil){}
void mark();
void sweep();
void init(const std::vector<string>&,const std::vector<string>&);
void clear();
void info();
var alloc(const u8);
var newstr(char);
var newstr(const char*);
var newstr(const string&);
void ctxchg(nas_co&);
void ctxreserve();
};
/* gc functions */
void gc::mark()
{
std::queue<var> bfs;
// scan coroutine process stack when coroutine ptr is not null
// scan main process stack when coroutine ptr is null
// this scan process must execute because when running coroutine,
// the nas_co related to it will not update it's context(like `top`) until the coroutine suspends or exits.
for(var* i=stack;i<=top;++i)
bfs.push(*i);
bfs.push(funcr);
bfs.push(upvalr);
bfs.push(temp);
if(cort) // scan main process stack
{
for(var* i=mctx.stack;i<=mctx.top;++i)
bfs.push(*i);
bfs.push(mctx.funcr);
bfs.push(mctx.upvalr);
}
while(!bfs.empty())
{
var tmp=bfs.front();
bfs.pop();
if(tmp.type<=vm_num || tmp.val.gcobj->mark) continue;
tmp.val.gcobj->mark=GC_FOUND;
switch(tmp.type)
{
case vm_vec:
for(auto& i:tmp.vec().elems)
bfs.push(i);
break;
case vm_hash:
for(auto& i:tmp.hash().elems)
bfs.push(i.second);
break;
case vm_func:
for(auto& i:tmp.func().local)
bfs.push(i);
for(auto& i:tmp.func().upval)
bfs.push(i);
break;
case vm_upval:
for(auto& i:tmp.upval().elems)
bfs.push(i);
break;
case vm_co:
bfs.push(tmp.co().funcr);
bfs.push(tmp.co().upvalr);
for(var* i=tmp.co().stack;i<=tmp.co().top;++i)
bfs.push(*i);
break;
}
}
}
void gc::sweep()
{
for(auto i:memory)
{
if(i->mark==GC_UNCOLLECTED)
{
i->clear();
unused[i->type-vm_str].push(i);
i->mark=GC_COLLECTED;
}
else if(i->mark==GC_FOUND)
i->mark=GC_UNCOLLECTED;
}
}
void gc::init(const std::vector<string>& s,const std::vector<string>& argv)
{
// initiaize function register
funcr=nil;
for(u8 i=0;i<gc_tsize;++i)
size[i]=count[i]=acnt[i]=0;
for(u8 i=0;i<gc_tsize;++i)
for(u32 j=0;j<ini[i];++j)
{
nas_val* tmp=new nas_val(i+vm_str);
memory.push_back(tmp);
unused[i].push(tmp);
}
cort=nullptr;
// init constant strings
strs.resize(s.size());
for(u32 i=0;i<strs.size();++i)
{
strs[i]={vm_str,new nas_val(vm_str)};
strs[i].val.gcobj->unmut=1;
strs[i].str()=s[i];
}
// record arguments
env_argv.resize(argv.size());
for(usize i=0;i<argv.size();++i)
{
env_argv[i]={vm_str,new nas_val(vm_str)};
env_argv[i].val.gcobj->unmut=1;
env_argv[i].str()=argv[i];
}
}
void gc::clear()
{
for(auto i:memory)
delete i;
memory.clear();
for(u8 i=0;i<gc_tsize;++i)
while(!unused[i].empty())
unused[i].pop();
for(auto& i:strs)
delete i.val.gcobj;
strs.clear();
env_argv.clear();
}
void gc::info()
{
const char* name[]={"str ","vec ","hash ","func ","upval","obj ","co "};
std::cout<<"\ngarbage collector info(gc/alloc)\n";
for(u8 i=0;i<gc_tsize;++i)
if(count[i] || acnt[i])
std::cout<<" "<<name[i]<<" | "<<count[i]<<","<<acnt[i]<<"\n";
std::cout<<"\nmemory allocator info(max size)\n";
for(u8 i=0;i<gc_tsize;++i)
if(ini[i] || size[i])
std::cout<<" "<<name[i]<<" | "<<ini[i]+size[i]*incr[i]<<" (+"<<size[i]<<")\n";
}
var gc::alloc(u8 type)
{
const u8 index=type-vm_str;
++acnt[index];
if(unused[index].empty())
{
++count[index];
mark();
sweep();
}
if(unused[index].empty())
{
++size[index];
for(u32 i=0;i<incr[index];++i)
{
nas_val* tmp=new nas_val(type);
memory.push_back(tmp);
unused[index].push(tmp);
}
}
var ret={type,unused[index].front()};
ret.val.gcobj->mark=GC_UNCOLLECTED;
unused[index].pop();
return ret;
}
var gc::newstr(char c)
{
var s=alloc(vm_str);
s.str()=c;
return s;
}
var gc::newstr(const char* buff)
{
var s=alloc(vm_str);
s.str()=buff;
return s;
}
var gc::newstr(const string& buff)
{
var s=alloc(vm_str);
s.str()=buff;
return s;
}
void gc::ctxchg(nas_co& ctx)
{
mctx.pc=pc;
mctx.top=top;
mctx.localr=localr;
mctx.memr=memr;
mctx.funcr=funcr;
mctx.upvalr=upvalr;
mctx.canary=canary;
mctx.stack=stack;
pc=ctx.pc;
top=ctx.top;
localr=ctx.localr;
memr=ctx.memr;
funcr=ctx.funcr;
upvalr=ctx.upvalr;
canary=ctx.canary;
stack=ctx.stack;
cort=&ctx;
cort->status=nas_co::running;
}
void gc::ctxreserve()
{
// pc=0 means this coroutine is finished
cort->status=pc?nas_co::suspended:nas_co::dead;
cort->pc=pc;
cort->localr=localr;
cort->memr=memr;
cort->funcr=funcr;
cort->upvalr=upvalr;
cort->canary=canary;
cort->top=top;
pc=mctx.pc;
localr=mctx.localr;
memr=mctx.memr;
funcr=mctx.funcr;
upvalr=mctx.upvalr;
canary=mctx.canary;
top=mctx.top;
stack=mctx.stack;
cort=nullptr;
}
// use to print error log and return error value
var nas_err(const string& err_f,const string& info)
{
std::cerr<<"[vm] "<<err_f<<": "<<info<<"\n";
return {vm_none};
}
typedef var (*mod)(var*,usize,gc*); // module function type
typedef mod (*getptr)(const char*); // module function "get" type

View File

@@ -1,225 +0,0 @@
#pragma once
#ifndef _MSC_VER
#include <unistd.h>
#else
#define _CRT_SECURE_NO_DEPRECATE 1
#define _CRT_NONSTDC_NO_DEPRECATE 1
#include <io.h>
#endif
#ifdef _MSC_VER
#define F_OK 0
#endif
class linker
{
private:
bool show_path;
bool lib_loaded;
error& err;
std::vector<string> files;
std::vector<string> envpath;
bool imptchk(const ast&);
bool exist(const string&);
void link(ast&,ast&&);
string path(const ast&);
string findf(const string&);
ast fimpt(ast&);
ast libimpt();
ast load(ast&,u16);
public:
linker(error&);
void link(parse&,const string&,bool);
const std::vector<string>& filelist() const {return files;}
};
linker::linker(error& e):show_path(false),lib_loaded(false),err(e){
#ifdef _WIN32
char sep=';';
#else
char sep=':';
#endif
string PATH=getenv("PATH");
usize last=0,pos=PATH.find(sep,0);
while(pos!=string::npos)
{
string dirpath=PATH.substr(last,pos-last);
if(dirpath.length())
envpath.push_back(dirpath);
last=pos+1;
pos=PATH.find(sep,last);
}
if(last!=PATH.length())
envpath.push_back(PATH.substr(last));
}
string linker::path(const ast& node)
{
if(node[1].type()==ast_callf)
return node[1][0].str();
string fpath=".";
for(usize i=1;i<node.size();++i)
#ifndef _WIN32
fpath+="/"+node[i].str();
#else
fpath+="\\"+node[i].str();
#endif
return fpath+".nas";
}
string linker::findf(const string& fname)
{
std::vector<string> filepath={fname};
for(auto&p:envpath)
{
#ifdef _WIN32
filepath.push_back(p+"\\"+fname);
#else
filepath.push_back(p+"/"+fname);
#endif
}
for(auto& i:filepath)
if(access(i.c_str(),F_OK)!=-1)
return i;
if(fname=="lib.nas")
#ifdef _WIN32
return findf("stl\\lib.nas");
#else
return findf("stl/lib.nas");
#endif
if(!show_path)
{
err.err("link","cannot find file <"+fname+">");
return "";
}
string paths="";
for(auto& i:filepath)
paths+=" "+i+"\n";
err.err("link","cannot find file <"+fname+"> in these paths:\n"+paths);
return "";
}
bool linker::imptchk(const ast& node)
{
// only these two kinds of node can be recognized as 'import':
/*
call
|_id:import
|_callh:stl
|_callh:file
*/
if(node.type()==ast_call && node[0].str()=="import" && node.size()>=2 && node[1].type()==ast_callh)
{
for(usize i=1;i<node.size();++i)
if(node[i].type()!=ast_callh)
return false;
return true;
}
/*
call
|_id:import
|_call_func
|_string:'filename'
*/
return (
node.type()==ast_call &&
node[0].str()=="import" &&
node.size()==2 &&
node[1].type()==ast_callf &&
node[1].size()==1 &&
node[1][0].type()==ast_str
);
}
bool linker::exist(const string& file)
{
// avoid importing the same file
for(auto& fname:files)
if(file==fname)
return true;
files.push_back(file);
return false;
}
void linker::link(ast& root,ast&& add_root)
{
// add children of add_root to the back of root
for(auto& i:add_root.child())
root.add(std::move(i));
}
ast linker::fimpt(ast& node)
{
lexer lex(err);
parse par(err);
// get filename and set node to ast_null
string filename=path(node);
node.clear();
// avoid infinite loading loop
filename=findf(filename);
if(!filename.length() || exist(filename))
return {0,0,ast_root};
// start importing...
lex.scan(filename);
par.compile(lex);
ast tmp=std::move(par.tree());
// check if tmp has 'import'
return load(tmp,files.size()-1);
}
ast linker::libimpt()
{
lexer lex(err);
parse par(err);
string filename=findf("lib.nas");
if(!filename.length())
return {0,0,ast_root};
// avoid infinite loading loop
if(exist(filename))
return {0,0,ast_root};
// start importing...
lex.scan(filename);
par.compile(lex);
ast tmp=std::move(par.tree());
// check if tmp has 'import'
return load(tmp,files.size()-1);
}
ast linker::load(ast& root,u16 fileindex)
{
ast new_root(0,0,ast_root);
if(!lib_loaded)
{
link(new_root,libimpt());
lib_loaded=true;
}
for(auto& i:root.child())
{
if(imptchk(i))
link(new_root,fimpt(i));
else
break;
}
// add root to the back of new_root
ast file_head(0,0,ast_file);
file_head.set_num(fileindex);
new_root.add(std::move(file_head));
link(new_root,std::move(root));
return new_root;
}
void linker::link(parse& parse,const string& self,bool spath=false)
{
show_path=spath;
// initializing
files={self};
// scan root and import files,then generate a new ast and return to import_ast
// the main file's index is 0
parse.tree()=load(parse.tree(),0);
err.chkerr();
}

View File

@@ -1,476 +0,0 @@
#pragma once
#include <sstream>
#include <sys/stat.h>
#ifdef _MSC_VER
#define S_ISREG(m) (((m)&0xF000)==0x8000)
#endif
enum tok:u32{
tok_null=0, // null token (default token type)
tok_num, // number literal
tok_str, // string literal
tok_id, // identifier
tok_for, // loop keyword for
tok_forindex,// loop keyword forindex
tok_foreach, // loop keyword foreach
tok_while, // loop keyword while
tok_var, // keyword for definition
tok_func, // keyword for definition of function
tok_break, // loop keyword break
tok_continue,// loop keyword continue
tok_ret, // function keyword return
tok_if, // condition expression keyword if
tok_elsif, // condition expression keyword elsif
tok_else, // condition expression keyword else
tok_nil, // nil literal
tok_lcurve, // (
tok_rcurve, // )
tok_lbracket,// [
tok_rbracket,// ]
tok_lbrace, // {
tok_rbrace, // }
tok_semi, // ;
tok_and, // operator and
tok_or, // operator or
tok_comma, // ,
tok_dot, // .
tok_ellipsis,// ...
tok_quesmark,// ?
tok_colon, // :
tok_add, // operator +
tok_sub, // operator -
tok_mult, // operator *
tok_div, // operator /
tok_link, // operator ~
tok_not, // operator !
tok_eq, // operator =
tok_addeq, // operator +=
tok_subeq, // operator -=
tok_multeq, // operator *=
tok_diveq, // operator /=
tok_lnkeq, // operator ~=
tok_cmpeq, // operator ==
tok_neq, // operator !=
tok_less, // operator <
tok_leq, // operator <=
tok_grt, // operator >
tok_geq, // operator >=
tok_eof // <eof> end of token list
};
struct{
const char* str;
const u32 type;
}tok_table[]={
{"for" ,tok_for },
{"forindex",tok_forindex },
{"foreach" ,tok_foreach },
{"while" ,tok_while },
{"var" ,tok_var },
{"func" ,tok_func },
{"break" ,tok_break },
{"continue",tok_continue },
{"return" ,tok_ret },
{"if" ,tok_if },
{"elsif" ,tok_elsif },
{"else" ,tok_else },
{"nil" ,tok_nil },
{"(" ,tok_lcurve },
{")" ,tok_rcurve },
{"[" ,tok_lbracket },
{"]" ,tok_rbracket },
{"{" ,tok_lbrace },
{"}" ,tok_rbrace },
{";" ,tok_semi },
{"and" ,tok_and },
{"or" ,tok_or },
{"," ,tok_comma },
{"." ,tok_dot },
{"..." ,tok_ellipsis },
{"?" ,tok_quesmark },
{":" ,tok_colon },
{"+" ,tok_add },
{"-" ,tok_sub },
{"*" ,tok_mult },
{"/" ,tok_div },
{"~" ,tok_link },
{"!" ,tok_not },
{"=" ,tok_eq },
{"+=" ,tok_addeq },
{"-=" ,tok_subeq },
{"*=" ,tok_multeq },
{"/=" ,tok_diveq },
{"~=" ,tok_lnkeq },
{"==" ,tok_cmpeq },
{"!=" ,tok_neq },
{"<" ,tok_less },
{"<=" ,tok_leq },
{">" ,tok_grt },
{">=" ,tok_geq },
{nullptr ,0 }
};
struct token
{
u32 line;
u32 col;
u32 type;
string str;
token(u32 l=0,u32 c=0,u32 t=tok_null,const string& s=""):str(s)
{
line=l;
col=c;
type=t;
}
};
class lexer
{
private:
u32 line;
u32 column;
usize ptr;
string res;
error& err;
std::vector<token> tokens;
u32 get_type(const string&);
bool is_id(char);
bool is_hex(char);
bool is_oct(char);
bool is_dec(char);
bool is_str(char);
bool is_single_opr(char);
bool is_calc_opr(char);
void die(const string& info){err.err("lexer",line,column,info);}
void open(const string&);
string utf8_gen();
string id_gen();
string num_gen();
string str_gen();
public:
lexer(error& e):
line(1),column(0),
ptr(0),res(""),
err(e){}
void scan(const string&);
void print();
const std::vector<token>& result() const {return tokens;}
};
bool lexer::is_id(char c)
{
return (c=='_')||('a'<=c && c<='z')||('A'<=c&&c<='Z')||(c<0);
}
bool lexer::is_hex(char c)
{
return ('0'<=c&&c<='9')||('a'<=c&&c<='f')||('A'<=c && c<='F');
}
bool lexer::is_oct(char c)
{
return '0'<=c&&c<='7';
}
bool lexer::is_dec(char c)
{
return '0'<=c&&c<='9';
}
bool lexer::is_str(char c)
{
return c=='\''||c=='\"'||c=='`';
}
bool lexer::is_single_opr(char c)
{
return (
c=='('||c==')'||c=='['||c==']'||
c=='{'||c=='}'||c==','||c==';'||
c=='|'||c==':'||c=='?'||c=='`'||
c=='&'||c=='@'||c=='%'||c=='$'||
c=='^'||c=='\\'
);
}
bool lexer::is_calc_opr(char c)
{
return c=='='||c=='+'||c=='-'||c=='*'||c=='!'||c=='/'||c=='<'||c=='>'||c=='~';
}
void lexer::open(const string& file)
{
struct stat buffer;
if(stat(file.c_str(),&buffer)==0 && !S_ISREG(buffer.st_mode))
{
err.err("lexer","<"+file+"> is not a regular file");
err.chkerr();
}
std::ifstream fin(file,std::ios::binary);
if(fin.fail())
err.err("lexer","failed to open <"+file+">");
else
err.load(file);
std::stringstream ss;
ss<<fin.rdbuf();
res=ss.str();
}
u32 lexer::get_type(const string& str)
{
for(u32 i=0;tok_table[i].str;++i)
if(str==tok_table[i].str)
return tok_table[i].type;
return tok_null;
}
string lexer::utf8_gen()
{
string str="";
while(ptr<res.size() && res[ptr]<0)
{
string tmp="";
u32 nbytes=utf8_hdchk(res[ptr]);
if(nbytes)
{
tmp+=res[ptr++];
for(u32 i=0;i<nbytes;++i,++ptr)
if(ptr<res.size() && (res[ptr]&0xc0)==0x80)
tmp+=res[ptr];
if(tmp.length()!=1+nbytes)
{
++column;
string utf_info="0x"+chrhex(tmp[0]);
for(u32 i=1;i<tmp.size();++i)
utf_info+=" 0x"+chrhex(tmp[i]);
die("invalid utf-8 character `"+utf_info+"`, make sure it is utf8-text file");
std::exit(1);
}
str+=tmp;
column+=2; // may have some problems because not all the unicode takes 2 space
}
else
{
++ptr;
++column;
}
}
return str;
}
string lexer::id_gen()
{
string str="";
while(ptr<res.size() && (is_id(res[ptr])||is_dec(res[ptr])))
{
if(res[ptr]<0) // utf-8
str+=utf8_gen();
else // ascii
{
str+=res[ptr++];
++column;
}
}
return str;
}
string lexer::num_gen()
{
// generate hex number
if(ptr+1<res.size() && res[ptr]=='0' && res[ptr+1]=='x')
{
string str="0x";
ptr+=2;
while(ptr<res.size() && is_hex(res[ptr]))
str+=res[ptr++];
column+=str.length();
if(str.length()<3)// "0x"
die("invalid number `"+str+"`");
return str;
}
// generate oct number
else if(ptr+1<res.size() && res[ptr]=='0' && res[ptr+1]=='o')
{
string str="0o";
ptr+=2;
while(ptr<res.size() && is_oct(res[ptr]))
str+=res[ptr++];
column+=str.length();
if(str.length()<3)// "0o"
die("invalid number `"+str+"`");
return str;
}
// generate dec number
// dec number -> [0~9][0~9]*(.[0~9]*)(e|E(+|-)0|[1~9][0~9]*)
string str="";
while(ptr<res.size() && is_dec(res[ptr]))
str+=res[ptr++];
if(ptr<res.size() && res[ptr]=='.')
{
str+=res[ptr++];
while(ptr<res.size() && is_dec(res[ptr]))
str+=res[ptr++];
// "xxxx." is not a correct number
if(str.back()=='.')
{
column+=str.length();
die("invalid number `"+str+"`");
return "0";
}
}
if(ptr<res.size() && (res[ptr]=='e' || res[ptr]=='E'))
{
str+=res[ptr++];
if(ptr<res.size() && (res[ptr]=='-' || res[ptr]=='+'))
str+=res[ptr++];
while(ptr<res.size() && is_dec(res[ptr]))
str+=res[ptr++];
// "xxxe(-|+)" is not a correct number
if(str.back()=='e' || str.back()=='E' || str.back()=='-' || str.back()=='+')
{
column+=str.length();
die("invalid number `"+str+"`");
return "0";
}
}
column+=str.length();
return str;
}
string lexer::str_gen()
{
string str="";
const char begin=res[ptr];
++column;
while(++ptr<res.size() && res[ptr]!=begin)
{
++column;
if(res[ptr]=='\n')
{
column=0;
++line;
}
if(res[ptr]=='\\' && ptr+1<res.size())
{
++column;
++ptr;
switch(res[ptr])
{
case '0': str+='\0'; break;
case 'a': str+='\a'; break;
case 'b': str+='\b'; break;
case 'e': str+='\033'; break;
case 't': str+='\t'; break;
case 'n': str+='\n'; break;
case 'v': str+='\v'; break;
case 'f': str+='\f'; break;
case 'r': str+='\r'; break;
case '?': str+='\?'; break;
case '\\':str+='\\'; break;
case '\'':str+='\''; break;
case '\"':str+='\"'; break;
default: str+=res[ptr];break;
}
continue;
}
str+=res[ptr];
}
// check if this string ends with a " or '
if(ptr++>=res.size())
{
die("get EOF when generating string");
return str;
}
++column;
if(begin=='`' && str.length()!=1)
die("\'`\' is used for string that includes one character");
return str;
}
void lexer::scan(const string& file)
{
line=1;
column=0;
ptr=0;
open(file);
string str;
while(ptr<res.size())
{
while(ptr<res.size() && (res[ptr]==' ' || res[ptr]=='\n' || res[ptr]=='\t' || res[ptr]=='\r' || res[ptr]==0))
{
// these characters will be ignored, and '\n' will cause ++line
++column;
if(res[ptr++]=='\n')
{
++line;
column=0;
}
}
if(ptr>=res.size()) break;
if(is_id(res[ptr]))
{
str=id_gen();
u32 type=get_type(str);
tokens.push_back({line,column,type?type:tok_id,str});
}
else if(is_dec(res[ptr]))
{
str=num_gen(); // make sure column is correct
tokens.push_back({line,column,tok_num,str});
}
else if(is_str(res[ptr]))
{
str=str_gen(); // make sure column is correct
tokens.push_back({line,column,tok_str,str});
}
else if(is_single_opr(res[ptr]))
{
str=res[ptr];
++column;
u32 type=get_type(str);
if(!type)
die("invalid operator `"+str+"`");
tokens.push_back({line,column,type,str});
++ptr;
}
else if(res[ptr]=='.')
{
str=".";
if(ptr+2<res.size() && res[ptr+1]=='.' && res[ptr+2]=='.')
str+="..";
ptr+=str.length();
column+=str.length();
tokens.push_back({line,column,get_type(str),str});
}
else if(is_calc_opr(res[ptr]))
{
// get calculation operator
str=res[ptr++];
if(ptr<res.size() && res[ptr]=='=')
str+=res[ptr++];
column+=str.length();
tokens.push_back({line,column,get_type(str),str});
}
else if(res[ptr]=='#')// avoid note, after this process ptr will point to a '\n', so next loop line counter+1
while(++ptr<res.size() && res[ptr]!='\n');
else
{
++column;
char c=res[ptr++];
die("invalid character 0x"+chrhex(c));
}
}
tokens.push_back({line,column,tok_eof,"<eof>"});
res="";
err.chkerr();
}
void lexer::print()
{
for(auto& tok:tokens)
std::cout<<"("<<tok.line<<" | "<<rawstr(tok.str,128)<<")\n";
}

View File

@@ -1,68 +0,0 @@
#pragma once
#include <cmath>
void const_str(ast& root)
{
auto& vec=root.child();
root.set_str(vec[0].str()+vec[1].str());
root.child().clear();
root.set_type(ast_str);
}
void const_num(ast& root)
{
auto& vec=root.child();
f64 res;
switch(root.type())
{
case ast_add: res=vec[0].num()+vec[1].num(); break;
case ast_sub: res=vec[0].num()-vec[1].num(); break;
case ast_mult:res=vec[0].num()*vec[1].num(); break;
case ast_div: res=vec[0].num()/vec[1].num(); break;
case ast_less:res=vec[0].num()<vec[1].num(); break;
case ast_leq: res=vec[0].num()<=vec[1].num();break;
case ast_grt: res=vec[0].num()>vec[1].num(); break;
case ast_geq: res=vec[0].num()>=vec[1].num();break;
}
// inf and nan will cause number hashmap error in codegen
if(std::isinf(res) || std::isnan(res))
return;
root.set_num(res);
root.child().clear();
root.set_type(ast_num);
}
void calc_const(ast& root)
{
auto& vec=root.child();
for(auto& i:vec)
calc_const(i);
if(vec.size()==1 && root.type()==ast_neg && vec[0].type()==ast_num)
{
f64 res=-vec[0].num();
root.set_num(res);
root.child().clear();
root.set_type(ast_num);
return;
}
if(vec.size()!=2)
return;
if(root.type()!=ast_add && root.type()!=ast_sub &&
root.type()!=ast_mult && root.type()!=ast_div &&
root.type()!=ast_link && root.type()!=ast_less &&
root.type()!=ast_leq && root.type()!=ast_grt &&
root.type()!=ast_geq)
return;
if(root.type()==ast_link &&
vec[0].type()==ast_str && vec[1].type()==ast_str)
const_str(root);
else if(root.type()!=ast_link &&
vec[0].type()==ast_num && vec[1].type()==ast_num)
const_num(root);
}
void optimize(ast& root)
{
for(auto& i:root.child())
calc_const(i);
}

File diff suppressed because it is too large Load Diff

1122
nasal_vm.h

File diff suppressed because it is too large Load Diff

518
src/ast_dumper.cpp Normal file
View File

@@ -0,0 +1,518 @@
#include "ast_dumper.h"
#include "util/util.h"
#include <iostream>
namespace nasal {
bool ast_dumper::visit_use_stmt(use_stmt* node) {
dump_indent();
std::cout << "use" << format_location(node);
push_indent();
for(auto i : node->get_path()) {
if (i==node->get_path().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_null_expr(null_expr* node) {
dump_indent();
std::cout << "null" << format_location(node);
return true;
}
bool ast_dumper::visit_nil_expr(nil_expr* node) {
dump_indent();
std::cout << "nil" << format_location(node);
return true;
}
bool ast_dumper::visit_number_literal(number_literal* node) {
dump_indent();
std::cout << "number " << node->get_number();
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_string_literal(string_literal* node) {
dump_indent();
std::cout << "string \"" << util::rawstr(node->get_content()) << "\"";
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_identifier(identifier* node) {
dump_indent();
std::cout << "identifier " << node->get_name();
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_bool_literal(bool_literal* node) {
dump_indent();
std::cout << "bool " << node->get_flag();
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_vector_expr(vector_expr* node) {
dump_indent();
std::cout << "vector";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_elements()) {
if (i==node->get_elements().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_hash_expr(hash_expr* node) {
dump_indent();
std::cout << "hash";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_members()) {
if (i==node->get_members().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_hash_pair(hash_pair* node) {
dump_indent();
std::cout << "pair " << node->get_name();
std::cout << format_location(node);
if (node->get_value()) {
push_indent();
set_last();
node->get_value()->accept(this);
pop_indent();
}
return true;
}
bool ast_dumper::visit_function(function* node) {
dump_indent();
std::cout << "function";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_parameter_list()) {
i->accept(this);
}
set_last();
node->get_code_block()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_code_block(code_block* node) {
dump_indent();
std::cout << "block";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_expressions()) {
if (i==node->get_expressions().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_parameter(parameter* node) {
dump_indent();
std::cout << "parameter ";
switch(node->get_parameter_type()) {
case parameter::kind::normal_parameter: std::cout << "[normal]"; break;
case parameter::kind::dynamic_parameter: std::cout << "[dynamic]"; break;
case parameter::kind::default_parameter: std::cout << "[default]"; break;
}
std::cout << " " << node->get_parameter_name();
std::cout << format_location(node);
if (node->get_default_value()) {
push_indent();
set_last();
node->get_default_value()->accept(this);
pop_indent();
}
return true;
}
bool ast_dumper::visit_ternary_operator(ternary_operator* node) {
dump_indent();
std::cout << "ternary_operator";
std::cout << format_location(node);
push_indent();
node->get_condition()->accept(this);
node->get_left()->accept(this);
set_last();
node->get_right()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_binary_operator(binary_operator* node) {
if (node->get_optimized_number()) {
node->get_optimized_number()->accept(this);
return true;
}
if (node->get_optimized_string()) {
node->get_optimized_string()->accept(this);
return true;
}
dump_indent();
std::cout << "binary_operator \"";
switch(node->get_operator_type()) {
case binary_operator::kind::add: std::cout << "+"; break;
case binary_operator::kind::sub: std::cout << "-"; break;
case binary_operator::kind::mult: std::cout << "*"; break;
case binary_operator::kind::div: std::cout << "/"; break;
case binary_operator::kind::concat: std::cout << "~"; break;
case binary_operator::kind::bitwise_and: std::cout << "&"; break;
case binary_operator::kind::bitwise_or: std::cout << "|"; break;
case binary_operator::kind::bitwise_xor: std::cout << "^"; break;
case binary_operator::kind::cmpeq: std::cout << "=="; break;
case binary_operator::kind::cmpneq: std::cout << "!="; break;
case binary_operator::kind::grt: std::cout << ">"; break;
case binary_operator::kind::geq: std::cout << ">="; break;
case binary_operator::kind::less: std::cout << "<"; break;
case binary_operator::kind::leq: std::cout << "<="; break;
case binary_operator::kind::condition_and: std::cout << "and"; break;
case binary_operator::kind::condition_or: std::cout << "or"; break;
case binary_operator::kind::null_chain: std::cout << "??"; break;
}
std::cout << "\"" << format_location(node);
push_indent();
node->get_left()->accept(this);
set_last();
node->get_right()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_unary_operator(unary_operator* node) {
if (node->get_optimized_number()) {
node->get_optimized_number()->accept(this);
return true;
}
dump_indent();
std::cout << "unary_operator \"";
switch(node->get_operator_type()) {
case unary_operator::kind::negative: std::cout << "-"; break;
case unary_operator::kind::logical_not: std::cout << "!"; break;
case unary_operator::kind::bitwise_not: std::cout << "~"; break;
}
std::cout << "\"" << format_location(node);
push_indent();
set_last();
node->get_value()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_call_expr(call_expr* node) {
dump_indent();
std::cout << "call_expr";
std::cout << format_location(node);
push_indent();
if (!node->get_calls().size()) {
set_last();
}
node->get_first()->accept(this);
for(auto i : node->get_calls()) {
if (i==node->get_calls().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_call_hash(call_hash* node) {
dump_indent();
std::cout << "call_hash " << node->get_field();
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_null_access(null_access* node) {
dump_indent();
std::cout << "null_access " << node->get_field();
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_call_vector(call_vector* node) {
dump_indent();
std::cout << "call_vector";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_slices()) {
if (i==node->get_slices().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_call_function(call_function* node) {
dump_indent();
std::cout << "call_function";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_argument()) {
if (i==node->get_argument().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_slice_vector(slice_vector* node) {
dump_indent();
std::cout << "slice";
std::cout << format_location(node);
push_indent();
if (!node->get_end()) {
set_last();
}
node->get_begin()->accept(this);
if (node->get_end()) {
set_last();
node->get_end()->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_definition_expr(definition_expr* node) {
dump_indent();
std::cout << "definition";
std::cout << format_location(node);
push_indent();
if (node->get_variable_name()) {
node->get_variable_name()->accept(this);
} else {
node->get_variables()->accept(this);
}
set_last();
if (node->get_tuple()) {
node->get_tuple()->accept(this);
} else {
node->get_value()->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_assignment_expr(assignment_expr* node) {
dump_indent();
std::cout << "assignment ";
switch(node->get_assignment_type()) {
case assignment_expr::kind::add_equal: std::cout << "+="; break;
case assignment_expr::kind::sub_equal: std::cout << "-="; break;
case assignment_expr::kind::mult_equal: std::cout << "*="; break;
case assignment_expr::kind::div_equal: std::cout << "/="; break;
case assignment_expr::kind::concat_equal: std::cout << "~="; break;
case assignment_expr::kind::equal: std::cout << "="; break;
case assignment_expr::kind::bitwise_and_equal: std::cout << "&="; break;
case assignment_expr::kind::bitwise_or_equal: std::cout << "|="; break;
case assignment_expr::kind::bitwise_xor_equal: std::cout << "^="; break;
}
std::cout << format_location(node);
push_indent();
node->get_left()->accept(this);
set_last();
node->get_right()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_multi_identifier(multi_identifier* node) {
dump_indent();
std::cout << "multiple_identifier";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_variables()) {
if (i==node->get_variables().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_tuple_expr(tuple_expr* node) {
dump_indent();
std::cout << "tuple";
std::cout << format_location(node);
push_indent();
for(auto i : node->get_elements()) {
if (i==node->get_elements().back()) {
set_last();
}
i->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_multi_assign(multi_assign* node) {
dump_indent();
std::cout << "multiple_assignment";
std::cout << format_location(node);
push_indent();
node->get_tuple()->accept(this);
set_last();
node->get_value()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_while_expr(while_expr* node) {
dump_indent();
std::cout << "while";
std::cout << format_location(node);
push_indent();
node->get_condition()->accept(this);
set_last();
node->get_code_block()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_for_expr(for_expr* node) {
dump_indent();
std::cout << "for";
std::cout << format_location(node);
push_indent();
node->get_initial()->accept(this);
node->get_condition()->accept(this);
node->get_step()->accept(this);
set_last();
node->get_code_block()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_iter_expr(iter_expr* node) {
dump_indent();
if (node->is_definition()) {
std::cout << "iterator_definition";
} else {
std::cout << "iterator";
}
std::cout << format_location(node);
push_indent();
set_last();
if (node->get_name()) {
node->get_name()->accept(this);
} else {
node->get_call()->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_forei_expr(forei_expr* node) {
dump_indent();
if (node->get_loop_type()==forei_expr::kind::foreach) {
std::cout << "foreach";
} else {
std::cout << "forindex";
}
std::cout << format_location(node);
push_indent();
node->get_iterator()->accept(this);
node->get_value()->accept(this);
set_last();
node->get_code_block()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_condition_expr(condition_expr* node) {
dump_indent();
std::cout << "condition";
std::cout << format_location(node);
push_indent();
if (!node->get_elsif_stataments().size() &&
!node->get_else_statement()) {
set_last();
}
node->get_if_statement()->accept(this);
for(auto i : node->get_elsif_stataments()) {
if (i==node->get_elsif_stataments().back() &&
!node->get_else_statement()) {
set_last();
}
i->accept(this);
}
if (node->get_else_statement()) {
set_last();
node->get_else_statement()->accept(this);
}
pop_indent();
return true;
}
bool ast_dumper::visit_if_expr(if_expr* node) {
dump_indent();
std::cout << "if";
std::cout << format_location(node);
push_indent();
if (node->get_condition()) {
node->get_condition()->accept(this);
}
set_last();
node->get_code_block()->accept(this);
pop_indent();
return true;
}
bool ast_dumper::visit_continue_expr(continue_expr* node) {
dump_indent();
std::cout << "continue";
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_break_expr(break_expr* node) {
dump_indent();
std::cout << "break";
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_return_expr(return_expr* node) {
dump_indent();
std::cout << "return";
std::cout << format_location(node);
if (node->get_value()) {
push_indent();
set_last();
node->get_value()->accept(this);
pop_indent();
}
return true;
}
}

101
src/ast_dumper.h Normal file
View File

@@ -0,0 +1,101 @@
#pragma once
#include "ast_visitor.h"
#include "util/util.h"
#include <iostream>
#include <cstring>
#include <sstream>
#include <vector>
namespace nasal {
class ast_dumper: public ast_visitor {
private:
std::vector<std::string> indent;
private:
void push_indent() {
if (indent.size()) {
if (indent.back()=="├──") {
indent.back() = "";
} else if (indent.back()=="╰──") {
indent.back() = " ";
}
}
indent.push_back("├──");
}
void pop_indent() {
indent.pop_back();
}
void set_last() {
indent.back() = "╰──";
}
void dump_indent() {
if (indent.size() && indent.back()=="") {
indent.back() = "├──";
}
for(const auto& i : indent) {
std::cout << i;
}
}
std::string format_location(expr* node) {
std::stringstream ss;
ss << " ⇒ [";
node->get_location().dump_begin(ss);
ss << "]\n";
return ss.str();
}
public:
bool visit_use_stmt(use_stmt*) override;
bool visit_null_expr(null_expr*) override;
bool visit_nil_expr(nil_expr*) override;
bool visit_number_literal(number_literal*) override;
bool visit_string_literal(string_literal*) override;
bool visit_identifier(identifier*) override;
bool visit_bool_literal(bool_literal*) override;
bool visit_vector_expr(vector_expr*) override;
bool visit_hash_expr(hash_expr*) override;
bool visit_hash_pair(hash_pair*) override;
bool visit_function(function*) override;
bool visit_code_block(code_block*) override;
bool visit_parameter(parameter*) override;
bool visit_ternary_operator(ternary_operator*) override;
bool visit_binary_operator(binary_operator*) override;
bool visit_unary_operator(unary_operator*) override;
bool visit_call_expr(call_expr*) override;
bool visit_call_hash(call_hash*) override;
bool visit_null_access(null_access*) override;
bool visit_call_vector(call_vector*) override;
bool visit_call_function(call_function*) override;
bool visit_slice_vector(slice_vector*) override;
bool visit_definition_expr(definition_expr*) override;
bool visit_assignment_expr(assignment_expr*) override;
bool visit_multi_identifier(multi_identifier*) override;
bool visit_tuple_expr(tuple_expr*) override;
bool visit_multi_assign(multi_assign*) override;
bool visit_while_expr(while_expr*) override;
bool visit_for_expr(for_expr*) override;
bool visit_iter_expr(iter_expr*) override;
bool visit_forei_expr(forei_expr*) override;
bool visit_condition_expr(condition_expr*) override;
bool visit_if_expr(if_expr*) override;
bool visit_continue_expr(continue_expr*) override;
bool visit_break_expr(break_expr*) override;
bool visit_return_expr(return_expr*) override;
public:
void dump(code_block* root) {
util::windows_code_page_manager wm;
wm.set_utf8_output();
root->accept(this);
wm.restore_code_page();
}
};
}

249
src/ast_visitor.cpp Normal file
View File

@@ -0,0 +1,249 @@
#include "ast_visitor.h"
namespace nasal {
bool ast_visitor::visit_expr(expr* node) {
node->accept(this);
return true;
}
bool ast_visitor::visit_use_stmt(use_stmt* node) {
for(auto i : node->get_path()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_call(call* node) {
node->accept(this);
return true;
}
bool ast_visitor::visit_null_expr(null_expr* node) {
return true;
}
bool ast_visitor::visit_nil_expr(nil_expr* node) {
return true;
}
bool ast_visitor::visit_number_literal(number_literal* node) {
return true;
}
bool ast_visitor::visit_string_literal(string_literal* node) {
return true;
}
bool ast_visitor::visit_identifier(identifier* node) {
return true;
}
bool ast_visitor::visit_bool_literal(bool_literal* node) {
return true;
}
bool ast_visitor::visit_vector_expr(vector_expr* node) {
for(auto i : node->get_elements()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_hash_expr(hash_expr* node) {
for(auto i : node->get_members()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_hash_pair(hash_pair* node) {
if (node->get_value()) {
node->get_value()->accept(this);
}
return true;
}
bool ast_visitor::visit_function(function* node) {
for(auto i : node->get_parameter_list()) {
i->accept(this);
}
node->get_code_block()->accept(this);
return true;
}
bool ast_visitor::visit_code_block(code_block* node) {
for(auto i : node->get_expressions()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_parameter(parameter* node) {
if (node->get_default_value()) {
node->get_default_value()->accept(this);
}
return true;
}
bool ast_visitor::visit_ternary_operator(ternary_operator* node) {
node->get_condition()->accept(this);
node->get_left()->accept(this);
node->get_right()->accept(this);
return true;
}
bool ast_visitor::visit_binary_operator(binary_operator* node) {
node->get_left()->accept(this);
node->get_right()->accept(this);
return true;
}
bool ast_visitor::visit_unary_operator(unary_operator* node) {
node->get_value()->accept(this);
return true;
}
bool ast_visitor::visit_call_expr(call_expr* node) {
node->get_first()->accept(this);
for(auto i : node->get_calls()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_call_hash(call_hash* node) {
return true;
}
bool ast_visitor::visit_null_access(null_access* node) {
return true;
}
bool ast_visitor::visit_call_vector(call_vector* node) {
for(auto i : node->get_slices()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_call_function(call_function* node) {
for(auto i : node->get_argument()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_slice_vector(slice_vector* node) {
node->get_begin()->accept(this);
if (node->get_end()) {
node->get_end()->accept(this);
}
return true;
}
bool ast_visitor::visit_definition_expr(definition_expr* node) {
if (node->get_variable_name()) {
node->get_variable_name()->accept(this);
} else {
node->get_variables()->accept(this);
}
if (node->get_tuple()) {
node->get_tuple()->accept(this);
} else {
node->get_value()->accept(this);
}
return true;
}
bool ast_visitor::visit_assignment_expr(assignment_expr* node) {
node->get_left()->accept(this);
node->get_right()->accept(this);
return true;
}
bool ast_visitor::visit_multi_identifier(multi_identifier* node) {
for(auto i : node->get_variables()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_tuple_expr(tuple_expr* node) {
for(auto i : node->get_elements()) {
i->accept(this);
}
return true;
}
bool ast_visitor::visit_multi_assign(multi_assign* node) {
node->get_tuple()->accept(this);
node->get_value()->accept(this);
return true;
}
bool ast_visitor::visit_while_expr(while_expr* node) {
node->get_condition()->accept(this);
node->get_code_block()->accept(this);
return true;
}
bool ast_visitor::visit_for_expr(for_expr* node) {
node->get_initial()->accept(this);
node->get_condition()->accept(this);
node->get_step()->accept(this);
node->get_code_block()->accept(this);
return true;
}
bool ast_visitor::visit_iter_expr(iter_expr* node) {
if (node->get_name()) {
node->get_name()->accept(this);
} else {
node->get_call()->accept(this);
}
return true;
}
bool ast_visitor::visit_forei_expr(forei_expr* node) {
node->get_iterator()->accept(this);
node->get_value()->accept(this);
node->get_code_block()->accept(this);
return true;
}
bool ast_visitor::visit_condition_expr(condition_expr* node) {
node->get_if_statement()->accept(this);
for(auto i : node->get_elsif_stataments()) {
i->accept(this);
}
if (node->get_else_statement()) {
node->get_else_statement()->accept(this);
}
return true;
}
bool ast_visitor::visit_if_expr(if_expr* node) {
if (node->get_condition()) {
node->get_condition()->accept(this);
}
node->get_code_block()->accept(this);
return true;
}
bool ast_visitor::visit_continue_expr(continue_expr* node) {
return true;
}
bool ast_visitor::visit_break_expr(break_expr* node) {
return true;
}
bool ast_visitor::visit_return_expr(return_expr* node) {
if (node->get_value()) {
node->get_value()->accept(this);
}
return true;
}
}

49
src/ast_visitor.h Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include "nasal_ast.h"
namespace nasal {
class ast_visitor {
public:
virtual bool visit_expr(expr*);
virtual bool visit_use_stmt(use_stmt*);
virtual bool visit_call(call*);
virtual bool visit_null_expr(null_expr*);
virtual bool visit_nil_expr(nil_expr*);
virtual bool visit_number_literal(number_literal*);
virtual bool visit_string_literal(string_literal*);
virtual bool visit_identifier(identifier*);
virtual bool visit_bool_literal(bool_literal*);
virtual bool visit_vector_expr(vector_expr*);
virtual bool visit_hash_expr(hash_expr*);
virtual bool visit_hash_pair(hash_pair*);
virtual bool visit_function(function*);
virtual bool visit_code_block(code_block*);
virtual bool visit_parameter(parameter*);
virtual bool visit_ternary_operator(ternary_operator*);
virtual bool visit_binary_operator(binary_operator*);
virtual bool visit_unary_operator(unary_operator*);
virtual bool visit_call_expr(call_expr*);
virtual bool visit_call_hash(call_hash*);
virtual bool visit_null_access(null_access*);
virtual bool visit_call_vector(call_vector*);
virtual bool visit_call_function(call_function*);
virtual bool visit_slice_vector(slice_vector*);
virtual bool visit_definition_expr(definition_expr*);
virtual bool visit_assignment_expr(assignment_expr*);
virtual bool visit_multi_identifier(multi_identifier*);
virtual bool visit_tuple_expr(tuple_expr*);
virtual bool visit_multi_assign(multi_assign*);
virtual bool visit_while_expr(while_expr*);
virtual bool visit_for_expr(for_expr*);
virtual bool visit_iter_expr(iter_expr*);
virtual bool visit_forei_expr(forei_expr*);
virtual bool visit_condition_expr(condition_expr*);
virtual bool visit_if_expr(if_expr*);
virtual bool visit_continue_expr(continue_expr*);
virtual bool visit_break_expr(break_expr*);
virtual bool visit_return_expr(return_expr*);
};
}

62
src/cli/cli.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include "cli/cli.h"
#include <iostream>
namespace nasal::cli {
cli_config parse(const std::vector<std::string>& args) {
cli_config result;
for(const auto& arg : args) {
if (cli_options.count(arg)) {
result.options.insert(cli_options.at(arg));
} else if (!result.input_file_path.length()) {
result.input_file_path = arg;
} else {
result.nasal_vm_args.push_back(arg);
}
}
if (result.input_file_path.length() && result.options.empty()) {
result.options.insert(option::cli_execute);
}
return result;
}
std::ostream& help(std::ostream& out) {
out
<< "\n"
<< " ,--#-,\n"
<< "<3 / \\____\\ <3\n"
<< " |_|__A_|\n"
<< "\nnasal <option>\n"
<< "option:\n"
<< " -h, --help | get help.\n"
<< " -v, --version | get version.\n"
<< " -r, --repl | use repl interpreter.\n"
<< "\nnasal [option] <file> [argv]\n"
<< "option:\n"
<< " -a, --ast | view ast after link/optimize process.\n"
<< " --raw-ast | view ast without after-processing.\n"
<< " -c, --code | view generated bytecode.\n"
<< " -s, --symbol | show analysed symbol info.\n"
<< " -e, --exec | execute directly.\n"
<< " -t, --time | show execute time.\n"
<< " -d, --detail | get detail info.\n"
<< " -f, --ref-file | get referenced files.\n"
<< " -dbg, --debug | debug mode.\n"
<< " --prof | show profiling result, "
<< "available under debug mode.\n"
<< " --prof-all | show profiling result of all files, "
<< "available under debug mode.\n"
<< " --limit | use limited execution mode "
<< "(readonly api enabled).\n"
<< "file:\n"
<< " <filename> | execute file.\n"
<< "argv:\n"
<< " <args> | cmd arguments used in program.\n"
<< "\n";
return out;
}
}

73
src/cli/cli.h Normal file
View File

@@ -0,0 +1,73 @@
#pragma once
#include <cstring>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <iostream>
namespace nasal::cli {
enum class option {
cli_help,
cli_version,
cli_repl_mode,
cli_view_ast,
cli_view_raw_ast,
cli_view_code,
cli_show_symbol,
cli_execute,
cli_show_execute_time,
cli_detail_info,
cli_show_referenced_file,
cli_debug_mode,
cli_profile,
cli_profile_all,
cli_limit_mode
};
struct cli_config {
std::string input_file_path = "";
std::unordered_set<option> options = {};
std::vector<std::string> nasal_vm_args = {};
bool has(option opt) const {
return options.count(opt);
}
};
const std::unordered_map<std::string, option> cli_options = {
{"-h", option::cli_help},
{"--help", option::cli_help},
{"-v", option::cli_version},
{"--version", option::cli_version},
{"-r", option::cli_repl_mode},
{"--repl", option::cli_repl_mode},
{"-a", option::cli_view_ast},
{"--ast", option::cli_view_ast},
{"--raw-ast", option::cli_view_raw_ast},
{"-c", option::cli_view_code},
{"--code", option::cli_view_code},
{"-s", option::cli_show_symbol},
{"--symbol", option::cli_show_symbol},
{"-e", option::cli_execute},
{"--exec", option::cli_execute},
{"-t", option::cli_show_execute_time},
{"--time", option::cli_show_execute_time},
{"-d", option::cli_detail_info},
{"--detail", option::cli_detail_info},
{"-f", option::cli_show_referenced_file},
{"--ref-file", option::cli_show_referenced_file},
{"-dbg", option::cli_debug_mode},
{"--debug", option::cli_debug_mode},
{"--prof", option::cli_profile},
{"--prof-all", option::cli_profile_all},
{"--limit", option::cli_limit_mode}
};
cli_config parse(const std::vector<std::string>&);
std::ostream& help(std::ostream&);
}

181
src/main.cpp Normal file
View File

@@ -0,0 +1,181 @@
#include "nasal.h"
#include "nasal_type.h"
#include "nasal_gc.h"
#include "nasal_err.h"
#include "nasal_lexer.h"
#include "nasal_ast.h"
#include "nasal_parse.h"
#include "nasal_import.h"
#include "ast_visitor.h"
#include "ast_dumper.h"
#include "symbol_finder.h"
#include "optimizer.h"
#include "nasal_codegen.h"
#include "nasal_vm.h"
#include "nasal_dbg.h"
#include "util/util.h"
#include "repl/repl.h"
#include "cli/cli.h"
#include <thread>
#include <cstdlib>
std::ostream& logo(std::ostream& out) {
out
<< "\n"
<< " __ _\n"
<< " /\\ \\ \\__ _ ___ __ _| |\n"
<< " / \\/ / _` / __|/ _` | |\n"
<< " / /\\ / (_| \\__ \\ (_| | |\n"
<< " \\_\\ \\/ \\__,_|___/\\__,_|_|\n"
<< "\n"
<< "ver : " << __nasver__
<< " " << nasal::util::get_platform()
<< " " << nasal::util::get_arch()
<< " (" << __DATE__ << " " << __TIME__ << ")\n"
<< "std : c++ " << __cplusplus << "\n"
<< "core : " << std::thread::hardware_concurrency() << " core(s)\n"
<< "repo : https://github.com/ValKmjolnir/Nasal-Interpreter\n"
<< "repo : https://gitee.com/valkmjolnir/Nasal-Interpreter\n"
<< "wiki : https://wiki.flightgear.org/Nasal_scripting_language\n"
<< "\n"
<< "presented by fgprc members:\n"
<< " - http://fgprc.org\n"
<< " - http://fgprc.org.cn\n"
<< "\n"
<< "input <nasal -h> to get help .\n\n";
return out;
}
std::ostream& version(std::ostream& out) {
std::srand(static_cast<u32>(std::time(nullptr)));
f64 num = 0;
for(u32 i = 0; i<5; ++i) {
num = (num+rand())*(1.0/(RAND_MAX+1.0));
}
// give you 5% to see this easter egg
if (num<0.05) {
nasal::parse::easter_egg();
}
out << "nasal version " << __nasver__;
out << " " << nasal::util::get_platform();
out << " " << nasal::util::get_arch();
out << " (" << __DATE__ << " " << __TIME__ << ")\n";
return out;
}
[[noreturn]]
void err() {
std::cerr << "invalid argument(s), use <nasal -h> to get help.\n";
std::exit(1);
}
void execute(const nasal::cli::cli_config& config) {
using option = nasal::cli::option;
using clk = std::chrono::high_resolution_clock;
const auto den = clk::duration::period::den;
nasal::lexer lex;
nasal::parse parse;
nasal::linker ld;
nasal::codegen gen;
// lexer scans file to get tokens
lex.scan(config.input_file_path).chkerr();
// parser gets lexer's token list to compile
parse.compile(lex).chkerr();
if (config.has(option::cli_view_raw_ast)) {
nasal::ast_dumper().dump(parse.tree());
}
// linker gets parser's ast and load import files to this ast
ld.link(parse, config.has(option::cli_detail_info)).chkerr();
if (config.has(option::cli_show_referenced_file)) {
if (ld.get_file_list().size()) {
std::cout << "referenced file(s):\n";
}
for(const auto& file: ld.get_file_list()) {
std::cout << " " << file << "\n";
}
}
// optimizer does simple optimization on ast
auto opt = std::make_unique<nasal::optimizer>();
opt->do_optimization(parse.tree());
if (config.has(option::cli_view_ast)) {
nasal::ast_dumper().dump(parse.tree());
}
// code generator gets parser's ast and import file list to generate code
gen.compile(parse, ld, false, config.has(option::cli_limit_mode)).chkerr();
if (config.has(option::cli_view_code)) {
gen.print(std::cout);
}
if (config.has(option::cli_show_symbol)) {
gen.symbol_dump(std::cout);
}
// run
const auto start = clk::now();
if (config.has(option::cli_debug_mode)) {
auto debugger = std::make_unique<nasal::dbg>();
debugger->run(
gen,
ld,
config.nasal_vm_args,
config.has(option::cli_profile),
config.has(option::cli_profile_all)
);
} else if (config.has(option::cli_show_execute_time) ||
config.has(option::cli_detail_info) ||
config.has(option::cli_limit_mode) ||
config.has(option::cli_execute)) {
auto runtime = std::make_unique<nasal::vm>();
runtime->set_detail_report_info(config.has(option::cli_detail_info));
runtime->set_limit_mode_flag(config.has(option::cli_limit_mode));
runtime->run(gen, ld, config.nasal_vm_args);
}
// get running time
const auto end = clk::now();
if (config.has(option::cli_show_execute_time)) {
std::clog << "process exited after ";
std::clog << static_cast<f64>((end-start).count())/den << "s.\n\n";
}
}
i32 main(i32 argc, const char* argv[]) {
// output version info
if (argc<=1) {
std::clog << logo;
return 0;
}
// the first argument is the executable itself, ignore it
const auto config = nasal::cli::parse({argv+1, argv+argc});
// run directly or show help
if (argc==2) {
if (config.has(nasal::cli::option::cli_help)) {
std::clog << nasal::cli::help;
} else if (config.has(nasal::cli::option::cli_version)) {
std::clog << version;
} else if (config.has(nasal::cli::option::cli_repl_mode)) {
auto repl = std::make_unique<nasal::repl::repl>();
repl->execute();
} else if (config.input_file_path.size()) {
execute(config);
} else {
err();
}
return 0;
}
// execute with arguments
execute(config);
return 0;
}

21
src/nasal.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#ifndef __nasver__
#define __nasver__ "11.2"
#endif
#include <cstddef>
#include <cstdint>
// abbreviation of some useful basic type
using i32 = std::int32_t;
using i64 = std::int64_t;
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
using usize = std::size_t;
using f64 = double;
// virtual machine stack depth, both global depth and value stack depth
const u32 VM_STACK_DEPTH = UINT16_MAX;

386
src/nasal_ast.cpp Normal file
View File

@@ -0,0 +1,386 @@
#include "nasal_ast.h"
#include "ast_visitor.h"
namespace nasal {
void expr::accept(ast_visitor* visitor) {
visitor->visit_expr(this);
}
use_stmt::~use_stmt() {
for(auto i : path) {
delete i;
}
}
void use_stmt::accept(ast_visitor* visitor) {
visitor->visit_use_stmt(this);
}
void call::accept(ast_visitor* visitor) {
visitor->visit_call(this);
}
void null_expr::accept(ast_visitor* visitor) {
visitor->visit_null_expr(this);
}
void nil_expr::accept(ast_visitor* visitor) {
visitor->visit_nil_expr(this);
}
void number_literal::accept(ast_visitor* visitor) {
visitor->visit_number_literal(this);
}
void string_literal::accept(ast_visitor* visitor) {
visitor->visit_string_literal(this);
}
void identifier::accept(ast_visitor* visitor) {
visitor->visit_identifier(this);
}
void bool_literal::accept(ast_visitor* visitor) {
visitor->visit_bool_literal(this);
}
vector_expr::~vector_expr() {
for(auto i : elements) {
delete i;
}
}
void vector_expr::accept(ast_visitor* visitor) {
visitor->visit_vector_expr(this);
}
hash_expr::~hash_expr() {
for(auto i : members) {
delete i;
}
}
void hash_expr::accept(ast_visitor* visitor) {
visitor->visit_hash_expr(this);
}
hash_pair::~hash_pair() {
if (value) {
delete value;
}
}
void hash_pair::accept(ast_visitor* visitor) {
visitor->visit_hash_pair(this);
}
function::~function() {
for(auto i : parameter_list) {
delete i;
}
if (block) {
delete block;
}
}
void function::accept(ast_visitor* visitor) {
visitor->visit_function(this);
}
code_block::~code_block() {
for(auto i : expressions) {
delete i;
}
}
void code_block::accept(ast_visitor* visitor) {
visitor->visit_code_block(this);
}
parameter::~parameter() {
if (default_value) {
delete default_value;
}
}
void parameter::accept(ast_visitor* visitor) {
visitor->visit_parameter(this);
}
ternary_operator::~ternary_operator() {
if (condition) {
delete condition;
}
if (left) {
delete left;
}
if (right) {
delete right;
}
}
void ternary_operator::accept(ast_visitor* visitor) {
visitor->visit_ternary_operator(this);
}
binary_operator::~binary_operator() {
if (left) {
delete left;
}
if (right) {
delete right;
}
if (optimized_const_number) {
delete optimized_const_number;
}
if (optimized_const_string) {
delete optimized_const_string;
}
}
void binary_operator::accept(ast_visitor* visitor) {
visitor->visit_binary_operator(this);
}
unary_operator::~unary_operator() {
if (value) {
delete value;
}
if (optimized_number) {
delete optimized_number;
}
}
void unary_operator::accept(ast_visitor* visitor) {
visitor->visit_unary_operator(this);
}
call_expr::~call_expr() {
if(first) {
delete first;
}
for(auto i : calls) {
delete i;
}
}
void call_expr::accept(ast_visitor* visitor) {
visitor->visit_call_expr(this);
}
void call_hash::accept(ast_visitor* visitor) {
visitor->visit_call_hash(this);
}
void null_access::accept(ast_visitor* visitor) {
visitor->visit_null_access(this);
}
call_vector::~call_vector() {
for(auto i : calls) {
delete i;
}
}
void call_vector::accept(ast_visitor* visitor) {
visitor->visit_call_vector(this);
}
call_function::~call_function() {
for(auto i : args) {
delete i;
}
}
void call_function::accept(ast_visitor* visitor) {
visitor->visit_call_function(this);
}
slice_vector::~slice_vector() {
if (begin) {
delete begin;
}
if (end) {
delete end;
}
}
void slice_vector::accept(ast_visitor* visitor) {
visitor->visit_slice_vector(this);
}
definition_expr::~definition_expr() {
if (variable_name) {
delete variable_name;
}
if (variables) {
delete variables;
}
if (tuple) {
delete tuple;
}
if (value) {
delete value;
}
}
void definition_expr::accept(ast_visitor* visitor) {
visitor->visit_definition_expr(this);
}
assignment_expr::~assignment_expr() {
if (left) {
delete left;
}
if (right) {
delete right;
}
}
void assignment_expr::accept(ast_visitor* visitor) {
visitor->visit_assignment_expr(this);
}
multi_identifier::~multi_identifier() {
for(auto i : variables) {
delete i;
}
}
void multi_identifier::accept(ast_visitor* visitor) {
visitor->visit_multi_identifier(this);
}
tuple_expr::~tuple_expr() {
for(auto i : elements) {
delete i;
}
}
void tuple_expr::accept(ast_visitor* visitor) {
visitor->visit_tuple_expr(this);
}
multi_assign::~multi_assign() {
if (tuple) {
delete tuple;
}
if (value) {
delete value;
}
}
void multi_assign::accept(ast_visitor* visitor) {
visitor->visit_multi_assign(this);
}
while_expr::~while_expr() {
if (condition) {
delete condition;
}
if (block) {
delete block;
}
}
void while_expr::accept(ast_visitor* visitor) {
visitor->visit_while_expr(this);
}
for_expr::~for_expr() {
if (initializing) {
delete initializing;
}
if (condition) {
delete condition;
}
if (step) {
delete step;
}
if (block) {
delete block;
}
}
void for_expr::accept(ast_visitor* visitor) {
visitor->visit_for_expr(this);
}
iter_expr::~iter_expr() {
if (name) {
delete name;
}
if (call) {
delete call;
}
}
void iter_expr::accept(ast_visitor* visitor) {
visitor->visit_iter_expr(this);
}
forei_expr::~forei_expr() {
if (iterator) {
delete iterator;
}
if (vector_node) {
delete vector_node;
}
if (block) {
delete block;
}
}
void forei_expr::accept(ast_visitor* visitor) {
visitor->visit_forei_expr(this);
}
condition_expr::~condition_expr() {
if (if_stmt) {
delete if_stmt;
}
for(auto i : elsif_stmt) {
delete i;
}
if (else_stmt) {
delete else_stmt;
}
}
void condition_expr::accept(ast_visitor* visitor) {
visitor->visit_condition_expr(this);
}
if_expr::~if_expr() {
if (condition) {
delete condition;
}
if (block) {
delete block;
}
}
void if_expr::accept(ast_visitor* visitor) {
visitor->visit_if_expr(this);
}
void continue_expr::accept(ast_visitor* visitor) {
visitor->visit_continue_expr(this);
}
void break_expr::accept(ast_visitor* visitor) {
visitor->visit_break_expr(this);
}
return_expr::~return_expr() {
if (value) {
delete value;
}
}
void return_expr::accept(ast_visitor* visitor) {
visitor->visit_return_expr(this);
}
}

709
src/nasal_ast.h Normal file
View File

@@ -0,0 +1,709 @@
#pragma once
#include "nasal.h"
#include "nasal_err.h"
#include <vector>
#include <unordered_map>
namespace nasal {
enum class expr_type {
ast_null = 0, // null node
ast_use, // use statement
ast_block, // code block
ast_nil, // nil keyword
ast_num, // number, basic value type
ast_str, // string, basic value type
ast_id, // identifier
ast_bool, // bools
ast_func, // func keyword
ast_hash, // hash, basic value type
ast_vec, // vector, basic value type
ast_pair, // pair of key and value in hashmap
ast_call, // mark a sub-tree of calling an identifier
ast_callh, // id.name
ast_null_access, // id?.name
ast_callv, // id[index]
ast_callf, // id()
ast_subvec, // id[index:index]
ast_param, // function parameter
ast_ternary, // ternary operator
ast_binary, // binary operator
ast_unary, // unary operator
ast_for, // for keyword
ast_forei, // foreach or forindex loop
ast_while, // while
ast_iter, // iterator, used in forindex/foreach
ast_cond, // mark a sub-tree of conditional expression
ast_if, // if keyword
ast_multi_id, // multi identifiers sub-tree
ast_tuple, // tuple, stores multiple scalars
ast_def, // definition
ast_assign, // assignment
ast_multi_assign,// multiple assignment
ast_continue, // continue keyword, only used in loop
ast_break, // break keyword, only used in loop
ast_ret // return keyword, only used in function block
};
class ast_visitor;
class identifier;
class hash_pair;
class parameter;
class slice_vector;
class multi_identifier;
class code_block;
class if_expr;
class tuple_expr;
class expr {
protected:
span nd_loc;
expr_type nd_type;
public:
expr(const span& location, expr_type node_type):
nd_loc(location), nd_type(node_type) {}
virtual ~expr() = default;
void set_begin(u64 line, u64 column) {
nd_loc.begin_line = line;
nd_loc.begin_column = column;
}
const auto& get_location() const { return nd_loc; }
const auto get_line() const { return nd_loc.begin_line; }
auto get_type() const { return nd_type; }
void update_location(const span& location) {
nd_loc.end_line = location.end_line;
nd_loc.end_column = location.end_column;
}
virtual void accept(ast_visitor*);
};
class use_stmt: public expr {
private:
std::vector<identifier*> path;
public:
use_stmt(const span& location):
expr(location, expr_type::ast_use) {}
~use_stmt() override;
void accept(ast_visitor*) override;
void add_path(identifier* node) {path.push_back(node);}
const auto& get_path() const {return path;}
};
class call: public expr {
public:
call(const span& location, expr_type node_type):
expr(location, node_type) {}
virtual ~call() = default;
virtual void accept(ast_visitor*);
};
class null_expr: public expr {
public:
null_expr(const span& location):
expr(location, expr_type::ast_null) {}
~null_expr() override = default;
void accept(ast_visitor*) override;
};
class nil_expr: public expr {
public:
nil_expr(const span& location):
expr(location, expr_type::ast_nil) {}
~nil_expr() override = default;
void accept(ast_visitor*) override;
};
class number_literal: public expr {
private:
f64 number;
public:
number_literal(const span& location, const f64 num):
expr(location, expr_type::ast_num), number(num) {}
~number_literal() override = default;
f64 get_number() const {return number;}
void accept(ast_visitor*) override;
};
class string_literal: public expr {
private:
std::string content;
public:
string_literal(const span& location, const std::string& str):
expr(location, expr_type::ast_str), content(str) {}
~string_literal() override = default;
const std::string& get_content() const {return content;}
void accept(ast_visitor*) override;
};
class identifier: public expr {
private:
std::string name;
public:
identifier(const span& location, const std::string& str):
expr(location, expr_type::ast_id), name(str) {}
~identifier() override = default;
const std::string& get_name() const {return name;}
void accept(ast_visitor*) override;
};
class bool_literal: public expr {
private:
bool flag;
public:
bool_literal(const span& location, const bool bool_flag):
expr(location, expr_type::ast_bool), flag(bool_flag) {}
~bool_literal() override = default;
bool get_flag() const {return flag;}
void accept(ast_visitor*) override;
};
class vector_expr: public expr {
private:
std::vector<expr*> elements;
public:
vector_expr(const span& location):
expr(location, expr_type::ast_vec) {}
~vector_expr() override;
void add_element(expr* node) {elements.push_back(node);}
std::vector<expr*>& get_elements() {return elements;}
void accept(ast_visitor*) override;
};
class hash_expr: public expr {
private:
std::vector<hash_pair*> members;
public:
hash_expr(const span& location):
expr(location, expr_type::ast_hash) {}
~hash_expr() override;
void add_member(hash_pair* node) {members.push_back(node);}
std::vector<hash_pair*>& get_members() {return members;}
void accept(ast_visitor*) override;
};
class hash_pair: public expr {
private:
std::string name;
expr* value;
public:
hash_pair(const span& location):
expr(location, expr_type::ast_pair),
value(nullptr) {}
~hash_pair() override;
void set_name(const std::string& field_name) {name = field_name;}
void set_value(expr* node) {value = node;}
const std::string& get_name() const {return name;}
expr* get_value() {return value;}
void accept(ast_visitor*) override;
};
class function: public expr {
private:
std::vector<parameter*> parameter_list;
code_block* block;
public:
function(const span& location):
expr(location, expr_type::ast_func),
block(nullptr) {}
~function() override;
void add_parameter(parameter* node) {parameter_list.push_back(node);}
void set_code_block(code_block* node) {block = node;}
std::vector<parameter*>& get_parameter_list() {return parameter_list;}
code_block* get_code_block() {return block;}
void accept(ast_visitor*) override;
};
class code_block: public expr {
private:
std::vector<expr*> expressions;
public:
code_block(const span& location):
expr(location, expr_type::ast_block) {}
~code_block() override;
void add_expression(expr* node) {expressions.push_back(node);}
std::vector<expr*>& get_expressions() {return expressions;}
void accept(ast_visitor*) override;
};
class parameter: public expr {
public:
enum class kind {
normal_parameter,
default_parameter,
dynamic_parameter
};
private:
kind type;
std::string name;
expr* default_value;
public:
parameter(const span& location):
expr(location, expr_type::ast_param),
name(""), default_value(nullptr) {}
~parameter() override;
void set_parameter_type(kind pt) {type = pt;}
void set_parameter_name(const std::string& pname) {name = pname;}
void set_default_value(expr* node) {default_value = node;}
auto get_parameter_type() {return type;}
const auto& get_parameter_name() const {return name;}
auto get_default_value() {return default_value;}
void accept(ast_visitor*) override;
};
class ternary_operator: public expr {
private:
expr* condition;
expr* left;
expr* right;
public:
ternary_operator(const span& location):
expr(location, expr_type::ast_ternary),
condition(nullptr), left(nullptr), right(nullptr) {}
~ternary_operator() override;
void set_condition(expr* node) {condition = node;}
void set_left(expr* node) {left = node;}
void set_right(expr* node) {right = node;}
expr* get_condition() {return condition;}
expr* get_left() {return left;}
expr* get_right() {return right;}
void accept(ast_visitor*) override;
};
class binary_operator: public expr {
public:
enum class kind {
add,
sub,
mult,
div,
concat,
cmpeq,
cmpneq,
less,
leq,
grt,
geq,
bitwise_or,
bitwise_xor,
bitwise_and,
condition_and,
condition_or,
null_chain
};
private:
kind type;
expr* left;
expr* right;
number_literal* optimized_const_number;
string_literal* optimized_const_string;
public:
binary_operator(const span& location):
expr(location, expr_type::ast_binary),
left(nullptr), right(nullptr),
optimized_const_number(nullptr),
optimized_const_string(nullptr) {}
~binary_operator() override;
void set_operator_type(kind operator_type) {type = operator_type;}
void set_left(expr* node) {left = node;}
void set_right(expr* node) {right = node;}
void set_optimized_number(number_literal* node) {optimized_const_number = node;}
void set_optimized_string(string_literal* node) {optimized_const_string = node;}
auto get_operator_type() const {return type;}
auto get_left() {return left;}
auto get_right() {return right;}
auto get_optimized_number() {return optimized_const_number;}
auto get_optimized_string() {return optimized_const_string;}
void accept(ast_visitor*) override;
};
class unary_operator: public expr {
public:
enum class kind {
negative,
logical_not,
bitwise_not
};
private:
kind type;
expr* value;
number_literal* optimized_number;
public:
unary_operator(const span& location):
expr(location, expr_type::ast_unary),
value(nullptr), optimized_number(nullptr) {}
~unary_operator() override;
void set_operator_type(kind operator_type) {type = operator_type;}
void set_value(expr* node) {value = node;}
void set_optimized_number(number_literal* node) {optimized_number = node;}
auto get_operator_type() const {return type;}
auto get_value() {return value;}
auto get_optimized_number() {return optimized_number;}
void accept(ast_visitor*) override;
};
class call_expr: public expr {
private:
expr* first;
std::vector<call*> calls;
public:
call_expr(const span& location):
expr(location, expr_type::ast_call),
first(nullptr) {}
~call_expr() override;
void set_first(expr* node) {first = node;}
void add_call(call* node) {calls.push_back(node);}
expr* get_first() {return first;}
auto& get_calls() {return calls;}
void accept(ast_visitor*) override;
};
class call_hash: public call {
private:
std::string field;
public:
call_hash(const span& location, const std::string& name):
call(location, expr_type::ast_callh),
field(name) {}
~call_hash() override = default;
const std::string& get_field() const {return field;}
void accept(ast_visitor*) override;
};
class null_access: public call {
private:
std::string field;
public:
null_access(const span& location, const std::string& name):
call(location, expr_type::ast_null_access),
field(name) {}
~null_access() override = default;
const std::string& get_field() const {return field;}
void accept(ast_visitor*) override;
};
class call_vector: public call {
private:
std::vector<slice_vector*> calls;
public:
call_vector(const span& location):
call(location, expr_type::ast_callv) {}
~call_vector() override;
void add_slice(slice_vector* node) {calls.push_back(node);}
std::vector<slice_vector*>& get_slices() {return calls;}
void accept(ast_visitor*) override;
};
class call_function: public call {
private:
std::vector<expr*> args;
public:
call_function(const span& location):
call(location, expr_type::ast_callf) {}
~call_function() override;
void add_argument(expr* node) {args.push_back(node);}
std::vector<expr*>& get_argument() {return args;}
void accept(ast_visitor*) override;
};
class slice_vector: public expr {
private:
expr* begin;
expr* end;
public:
slice_vector(const span& location):
expr(location, expr_type::ast_subvec),
begin(nullptr), end(nullptr) {}
~slice_vector() override;
void set_begin(expr* node) {begin = node;}
void set_end(expr* node) {end = node;}
expr* get_begin() {return begin;}
expr* get_end() {return end;}
void accept(ast_visitor*) override;
};
class definition_expr: public expr {
private:
identifier* variable_name;
multi_identifier* variables;
tuple_expr* tuple;
expr* value;
public:
definition_expr(const span& location):
expr(location, expr_type::ast_def),
variable_name(nullptr), variables(nullptr),
tuple(nullptr), value(nullptr) {}
~definition_expr() override;
void set_identifier(identifier* node) {variable_name = node;}
void set_multi_define(multi_identifier* node) {variables = node;}
void set_tuple(tuple_expr* node) {tuple = node;}
void set_value(expr* node) {value = node;}
identifier* get_variable_name() {return variable_name;}
multi_identifier* get_variables() {return variables;}
tuple_expr* get_tuple() {return tuple;}
expr* get_value() {return value;}
void accept(ast_visitor*) override;
};
class assignment_expr: public expr {
public:
enum class kind {
equal,
add_equal,
sub_equal,
mult_equal,
div_equal,
concat_equal,
bitwise_and_equal,
bitwise_or_equal,
bitwise_xor_equal
};
private:
kind type;
expr* left;
expr* right;
public:
assignment_expr(const span& location):
expr(location, expr_type::ast_assign),
left(nullptr), right(nullptr) {}
~assignment_expr() override;
void set_assignment_type(kind operator_type) {type = operator_type;}
void set_left(expr* node) {left = node;}
void set_right(expr* node) {right = node;}
auto get_assignment_type() const {return type;}
auto get_left() {return left;}
auto get_right() {return right;}
void accept(ast_visitor*) override;
};
class multi_identifier: public expr {
private:
std::vector<identifier*> variables;
public:
multi_identifier(const span& location):
expr(location, expr_type::ast_multi_id) {}
~multi_identifier() override;
void add_var(identifier* node) {variables.push_back(node);}
std::vector<identifier*>& get_variables() {return variables;}
void accept(ast_visitor*) override;
};
class tuple_expr: public expr {
private:
std::vector<expr*> elements;
public:
tuple_expr(const span& location):
expr(location, expr_type::ast_tuple) {}
~tuple_expr() override;
void add_element(expr* node) {elements.push_back(node);}
std::vector<expr*>& get_elements() {return elements;}
void accept(ast_visitor*) override;
};
class multi_assign: public expr {
private:
tuple_expr* tuple;
expr* value;
public:
multi_assign(const span& location):
expr(location, expr_type::ast_multi_assign),
tuple(nullptr), value(nullptr) {}
~multi_assign() override;
void set_tuple(tuple_expr* node) {tuple = node;}
void set_value(expr* node) {value = node;}
tuple_expr* get_tuple() {return tuple;}
expr* get_value() {return value;}
void accept(ast_visitor*) override;
};
class while_expr: public expr {
private:
expr* condition;
code_block* block;
public:
while_expr(const span& location):
expr(location, expr_type::ast_while),
condition(nullptr), block(nullptr) {}
~while_expr() override;
void set_condition(expr* node) {condition = node;}
void set_code_block (code_block* node) {block = node;}
expr* get_condition() {return condition;}
code_block* get_code_block() {return block;}
void accept(ast_visitor*) override;
};
class for_expr: public expr {
private:
expr* initializing;
expr* condition;
expr* step;
code_block* block;
public:
for_expr(const span& location):
expr(location, expr_type::ast_for),
initializing(nullptr), condition(nullptr),
step(nullptr), block(nullptr) {}
~for_expr() override;
void set_initial(expr* node) {initializing = node;}
void set_condition(expr* node) {condition = node;}
void set_step(expr* node) {step = node;}
void set_code_block(code_block* node) {block = node;}
expr* get_initial() {return initializing;}
expr* get_condition() {return condition;}
expr* get_step() {return step;}
code_block* get_code_block() {return block;}
void accept(ast_visitor*) override;
};
class iter_expr: public expr {
private:
bool is_iterator_definition;
identifier* name;
call_expr* call;
public:
iter_expr(const span& location):
expr(location, expr_type::ast_iter),
is_iterator_definition(false),
name(nullptr), call(nullptr) {}
~iter_expr() override;
void set_name(identifier* node) {name = node;}
void set_call(call_expr* node) {call = node;}
void set_is_definition(bool flag) {is_iterator_definition = flag;}
identifier* get_name() {return name;}
call_expr* get_call() {return call;}
bool is_definition() const {return is_iterator_definition;}
void accept(ast_visitor*) override;
};
class forei_expr: public expr {
public:
enum class kind {
foreach,
forindex
};
private:
kind type;
iter_expr* iterator;
expr* vector_node;
code_block* block;
public:
forei_expr(const span& location):
expr(location, expr_type::ast_forei),
type(kind::foreach), iterator(nullptr),
vector_node(nullptr), block(nullptr) {}
~forei_expr() override;
void set_loop_type(kind ft) {type = ft;}
void set_iterator(iter_expr* node) {iterator = node;}
void set_value(expr* node) {vector_node = node;}
void set_code_block(code_block* node) {block = node;}
auto get_loop_type() const {return type;}
auto get_iterator() {return iterator;}
auto get_value() {return vector_node;}
auto get_code_block() {return block;}
void accept(ast_visitor*) override;
};
class condition_expr: public expr {
private:
if_expr* if_stmt;
std::vector<if_expr*> elsif_stmt;
if_expr* else_stmt;
public:
condition_expr(const span& location):
expr(location, expr_type::ast_cond),
if_stmt(nullptr), else_stmt(nullptr) {}
~condition_expr() override;
void set_if_statement(if_expr* node) {if_stmt = node;}
void add_elsif_statement(if_expr* node) {elsif_stmt.push_back(node);}
void set_else_statement(if_expr* node) {else_stmt = node;}
if_expr* get_if_statement() {return if_stmt;}
std::vector<if_expr*>& get_elsif_stataments() {return elsif_stmt;}
if_expr* get_else_statement() {return else_stmt;}
void accept(ast_visitor*) override;
};
class if_expr: public expr {
private:
expr* condition;
code_block* block;
public:
if_expr(const span& location):
expr(location, expr_type::ast_if),
condition(nullptr), block(nullptr) {}
~if_expr() override;
void set_condition(expr* node) {condition = node;}
void set_code_block(code_block* node) {block = node;}
expr* get_condition() {return condition;}
code_block* get_code_block() {return block;}
void accept(ast_visitor*) override;
};
class continue_expr: public expr {
public:
continue_expr(const span& location):
expr(location, expr_type::ast_continue) {}
~continue_expr() override = default;
void accept(ast_visitor*) override;
};
class break_expr: public expr {
public:
break_expr(const span& location):
expr(location, expr_type::ast_break) {}
~break_expr() = default;
void accept(ast_visitor*) override;
};
class return_expr: public expr {
private:
expr* value;
public:
return_expr(const span& location):
expr(location, expr_type::ast_ret),
value(nullptr) {}
~return_expr() override;
void set_value(expr* node) {value = node;}
expr* get_value() {return value;}
void accept(ast_visitor*) override;
};
}

1494
src/nasal_codegen.cpp Normal file

File diff suppressed because it is too large Load Diff

173
src/nasal_codegen.h Normal file
View File

@@ -0,0 +1,173 @@
#pragma once
#include "nasal_err.h"
#include "nasal_opcode.h"
#include "nasal_ast.h"
#include "ast_visitor.h"
#include "symbol_finder.h"
#include "nasal_parse.h"
#include "nasal_import.h"
#include "natives/builtin.h"
#include "natives/coroutine.h"
#include "natives/bits_lib.h"
#include "natives/math_lib.h"
#include "natives/fg_props.h"
#include "natives/io_lib.h"
#include "natives/json_lib.h"
#include "natives/dylib_lib.h"
#include "natives/regex_lib.h"
#include "natives/unix_lib.h"
#include <iomanip>
#include <list>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#ifdef _MSC_VER
#pragma warning (disable:4244)
#pragma warning (disable:4267)
#endif
namespace nasal {
class codegen {
private:
error err;
// repl output flag, will generate op_repl to output stack top value if true
bool flag_need_repl_output = false;
// limit mode flag
bool flag_limited_mode = false;
// under limited mode, unsafe system api will be banned
const std::unordered_set<std::string> unsafe_system_api = {
// builtin
"__system", "__input",
// io
"__fout", "__open", "__write", "__stat"
// bits
"__fld", "__sfld", "__setfld",
"__buf",
// fg
"__logprint",
// dylib
"__dlopen", "__dlclose", "__dlcallv", "__dlcall",
// unix
"__pipe", "__fork", "__waitpid", "__chdir",
"__environ", "__getcwd", "__getenv"
};
// file mapper for file -> index
std::unordered_map<std::string, usize> file_map;
void init_file_map(const std::vector<std::string>&);
// used for generate pop in return expression
std::vector<u32> in_foreach_loop_level;
// constant numbers and strings
std::unordered_map<f64, u64> const_number_map;
std::unordered_map<std::string, u64> const_string_map;
std::vector<f64> const_number_table;
std::vector<std::string> const_string_table;
// native functions
std::vector<nasal_builtin_table> native_function;
std::unordered_map<std::string, usize> native_function_mapper;
void load_native_function_table(nasal_builtin_table*);
void init_native_function();
// generated opcodes
std::vector<opcode> code;
// used to store jmp operands index, to fill the jump address back
std::list<std::vector<u64>> continue_ptr;
std::list<std::vector<u64>> break_ptr;
// symbol table
// global : max VM_STACK_DEPTH-1 values
std::unordered_map<std::string, u64> global;
// nasal namespace
// stores all global symbols of each file
std::unordered_map<std::string, std::unordered_set<std::string>> nasal_namespace;
// local : max 32768 upvalues 65536 values
// but in fact local scope also has less than VM_STACK_DEPTH value
std::list<std::unordered_map<std::string, u64>> local;
void check_id_exist(identifier*);
void die(const std::string& info, const span& loc) {
err.err("code", loc, info);
}
void regist_number(const f64);
void regist_string(const std::string&);
void find_symbol(code_block*);
void regist_symbol(const std::string&);
i64 local_symbol_find(const std::string&);
i64 global_symbol_find(const std::string&);
i64 upvalue_symbol_find(const std::string&);
void emit(u8, u64, const span&);
void number_gen(number_literal*);
void string_gen(string_literal*);
void bool_gen(bool_literal*);
void vector_gen(vector_expr*);
void hash_gen(hash_expr*);
void func_gen(function*);
void call_gen(call_expr*);
void call_identifier(identifier*);
void call_hash_gen(call_hash*);
void null_access_gen(null_access*);
void call_vector_gen(call_vector*);
void call_func_gen(call_function*);
void mcall(expr*);
void mcall_identifier(identifier*);
void mcall_vec(call_vector*);
void mcall_hash(call_hash*);
void multi_def(definition_expr*);
void single_def(definition_expr*);
void definition_gen(definition_expr*);
void assignment_expression(assignment_expr*);
void gen_assignment_equal_statement(assignment_expr*);
void replace_left_assignment_with_load(const span&);
void assignment_statement(assignment_expr*);
void multi_assign_gen(multi_assign*);
void cond_gen(condition_expr*);
void loop_gen(expr*);
void load_continue_break(u64, u64);
void while_gen(while_expr*);
void for_gen(for_expr*);
void forei_gen(forei_expr*);
void statement_generation(expr*);
void or_gen(binary_operator*);
void and_gen(binary_operator*);
void unary_gen(unary_operator*);
void binary_gen(binary_operator*);
void null_chain_gen(binary_operator*);
void trino_gen(ternary_operator*);
void calc_gen(expr*);
void repl_mode_info_output_gen(expr*);
void block_gen(code_block*);
void ret_gen(return_expr*);
public:
const auto& strs() const {return const_string_table;}
const auto& nums() const {return const_number_table;}
const auto& natives() const {return native_function;}
const auto& codes() const {return code;}
const auto& globals() const {return global;}
public:
codegen() = default;
const error& compile(parse&, linker&, bool, bool);
void print(std::ostream&);
void symbol_dump(std::ostream&) const;
};
}

296
src/nasal_dbg.cpp Normal file
View File

@@ -0,0 +1,296 @@
#include "nasal_dbg.h"
namespace nasal {
void operand_line_counter::init_counter() {
for(usize i = 0; i<operand_line_counter::operand_size; ++i) {
operand_counter[i] = 0;
}
}
void operand_line_counter::load_file_line_counter(
const std::vector<std::string>& file_list) {
file_name_list = file_list;
file_line_counter = {};
file_contents = {};
filestream fs;
for(usize i =0; i<file_list.size(); ++i) {
fs.load(file_list[i]);
file_contents.push_back(fs.file_content());
file_line_counter.push_back({});
file_line_counter.back().resize(fs.size(), 0);
}
}
void operand_line_counter::init(const std::vector<std::string>& file_list) {
init_counter();
load_file_line_counter(file_list);
}
void operand_line_counter::dump_operand_count() const {
typedef std::pair<u32, u64> op_count;
std::vector<op_count> opcall;
u64 total = 0;
for(usize i = 0; i<operand_line_counter::operand_size; ++i) {
total += operand_counter[i];
opcall.push_back({i, operand_counter[i]});
}
std::sort(opcall.begin(), opcall.end(),
[](const op_count& a, const op_count& b) {
return a.second>b.second;
}
);
std::clog << "\noperands call info (<1% ignored)\n";
for(const auto& i : opcall) {
u64 rate = i.second*100/total;
if (!rate) {
break;
}
std::clog << " ";
std::clog << operand_name_table.at(static_cast<op_code_type>(i.first));
std::clog << " : " << i.second << " (" << rate << "%)\n";
}
std::clog << " total : " << total << '\n';
}
void operand_line_counter::dump_all_code_line_counter(std::ostream& os) const {
u64 max_call_time = 0;
for(const auto& context : file_line_counter) {
for(const auto& count : context) {
max_call_time = count>max_call_time? count:max_call_time;
}
}
auto pad_length = std::to_string(max_call_time).length();
for(usize i = 0; i<file_name_list.size(); ++i) {
os << "\ncode profiling data of " << file_name_list[i] << ":\n";
const auto& context = file_contents[i];
const auto& counter = file_line_counter[i];
for(usize j = 0; j<context.size(); ++j) {
os << " " << std::right << std::setw(pad_length);
os << std::setfill(' ');
os << (counter[j]==0? "":std::to_string(counter[j]));
os << " " << context[j] << "\n";
}
}
}
void operand_line_counter::dump_this_file_line_counter(std::ostream& os) const {
u64 max_call_time = 0;
for(const auto& count : file_line_counter[0]) {
max_call_time = count>max_call_time? count:max_call_time;
}
auto pad_length = std::to_string(max_call_time).length();
os << "\ncode profiling data of " << file_name_list[0] << ":\n";
const auto& context = file_contents[0];
const auto& counter = file_line_counter[0];
for(usize i = 0; i<context.size(); ++i) {
os << " " << std::right << std::setw(pad_length);
os << std::setfill(' ');
os << (counter[i]==0? "":std::to_string(counter[i]));
os << " " << context[i] << "\n";
}
}
std::vector<std::string> dbg::parse(const std::string& cmd) {
std::vector<std::string> res;
usize last = 0, pos = cmd.find(" ", 0);
while(pos!=std::string::npos) {
if (pos>last) {
res.push_back(cmd.substr(last, pos-last));
}
last = pos+1;
pos = cmd.find(" ", last);
}
if (last<cmd.length()) {
res.push_back(cmd.substr(last));
}
return res;
}
u16 dbg::file_index(const std::string& filename) const {
for(u16 i = 0; i<file_list_size; ++i) {
if (filename==files[i]) {
return i;
}
}
return UINT16_MAX;
}
void dbg::err() const {
std::cerr
<< "incorrect command\n"
<< "input \'h\' to get help\n";
}
void dbg::help() const {
std::clog
<< "<option>\n"
<< " h, help | get help\n"
<< " bt, backtrace | get function call trace\n"
<< " c, continue | run program until break point or exit\n"
<< " f, file | see all the compiled files\n"
<< " g, global | see global values\n"
<< " l, local | see local values\n"
<< " u, upval | see upvalue\n"
<< " r, register | show vm register detail\n"
<< " a, all | show global, local and upvalue\n"
<< " n, next | execute next bytecode\n"
<< " q, exit | exit debugger\n"
<< "<option> <filename> <line>\n"
<< " bk, break | set break point\n";
}
void dbg::list_file() const {
for(usize i = 0; i<file_list_size; ++i) {
std::clog << "[" << i << "] " << files[i] << "\n";
}
}
void dbg::step_info() {
u64 line = bytecode[ctx.pc].line==0? 0:bytecode[ctx.pc].line-1;
u64 begin = (line>>3)==0? 0:((line>>3)<<3);
u64 end = (1+(line>>3))<<3;
src.load(files[bytecode[ctx.pc].fidx]);
std::clog << "\nsource code:\n";
for(u64 i = begin; i<end && i<src.size(); ++i) {
std::clog << (i==line? back_white:reset);
std::clog << (i==line? "--> ":" ") << src[i] << reset << "\n";
}
begin = (ctx.pc>>3)==0? 0:((ctx.pc>>3)<<3);
end = (1+(ctx.pc>>3))<<3;
codestream::set(const_number, const_string, native_function.data(), files);
std::clog << "\nnext bytecode:\n";
for(u64 i = begin; i<end && bytecode[i].op!=op_exit; ++i) {
std::clog
<< (i==ctx.pc? back_white:reset)
<< (i==ctx.pc? "--> ":" ")
<< codestream(bytecode[i], i)
<< reset << "\n";
}
stack_info(16);
}
void dbg::interact() {
// special operand, end execution
if (bytecode[ctx.pc].op==op_exit) {
return;
}
// do not need interact while doing profiling
if (do_operand_count) {
return;
}
// is not break point and is not next stop command
const auto& code = bytecode[ctx.pc];
if ((code.fidx!=break_file_index || code.line!=break_line) && !next) {
return;
}
next = false;
std::string cmd;
step_info();
while(true) {
std::clog << ">> ";
std::getline(std::cin, cmd);
auto res = parse(cmd);
if (res.size()==0) {
step_info();
} else if (res.size()==1) {
switch(get_cmd_type(res[0])) {
case cmd_kind::cmd_help: help(); break;
case cmd_kind::cmd_backtrace:
function_call_trace();
trace_back();
break;
case cmd_kind::cmd_continue: return;
case cmd_kind::cmd_list_file: list_file(); break;
case cmd_kind::cmd_global: global_state(); break;
case cmd_kind::cmd_local: local_state(); break;
case cmd_kind::cmd_upval: upvalue_state(); break;
case cmd_kind::cmd_register: register_info(); break;
case cmd_kind::cmd_show_all: all_state_detail(); break;
case cmd_kind::cmd_next: next = true; return;
case cmd_kind::cmd_exit: std::exit(0);
default: err(); break;
}
} else if (res.size()==3 &&
get_cmd_type(res[0])==cmd_kind::cmd_break_point) {
break_file_index = file_index(res[1]);
if (break_file_index==UINT16_MAX) {
std::clog << "cannot find file named `" << res[1] << "`\n";
continue;
}
i32 tmp = atoi(res[2].c_str());
if (tmp<=0) {
std::clog << "incorrect line number `" << res[2] << "`\n";
} else {
break_line = tmp;
}
} else {
err();
}
}
}
void dbg::run(const codegen& gen,
const linker& linker,
const std::vector<std::string>& argv,
bool profile,
bool show_all_prof_result) {
set_detail_report_info(true);
do_operand_count = profile || show_all_prof_result;
const auto& file_list = linker.get_file_list();
file_list_size = file_list.size();
vm_init_enrty(
gen.strs(),
gen.nums(),
gen.natives(),
gen.codes(),
gen.globals(),
file_list,
argv
);
counter.init(file_list);
std::vector<u8> code;
std::vector<u16> code_file_index;
std::vector<u64> code_line;
for(const auto& i : gen.codes()) {
code.push_back(i.op);
code_file_index.push_back(i.fidx);
code_line.push_back(i.line);
imm.push_back(i.num);
}
while(operand_function[code[ctx.pc]]) {
interact();
counter.add_operand_counter(code[ctx.pc]);
counter.add_code_line_counter(code_file_index[ctx.pc], code_line[ctx.pc]);
(this->*operand_function[code[ctx.pc]])();
if (ctx.top>=ctx.canary) {
die("stack overflow");
}
++ctx.pc;
}
counter.dump_operand_count();
if (do_operand_count) {
show_all_prof_result?
counter.dump_all_code_line_counter(std::clog):
counter.dump_this_file_line_counter(std::clog);
}
ngc.info();
ngc.clear();
imm.clear();
return;
}
}

123
src/nasal_dbg.h Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include "nasal_import.h"
#include "nasal_err.h"
#include "nasal_opcode.h"
#include "nasal_vm.h"
#include <cstring>
#include <algorithm>
#include <unordered_map>
namespace nasal {
// count detail operand calling
// and show them before each line of the source file
class operand_line_counter {
private:
static const usize operand_size = op_code_type::op_ret + 1;
u64 operand_counter[operand_size];
std::vector<std::string> file_name_list;
std::vector<std::vector<u64>> file_line_counter;
std::vector<std::vector<std::string>> file_contents;
private:
void init_counter();
void load_file_line_counter(const std::vector<std::string>&);
public:
void init(const std::vector<std::string>&);
void dump_operand_count() const;
void dump_all_code_line_counter(std::ostream&) const;
void dump_this_file_line_counter(std::ostream&) const;
void add_operand_counter(usize index) {
operand_counter[index] += index<operand_size? 1:0;
}
void add_code_line_counter(usize fidx, usize line) {
auto& vec = file_line_counter[fidx];
vec[line==0? line: line-1]++;
}
};
class dbg: public vm {
private:
enum class cmd_kind {
cmd_error,
cmd_help,
cmd_backtrace,
cmd_continue,
cmd_list_file,
cmd_global,
cmd_local,
cmd_upval,
cmd_register,
cmd_show_all,
cmd_next,
cmd_break_point,
cmd_exit
};
private:
const std::unordered_map<std::string, cmd_kind> command_table = {
{"h", cmd_kind::cmd_help},
{"help", cmd_kind::cmd_help},
{"bt", cmd_kind::cmd_backtrace},
{"backtrace", cmd_kind::cmd_backtrace},
{"c", cmd_kind::cmd_continue},
{"continue", cmd_kind::cmd_continue},
{"f", cmd_kind::cmd_list_file},
{"file", cmd_kind::cmd_list_file},
{"g", cmd_kind::cmd_global},
{"global", cmd_kind::cmd_global},
{"l", cmd_kind::cmd_local},
{"local", cmd_kind::cmd_local},
{"u", cmd_kind::cmd_upval},
{"upval", cmd_kind::cmd_upval},
{"r", cmd_kind::cmd_register},
{"register", cmd_kind::cmd_register},
{"a", cmd_kind::cmd_show_all},
{"all", cmd_kind::cmd_show_all},
{"n", cmd_kind::cmd_next},
{"next", cmd_kind::cmd_next},
{"bk", cmd_kind::cmd_break_point},
{"break", cmd_kind::cmd_break_point},
{"q", cmd_kind::cmd_exit},
{"exit", cmd_kind::cmd_exit}
};
cmd_kind get_cmd_type(const std::string& cmd) const {
return command_table.count(cmd)?
command_table.at(cmd):cmd_kind::cmd_error;
}
private:
bool next;
usize file_list_size;
u16 break_file_index;
u64 break_line;
error src;
private:
operand_line_counter counter;
bool do_operand_count;
private:
std::vector<std::string> parse(const std::string&);
u16 file_index(const std::string&) const;
void err() const;
void help() const;
void list_file() const;
void step_info();
void interact();
public:
dbg(): next(true), file_list_size(0),
break_file_index(0), break_line(0),
do_operand_count(false) {}
void run(const codegen&,
const linker&,
const std::vector<std::string>&,
bool,
bool);
};
}

199
src/nasal_err.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include "nasal_err.h"
#include "repl/repl.h"
namespace nasal {
#ifdef _WIN32
#include <windows.h> // use SetConsoleTextAttribute
struct for_reset {
CONSOLE_SCREEN_BUFFER_INFO scr;
for_reset() {
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &scr);
}
};
static for_reset windows_system_set;
#endif
std::ostream& back_white(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xf0);
#else
s << "\033[7m";
#endif
return s;
}
std::ostream& red(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0c);
#else
s << "\033[91;1m";
#endif
return s;
}
std::ostream& cyan(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x03);
#else
s << "\033[36;1m";
#endif
return s;
}
std::ostream& orange(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0e);
#else
s << "\033[93;1m";
#endif
return s;
}
std::ostream& white(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0f);
#else
s << "\033[0m\033[1m";
#endif
return s;
}
std::ostream& reset(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(
GetStdHandle(STD_OUTPUT_HANDLE),
windows_system_set.scr.wAttributes
);
#else
s << "\033[0m";
#endif
return s;
}
void filestream::load(const std::string& f) {
if (file==f) { // don't need to load a loaded file
return;
}
// update file name
file = f;
// REPL: load from memory
if (repl::info::instance()->in_repl_mode &&
repl::info::instance()->repl_file_name==file) {
const auto& source = repl::info::instance()->repl_file_source;
res = {};
size_t pos = 0, last = 0;
while ((pos = source.find("\n", last))!=std::string::npos) {
res.push_back(source.substr(last, pos - last));
last = pos + 1;
}
if (last<source.length()) {
res.push_back(source.substr(last));
} else {
res.push_back("");
}
return;
}
res.clear();
std::ifstream in(f, std::ios::binary);
if (in.fail()) {
std::cerr << red << "src: " << reset << "cannot open <" << f << ">\n";
std::exit(1);
}
while(!in.eof()) {
std::string line;
std::getline(in, line);
res.push_back(line);
}
}
void error::err(const std::string& stage, const std::string& info) {
++cnt;
std::cerr << red << stage << ": " << white << info << reset << "\n\n";
}
void error::warn(const std::string& stage, const std::string& info) {
std::clog << orange << stage << ": " << white << info << reset << "\n\n";
}
void error::err(const std::string& stage,
const span& loc,
const std::string& info) {
// load error occurred file into string lines
load(loc.file);
++cnt;
std::cerr << red << stage << ": " << white << info << reset << "\n";
std::cerr << cyan << " --> " << red;
loc.dump_begin(std::cerr);
std::cerr << reset << "\n";
const usize maxlen = std::to_string(loc.end_line).length();
const std::string iden = identation(maxlen);
for(u64 line = loc.begin_line; line<=loc.end_line; ++line) {
// skip line 0
if (!line) {
continue;
}
if (loc.begin_line<line && line<loc.end_line) {
if (line==loc.begin_line+1) {
std::cerr << cyan << iden << " | " << reset << "...\n";
std::cerr << cyan << iden << " | " << reset << "\n";
}
continue;
}
// if this line has nothing, skip
if (!res[line-1].length() && line!=loc.end_line) {
continue;
}
// line out of range
if (line-1>=res.size()) {
continue;
}
const auto& code = res[line-1];
std::cerr << cyan << leftpad(line, maxlen) << " | " << reset << code << "\n";
// output underline
std::cerr << cyan << iden << " | " << reset;
if (loc.begin_line==loc.end_line) {
for(u64 i = 0; i<loc.begin_column; ++i) {
std::cerr << char(" \t"[code[i]=='\t']);
}
for(u64 i = loc.begin_column; i<loc.end_column; ++i) {
std::cerr << red << (code[i]=='\t'? "^^^^":"^") << reset;
}
} else if (line==loc.begin_line) {
for(u64 i = 0; i<loc.begin_column; ++i) {
std::cerr << char(" \t"[code[i]=='\t']);
}
for(u64 i = loc.begin_column; i<code.size(); ++i) {
std::cerr << red << (code[i]=='\t'? "^^^^":"^") << reset;
}
} else if (loc.begin_line<line && line<loc.end_line) {
for(u64 i = 0; i<code.size(); ++i) {
std::cerr << red << (code[i]=='\t'? "^^^^":"^");
}
} else {
for(u64 i = 0; i<loc.end_column; ++i) {
std::cerr << red << (code[i]=='\t'? "^^^^":"^");
}
}
if (line==loc.end_line) {
std::cerr << reset;
} else {
std::cerr << reset << "\n";
}
}
std::cerr << "\n\n";
}
}

75
src/nasal_err.h Normal file
View File

@@ -0,0 +1,75 @@
#pragma once
#include <iostream>
#include <fstream>
#include <sstream> // MSVC need this to use std::getline
#include <cstring>
#include <vector>
#include "nasal.h"
namespace nasal {
struct span {
u64 begin_line;
u64 begin_column;
u64 end_line;
u64 end_column;
std::string file;
void dump_begin(std::ostream& out) const {
out << file << ":" << begin_line << ":" << begin_column + 1;
}
};
std::ostream& back_white(std::ostream&);
std::ostream& red(std::ostream&);
std::ostream& cyan(std::ostream&);
std::ostream& orange(std::ostream&);
std::ostream& white(std::ostream&);
std::ostream& reset(std::ostream&);
class filestream {
protected:
std::string file;
std::vector<std::string> res;
public:
filestream(): file("") {}
void load(const std::string&);
const std::string& operator[](usize n) const {return res[n];}
const auto& name() const {return file;}
const auto& file_content() const {return res;}
usize size() const {return res.size();}
};
class error: public filestream {
private:
u32 cnt; // counter for errors
std::string identation(usize len) {
return std::string(len, ' ');
}
std::string leftpad(u64 num, usize len) {
auto tmp = std::to_string(num);
while(tmp.length()<len) {
tmp = " "+tmp;
}
return tmp;
}
public:
error(): cnt(0) {}
void err(const std::string&, const std::string&);
void warn(const std::string&, const std::string&);
void err(const std::string&, const span&, const std::string&);
void chkerr() const {
if (cnt) {
std::exit(1);
}
}
auto geterr() const { return cnt; }
};
}

443
src/nasal_gc.cpp Normal file
View File

@@ -0,0 +1,443 @@
#include "nasal_gc.h"
#include "util/util.h"
namespace nasal {
void gc::do_mark_sweep() {
using clk = std::chrono::high_resolution_clock;
auto begin = clk::now();
mark();
auto mark_end = clk::now();
sweep();
auto sweep_end = clk::now();
auto total_time = (sweep_end-begin).count();
auto mark_time = (mark_end-begin).count();
auto sweep_time = (sweep_end-mark_end).count();
worktime += total_time;
max_time = max_time<total_time? total_time:max_time;
max_mark_time = max_mark_time<mark_time? mark_time:max_mark_time;
max_sweep_time = max_sweep_time<sweep_time? sweep_time:max_sweep_time;
}
void gc::mark() {
std::vector<var> bfs;
mark_context_root(bfs);
// concurrent mark, experimental
if (memory.size()>UINT16_MAX && bfs.size()>32) {
flag_concurrent_mark_triggered = true;
usize size = bfs.size();
std::thread t0(&gc::concurrent_mark, this, std::ref(bfs), 0, size/4);
std::thread t1(&gc::concurrent_mark, this, std::ref(bfs), size/4, size/2);
std::thread t2(&gc::concurrent_mark, this, std::ref(bfs), size/2, size/4*3);
std::thread t3(&gc::concurrent_mark, this, std::ref(bfs), size/4*3, size);
t0.join();
t1.join();
t2.join();
t3.join();
return;
}
// normal mark
while(!bfs.empty()) {
var value = bfs.back();
bfs.pop_back();
if (value.type<=vm_type::vm_num ||
value.val.gcobj->mark!=nas_val::gc_status::uncollected) {
continue;
}
mark_var(bfs, value);
}
}
void gc::concurrent_mark(std::vector<var>& vec, usize begin, usize end) {
std::vector<var> bfs;
for(auto i = begin; i<end; ++i) {
var value = vec[i];
if (value.type<=vm_type::vm_num ||
value.val.gcobj->mark!=nas_val::gc_status::uncollected) {
continue;
}
mark_var(bfs, value);
}
while(!bfs.empty()) {
var value = bfs.back();
bfs.pop_back();
if (value.type<=vm_type::vm_num ||
value.val.gcobj->mark!=nas_val::gc_status::uncollected) {
continue;
}
mark_var(bfs, value);
}
}
void gc::mark_context_root(std::vector<var>& bfs_queue) {
// scan global
for(usize i = 0; i<main_context_global_size; ++i) {
auto& val = main_context_global[i];
if (val.type>vm_type::vm_num) {
bfs_queue.push_back(val);
}
}
// scan now running context, this context maybe related to coroutine or main
for(var* i = running_context->stack; i<=running_context->top; ++i) {
if (i->type>vm_type::vm_num) {
bfs_queue.push_back(*i);
}
}
bfs_queue.push_back(running_context->funcr);
bfs_queue.push_back(running_context->upvalr);
bfs_queue.push_back(temp);
if (!cort) {
return;
}
// coroutine is running, so scan main process stack from mctx
for(var* i = main_context.stack; i<=main_context.top; ++i) {
if (i->type>vm_type::vm_num) {
bfs_queue.push_back(*i);
}
}
bfs_queue.push_back(main_context.funcr);
bfs_queue.push_back(main_context.upvalr);
}
void gc::mark_var(std::vector<var>& bfs_queue, var& value) {
value.val.gcobj->mark = nas_val::gc_status::found;
switch(value.type) {
case vm_type::vm_vec: mark_vec(bfs_queue, value.vec()); break;
case vm_type::vm_hash: mark_hash(bfs_queue, value.hash()); break;
case vm_type::vm_func: mark_func(bfs_queue, value.func()); break;
case vm_type::vm_upval: mark_upval(bfs_queue, value.upval()); break;
case vm_type::vm_ghost: mark_ghost(bfs_queue, value.ghost()); break;
case vm_type::vm_co: mark_co(bfs_queue, value.co()); break;
case vm_type::vm_map: mark_map(bfs_queue, value.map()); break;
default: break;
}
}
void gc::mark_vec(std::vector<var>& bfs_queue, nas_vec& vec) {
for(auto& i : vec.elems) {
if (i.type>vm_type::vm_num) {
bfs_queue.push_back(i);
}
}
}
void gc::mark_hash(std::vector<var>& bfs_queue, nas_hash& hash) {
for(auto& i : hash.elems) {
if (i.second.type>vm_type::vm_num) {
bfs_queue.push_back(i.second);
}
}
}
void gc::mark_func(std::vector<var>& bfs_queue, nas_func& function) {
for(auto& i : function.local) {
if (i.type>vm_type::vm_num) {
bfs_queue.push_back(i);
}
}
for(auto& i : function.upval) {
bfs_queue.push_back(i);
}
}
void gc::mark_upval(std::vector<var>& bfs_queue, nas_upval& upval) {
for(auto& i : upval.elems) {
if (i.type>vm_type::vm_num) {
bfs_queue.push_back(i);
}
}
}
void gc::mark_ghost(std::vector<var>& bfs_queue, nas_ghost& ghost) {
if (!ghost.gc_mark_function) {
return;
}
ghost.gc_mark_function(ghost.pointer, &bfs_queue);
}
void gc::mark_co(std::vector<var>& bfs_queue, nas_co& co) {
bfs_queue.push_back(co.ctx.funcr);
bfs_queue.push_back(co.ctx.upvalr);
for(var* i = co.ctx.stack; i<=co.ctx.top; ++i) {
if (i->type>vm_type::vm_num) {
bfs_queue.push_back(*i);
}
}
}
void gc::mark_map(std::vector<var>& bfs_queue, nas_map& mp) {
for(const auto& i : mp.mapper) {
if (i.second->type>vm_type::vm_num) {
bfs_queue.push_back(*i.second);
}
}
}
void gc::sweep() {
for(auto i : memory) {
if (i->mark==nas_val::gc_status::uncollected) {
i->clear();
unused[static_cast<u8>(i->type)-static_cast<u8>(vm_type::vm_str)].push_back(i);
i->mark = nas_val::gc_status::collected;
} else if (i->mark==nas_val::gc_status::found) {
i->mark = nas_val::gc_status::uncollected;
}
}
}
void gc::extend(const vm_type type) {
const u8 index = static_cast<u8>(type)-static_cast<u8>(vm_type::vm_str);
size[index] += incr[index];
for(u64 i = 0; i<incr[index]; ++i) {
// no need to check, will be killed if memory is not enough
nas_val* tmp = new nas_val(type);
// add to heap
memory.push_back(tmp);
unused[index].push_back(tmp);
}
// if incr[index] = 1, this will always be 1
incr[index] = incr[index]+incr[index]/2;
}
void gc::init(const std::vector<std::string>& constant_strings,
const std::vector<std::string>& argv) {
// initialize counters
worktime = 0;
for(u8 i = 0; i<gc_type_size; ++i) {
size[i] = gcnt[i] = acnt[i] = 0;
}
// coroutine pointer set to nullptr
cort = nullptr;
// init constant strings
strs.resize(constant_strings.size());
for(u64 i = 0; i<strs.size(); ++i) {
// incremental initialization, avoid memory leak in repl mode
if (strs[i].is_str() && strs[i].str()==constant_strings[i]) {
continue;
}
strs[i] = var::gcobj(new nas_val(vm_type::vm_str));
strs[i].val.gcobj->immutable = 1;
strs[i].str() = constant_strings[i];
}
// record arguments
env_argv.resize(argv.size());
for(u64 i = 0; i<argv.size(); ++i) {
// incremental initialization, avoid memory leak in repl mode
if (env_argv[i].is_str() && env_argv[i].str()==argv[i]) {
continue;
}
env_argv[i] = var::gcobj(new nas_val(vm_type::vm_str));
env_argv[i].val.gcobj->immutable = 1;
env_argv[i].str() = argv[i];
}
}
void gc::clear() {
for(auto i : memory) {
delete i;
}
memory.clear();
for(u8 i = 0; i<gc_type_size; ++i) {
unused[i].clear();
}
for(auto& i : strs) {
delete i.val.gcobj;
}
strs.clear();
env_argv.clear();
}
void gc::info() const {
util::windows_code_page_manager wm;
wm.set_utf8_output();
using std::left;
using std::setw;
using std::setfill;
using std::setprecision;
const char* used_table_name[] = {
"object type",
"gc count",
"alloc count",
"memory size",
"detail",
"time spend",
"gc time",
"avg time",
"max gc",
"max mark",
"max sweep",
nullptr
};
const char* name[] = {
"string",
"vector",
"hashmap",
"function",
"upvalue",
"ghost",
"coroutine",
"namespace",
nullptr
};
usize indent = 0, len = 0;
for(usize i = 0; used_table_name[i]; ++i) {
len = std::string(used_table_name[i]).length();
indent = indent<len? len:indent;
}
for(usize i = 0; name[i]; ++i) {
len = std::string(name[i]).length();
indent = indent<len? len:indent;
}
for(u32 i = 0; i<gc_type_size; ++i) {
len = std::to_string(gcnt[i]).length();
indent = indent<len? len:indent;
len = std::to_string(acnt[i]).length();
indent = indent<len? len:indent;
len = std::to_string(size[i]).length();
indent = indent<len? len:indent;
}
auto indent_string = std::string("──");
for(usize i = 0; i<indent; ++i) {
indent_string += "";
}
const auto first_line = "" + indent_string + "" +
indent_string + "" +
indent_string + "" +
indent_string + "";
const auto mid_line = "" + indent_string + "" +
indent_string + "" +
indent_string + "" +
indent_string + "";
const auto another_mid_line = "" + indent_string + "" +
indent_string + "" +
indent_string + "" +
indent_string + "";
const auto last_line = "" + indent_string + "" +
indent_string + "" +
indent_string + "" +
indent_string + "";
std::clog << "\n" << first_line << "\n";
std::clog << "" << left << setw(indent) << setfill(' ') << "object type";
std::clog << "" << left << setw(indent) << setfill(' ') << "gc count";
std::clog << "" << left << setw(indent) << setfill(' ') << "alloc count";
std::clog << "" << left << setw(indent) << setfill(' ') << "memory size";
std::clog << "\n" << mid_line << "\n";
double total = 0;
for(u8 i = 0; i<gc_type_size; ++i) {
if (!gcnt[i] && !acnt[i] && !size[i]) {
continue;
}
total += static_cast<f64>(gcnt[i]);
std::clog << "" << left << setw(indent) << setfill(' ') << name[i];
std::clog << "" << left << setw(indent) << setfill(' ') << gcnt[i];
std::clog << "" << left << setw(indent) << setfill(' ') << acnt[i];
std::clog << "" << left << setw(indent) << setfill(' ') << size[i];
std::clog << "\n";
}
std::clog << mid_line << "\n";
const auto den = std::chrono::high_resolution_clock::duration::period::den;
std::clog << "" << left << setw(indent) << setfill(' ') << "detail";
std::clog << "" << left << setw(indent) << setfill(' ') << "time spend";
std::clog << "" << left << setw(indent) << setfill('x') << "x";
std::clog << "" << left << setw(indent) << setfill('x') << "x";
std::clog << "\n" << another_mid_line << "\n";
const auto gc_time = worktime*1.0/den*1000;
std::clog << "" << left << setw(indent) << setfill(' ') << "gc time";
std::clog << "" << setw(indent-3) << setprecision(4) << gc_time << " ms";
std::clog << setw(indent*2+7) << " " << "\n";
const auto avg_time = worktime*1.0/den*1000/total;
std::clog << "" << left << setw(indent) << setfill(' ') << "avg time";
std::clog << "" << setw(indent-3) << setprecision(4) << avg_time << " ms";
std::clog << setw(indent*2+7) << " " << "\n";
const auto max_gc = max_time*1.0/den*1000;
std::clog << "" << left << setw(indent) << setfill(' ') << "max gc";
std::clog << "" << setw(indent-3) << setprecision(4) << max_gc << " ms";
std::clog << setw(indent*2+7) << " " << "\n";
const auto max_mark = max_mark_time*1.0/den*1000;
std::clog << "" << left << setw(indent) << setfill(' ') << "max mark";
std::clog << "" << setw(indent-3) << setprecision(4) << max_mark << " ms";
std::clog << setw(indent*2+7) << " " << "\n";
const auto max_sweep = max_sweep_time*1.0/den*1000;
std::clog << "" << left << setw(indent) << setfill(' ') << "max sweep";
std::clog << "" << setw(indent-3) << setprecision(4) << max_sweep << " ms";
std::clog << setw(indent*2+7) << " " << "\n";
std::clog << "" << left << setw(indent) << setfill(' ') << "concurrent";
std::clog << "" << setw(indent)
<< (flag_concurrent_mark_triggered? "true":"false");
std::clog << setw(indent*2+7) << " " << "\n";
std::clog << last_line << "\n";
wm.restore_code_page();
}
var gc::alloc(const vm_type type) {
const u8 index = static_cast<u8>(type)-static_cast<u8>(vm_type::vm_str);
++acnt[index];
if (unused[index].empty()) {
++gcnt[index];
do_mark_sweep();
}
if (unused[index].empty()) {
extend(type);
}
var ret = var::gcobj(unused[index].back());
ret.val.gcobj->mark = nas_val::gc_status::uncollected;
unused[index].pop_back();
return ret;
}
void gc::context_change(nas_co* co) {
// store running state to main context
main_context = *running_context;
// restore coroutine context state
*running_context = co->ctx;
// set coroutine pointer
cort = co;
// set coroutine state to running
cort->status = nas_co::status::running;
}
void gc::context_reserve() {
// pc = 0 means this coroutine is finished
cort->status = running_context->pc?
nas_co::status::suspended:
nas_co::status::dead;
// store running state to coroutine
cort->ctx = *running_context;
// restore main context state
*running_context = main_context;
// set coroutine pointer to nullptr
cort = nullptr;
}
}

138
src/nasal_gc.h Normal file
View File

@@ -0,0 +1,138 @@
#pragma once
// avoid MSVC warnings
#ifdef _MSC_VER
#pragma warning (disable:4244)
#pragma warning (disable:4267)
#pragma warning (disable:4102)
#endif
#include <iomanip>
#include <vector>
#include <chrono>
#include <thread>
#include <cstring>
#include <sstream>
#include <iostream>
#include "nasal.h"
#include "nasal_type.h"
namespace nasal {
struct gc {
/* main context temporary storage */
context main_context;
/* global storage */
var* main_context_global = nullptr;
usize main_context_global_size = 0;
/* runtime context */
context* running_context = nullptr;
nas_co* cort = nullptr; // running coroutine
/* temporary space used in native/module functions */
var temp = nil;
/* constants and memory pool */
std::vector<var> strs = {}; // reserved address for const vm_str
std::vector<var> env_argv = {}; // command line arguments
std::vector<nas_val*> memory; // gc memory
std::vector<nas_val*> unused[gc_type_size]; // gc free list
/* heap increase size */
u64 incr[gc_type_size] = {
128, // vm_str
128, // vm_vec
64, // vm_hash
256, // vm_func
256, // vm_upval
16, // vm_obj
16, // vm_co
2, // vm_map
};
/* values for analysis */
u64 size[gc_type_size];
u64 gcnt[gc_type_size];
u64 acnt[gc_type_size];
i64 worktime = 0;
i64 max_time = 0;
i64 max_mark_time = 0;
i64 max_sweep_time = 0;
bool flag_concurrent_mark_triggered = false;
void set(context* _ctx, var* _global, usize _size) {
running_context = _ctx;
main_context_global = _global;
main_context_global_size = _size;
}
private:
/* gc functions */
void do_mark_sweep();
void mark();
void concurrent_mark(std::vector<var>&, usize, usize);
void mark_context_root(std::vector<var>&);
void mark_var(std::vector<var>&, var&);
void mark_vec(std::vector<var>&, nas_vec&);
void mark_hash(std::vector<var>&, nas_hash&);
void mark_func(std::vector<var>&, nas_func&);
void mark_upval(std::vector<var>&, nas_upval&);
void mark_ghost(std::vector<var>&, nas_ghost&);
void mark_co(std::vector<var>&, nas_co&);
void mark_map(std::vector<var>&, nas_map&);
void sweep();
public:
void extend(const vm_type);
void init(const std::vector<std::string>&, const std::vector<std::string>&);
void clear();
void info() const;
var alloc(const vm_type);
void context_change(nas_co*);
void context_reserve();
public:
var newstr(char c) {
var s = alloc(vm_type::vm_str);
s.str() = c;
return s;
}
var newstr(const char* buff) {
var s = alloc(vm_type::vm_str);
s.str() = std::string(buff);
return s;
}
var newstr(const std::string& buff) {
var s = alloc(vm_type::vm_str);
s.str() = buff;
return s;
}
};
// module function type
typedef var (*module_func)(var*, usize, gc*);
// module function stores in tables with this type, end with {nullptr, nullptr}
struct module_func_info {
const char* name;
module_func fd;
};
// module function "get" type
typedef module_func_info* (*get_func_ptr)();
// avoid error loading function bug in MSVC version nasal.exe
#ifdef _MSC_VER
// and fuck MSVC again
#define NASAL_EXTERN extern "C" __declspec(dllexport)
#else
#define NASAL_EXTERN extern "C"
#endif
}

421
src/nasal_import.cpp Normal file
View File

@@ -0,0 +1,421 @@
#include "nasal_import.h"
#include "symbol_finder.h"
#include "util/util.h"
#include "util/fs.h"
#include <memory>
#include <unordered_set>
namespace nasal {
linker::linker(): show_path_flag(false), this_file("") {
const auto seperator = util::is_windows()? ';':':';
const auto PATH = std::string(getenv("PATH"));
usize last = 0, position = PATH.find(seperator, 0);
while(position!=std::string::npos) {
std::string dirpath = PATH.substr(last, position-last);
if (dirpath.length()) {
envpath.push_back(dirpath);
}
last = position+1;
position = PATH.find(seperator, last);
}
if (last!=PATH.length()) {
envpath.push_back(PATH.substr(last));
}
}
std::string linker::get_path(expr* node) {
if (node->get_type()==expr_type::ast_use) {
auto file_relative_path = std::string("");
const auto& path = reinterpret_cast<use_stmt*>(node)->get_path();
for(auto i : path) {
file_relative_path += i->get_name();
if (i!=path.back()) {
file_relative_path += (util::is_windows()? "\\":"/");
}
}
return file_relative_path + ".nas";
}
auto call_node = reinterpret_cast<call_expr*>(node);
auto arguments = reinterpret_cast<call_function*>(call_node->get_calls()[0]);
auto content = reinterpret_cast<string_literal*>(arguments->get_argument()[0]);
return content->get_content();
}
std::string linker::find_real_file_path(const std::string& filename,
const span& location) {
// first add file name itself into the file path
std::vector<fs::path> path_list = {filename};
// generate search path from environ path
for(const auto& p : envpath) {
path_list.push_back(fs::path(p)/filename);
}
// search file
for(const auto& path : path_list) {
if (fs::exists(path)) {
return path.str();
}
}
// we will find lib.nas in nasal std directory
if (filename=="lib.nas") {
return util::is_windows()?
find_real_file_path("std\\lib.nas", location):
find_real_file_path("std/lib.nas", location);
}
if (!show_path_flag) {
err.err("link",
"in <" + location.file + ">: " +
"cannot find file <" + filename + ">, " +
"use <-d> to get detail search path"
);
return "";
}
auto path_list_info = std::string("");
for(const auto& path : path_list) {
path_list_info += " -> " + path.str() + "\n";
}
err.err("link",
"in <" + location.file + ">: " +
"cannot find file <" + filename +
"> in these paths:\n" + path_list_info
);
return "";
}
bool linker::import_check(expr* node) {
if (node->get_type()==expr_type::ast_use) {
return true;
}
/*
call
|_id:import
|_call_func
|_string:'filename'
*/
if (node->get_type()!=expr_type::ast_call) {
return false;
}
auto call_node = reinterpret_cast<call_expr*>(node);
auto first_expr = call_node->get_first();
if (first_expr->get_type()!=expr_type::ast_id) {
return false;
}
if (reinterpret_cast<identifier*>(first_expr)->get_name()!="import") {
return false;
}
if (!call_node->get_calls().size()) {
return false;
}
// import("xxx");
if (call_node->get_calls().size()!=1) {
return false;
}
auto maybe_func_call = call_node->get_calls()[0];
if (maybe_func_call->get_type()!=expr_type::ast_callf) {
return false;
}
auto func_call = reinterpret_cast<call_function*>(maybe_func_call);
if (func_call->get_argument().size()!=1) {
return false;
}
if (func_call->get_argument()[0]->get_type()!=expr_type::ast_str) {
return false;
}
return true;
}
bool linker::check_exist_or_record_file(const std::string& file) {
// avoid importing the same file
for(const auto& name : imported_files) {
if (file==name) {
return true;
}
}
imported_files.push_back(file);
return false;
}
bool linker::check_self_import(const std::string& file) {
for(const auto& name : module_load_stack) {
if (file==name) {
return true;
}
}
return false;
}
std::string linker::generate_self_import_path(const std::string& filename) {
std::string res = "";
for(const auto& i : module_load_stack) {
res += "[" + i + "] -> ";
}
return res + "[" + filename + "]";
}
void linker::merge_tree(code_block* new_tree_root, code_block* old_tree_root) {
// add children of add_root to the back of root
for(auto& i : old_tree_root->get_expressions()) {
new_tree_root->add_expression(i);
}
// clean old root
old_tree_root->get_expressions().clear();
}
code_block* linker::import_regular_file(
expr* node, std::unordered_set<std::string>& used_modules) {
// get filename
auto filename = get_path(node);
// avoid infinite loading loop
filename = find_real_file_path(filename, node->get_location());
// if get empty string(error) or this file is used before, do not parse
if (!filename.length() || used_modules.count(filename)) {
return new code_block({0, 0, 0, 0, filename});
}
// check self import, avoid infinite loading loop
if (check_self_import(filename)) {
err.err("link",
node->get_location(),
"self-referenced module <" + filename + ">, " +
"reference path: " + generate_self_import_path(filename)
);
return new code_block({0, 0, 0, 0, filename});
}
check_exist_or_record_file(filename);
module_load_stack.push_back(filename);
// avoid stack overflow
if (module_load_stack.size()>MAX_RECURSION_DEPTH) {
err.err("link",
node->get_location(),
"too deep module import stack (>" +
std::to_string(MAX_RECURSION_DEPTH) + ")."
);
return new code_block({0, 0, 0, 0, filename});
}
// start importing...
lexer nasal_lexer;
parse nasal_parser;
if (nasal_lexer.scan(filename).geterr()) {
err.err("link",
node->get_location(),
"error occurred when analysing <" + filename + ">"
);
return new code_block({0, 0, 0, 0, filename});
}
if (nasal_parser.compile(nasal_lexer).geterr()) {
err.err("link",
node->get_location(),
"error occurred when analysing <" + filename + ">"
);
return new code_block({0, 0, 0, 0, filename});
}
// swap result out
auto parse_result = nasal_parser.swap(nullptr);
// check if parse result has 'import'
load(parse_result, filename);
module_load_stack.pop_back();
return parse_result;
}
code_block* linker::import_nasal_lib() {
auto path = find_real_file_path(
"lib.nas", {0, 0, 0, 0, this_file}
);
if (!path.length()) {
return new code_block({0, 0, 0, 0, path});
}
// avoid infinite loading library
if (check_exist_or_record_file(path)) {
return new code_block({0, 0, 0, 0, path});
}
// start importing...
lexer nasal_lexer;
parse nasal_parser;
if (nasal_lexer.scan(path).geterr()) {
err.err("link",
"error occurred when analysing library <" + path + ">"
);
return new code_block({0, 0, 0, 0, path});
}
if (nasal_parser.compile(nasal_lexer).geterr()) {
err.err("link",
"error occurred when analysing library <" + path + ">"
);
return new code_block({0, 0, 0, 0, path});
}
// swap result out
auto parse_result = nasal_parser.swap(nullptr);
// check if library has 'import' (in fact it should not)
load(parse_result, path);
return parse_result;
}
std::string linker::generate_module_name(const std::string& file_path) {
// import("...") may trigger this error module name
auto error_name = "module@[" + file_path + "]";
if (!file_path.length()) {
return error_name;
}
// check file suffix and get file suffix position
auto suffix_position = file_path.find(".nas");
if (suffix_position==std::string::npos) {
err.warn("link",
"get invalid module name from <" + file_path + ">, " +
"will not be easily accessed. " +
"\".nas\" suffix is required."
);
return error_name;
}
if (suffix_position+4!=file_path.length()) {
err.warn("link",
"get invalid module name from <" + file_path + ">, " +
"will not be easily accessed. " +
"only one \".nas\" suffix is required in the path."
);
return error_name;
}
// only get the file name as module name, directory path is not included
auto split_position = file_path.find_last_of("/");
// find "\\" in windows platform
if (split_position==std::string::npos) {
split_position = file_path.find_last_of("\\");
}
// split file path to get module name
auto module_name = split_position==std::string::npos?
file_path.substr(0, suffix_position):
file_path.substr(split_position+1, suffix_position-split_position-1);
// check validation of module name
if (!module_name.length()) {
err.warn("link",
"get empty module name from <" + file_path + ">, " +
"will not be easily accessed."
);
return module_name;
}
if (std::isdigit(module_name[0]) ||
module_name.find(".")!=std::string::npos ||
module_name.find("-")!=std::string::npos) {
err.warn("link",
"get module <" + module_name + "> from <" + file_path + ">, " +
"will not be easily accessed."
);
}
return module_name;
}
return_expr* linker::generate_module_return(code_block* block) {
auto finder = std::make_unique<symbol_finder>();
auto result = new return_expr(block->get_location());
auto value = new hash_expr(block->get_location());
result->set_value(value);
for(const auto& i : finder->do_find(block)) {
auto pair = new hash_pair(block->get_location());
// do not export symbol begins with '_'
if (i.name.length() && i.name[0]=='_') {
continue;
}
pair->set_name(i.name);
pair->set_value(new identifier(block->get_location(), i.name));
value->add_member(pair);
}
return result;
}
definition_expr* linker::generate_module_definition(code_block* block) {
// generate ast node like this:
// var {module_name} = (func() {
// ... # module itself
// })();
auto def = new definition_expr(block->get_location());
def->set_identifier(new identifier(
block->get_location(),
generate_module_name(block->get_location().file)
));
// (func() {...})();
auto call = new call_expr(block->get_location());
// func() {...}
auto func = new function(block->get_location());
func->set_code_block(block);
func->get_code_block()->add_expression(generate_module_return(block));
call->set_first(func);
call->add_call(new call_function(block->get_location()));
def->set_value(call);
return def;
}
void linker::load(code_block* program_root, const std::string& filename) {
// load imported modules
std::unordered_set<std::string> used_modules = {};
for(auto& import_node : program_root->get_expressions()) {
if (!import_check(import_node)) {
break;
}
// parse file and get ast
auto module_code_block = import_regular_file(import_node, used_modules);
// avoid repeatedly importing the same module in one file
const auto& module_path = module_code_block->get_location().file;
if (used_modules.count(module_path)) {
delete module_code_block;
auto replace_node = new null_expr(import_node->get_location());
// after importing the regular file as module, delete this node
delete import_node;
// and replace the node with null_expr node
import_node = replace_node;
continue;
}
used_modules.insert(module_path);
delete import_node;
// then we generate a function warping the code block,
// and export the necessary global symbols in this code block
// by generate a return statement, with a hashmap return value
import_node = generate_module_definition(module_code_block);
}
}
const error& linker::link(parse& parse, bool spath = false) {
// switch for showing path when errors occur
show_path_flag = spath;
// initializing file map
this_file = parse.tree()->get_location().file;
imported_files = {this_file};
module_load_stack = {this_file};
// scan root and import files
// then generate a new ast and return to import_ast
// dfs load file
auto library = import_nasal_lib();
// load used modules of this file
load(parse.tree(), this_file);
// then insert the whole tree into library tree root
merge_tree(library, parse.tree());
// swap tree root, and delete old root
delete parse.swap(library);
if (imported_files.size()>=UINT16_MAX) {
err.err("link",
"too many imported files: " +
std::to_string(imported_files.size())
);
}
return err;
}
}

56
src/nasal_import.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#ifndef _MSC_VER
#include <unistd.h>
#else
#define _CRT_SECURE_NO_DEPRECATE 1
#define _CRT_NONSTDC_NO_DEPRECATE 1
#include <io.h>
#endif
#include "nasal.h"
#include "nasal_ast.h"
#include "nasal_lexer.h"
#include "nasal_parse.h"
#include "symbol_finder.h"
#include "util/fs.h"
#include <cstring>
#include <sstream>
#include <vector>
#include <unordered_set>
namespace nasal {
class linker {
private:
const u32 MAX_RECURSION_DEPTH = 256;
bool show_path_flag;
std::string this_file;
error err;
std::vector<std::string> imported_files;
std::vector<std::string> module_load_stack;
std::vector<fs::path> envpath;
private:
bool import_check(expr*);
bool check_exist_or_record_file(const std::string&);
bool check_self_import(const std::string&);
std::string generate_self_import_path(const std::string&);
void merge_tree(code_block*, code_block*);
std::string get_path(expr*);
std::string find_real_file_path(const std::string&, const span&);
code_block* import_regular_file(expr*, std::unordered_set<std::string>&);
code_block* import_nasal_lib();
std::string generate_module_name(const std::string&);
return_expr* generate_module_return(code_block*);
definition_expr* generate_module_definition(code_block*);
void load(code_block*, const std::string&);
public:
linker();
const error& link(parse&, bool);
const auto& get_file_list() const {return imported_files;}
};
}

458
src/nasal_lexer.cpp Normal file
View File

@@ -0,0 +1,458 @@
#ifdef _MSC_VER
#pragma warning (disable:4244)
#pragma warning (disable:4267)
#pragma warning (disable:4102)
#endif
#include "nasal_lexer.h"
#include "repl/repl.h"
#include "util/util.h"
#include "util/fs.h"
namespace nasal {
bool lexer::skip(char c) {
return c==' ' || c=='\n' || c=='\t' || c=='\r' || c==0;
}
bool lexer::is_id(char c) {
return (c=='_') || std::isalpha(c) || (c<0);
}
bool lexer::is_hex(char c) {
return std::isxdigit(c);
}
bool lexer::is_oct(char c) {
return '0'<=c && c<='7';
}
bool lexer::is_dec(char c) {
return std::isdigit(c);
}
bool lexer::is_str(char c) {
return c=='\'' || c=='\"' || c=='`';
}
bool lexer::is_quesmark(char c) {
return c=='?';
}
bool lexer::is_single_opr(char c) {
return (
c=='(' || c==')' || c=='[' || c==']' ||
c=='{' || c=='}' || c==',' || c==';' ||
c==':' || c=='`' || c=='@' || c=='%' ||
c=='$' || c=='\\'
);
}
bool lexer::is_calc_opr(char c) {
return (
c=='=' || c=='+' || c=='-' || c=='*' ||
c=='!' || c=='/' || c=='<' || c=='>' ||
c=='~' || c=='|' || c=='&' || c=='^'
);
}
void lexer::skip_note() {
// avoid note, after this process ptr will point to '\n'
// so next loop line counter+1
while(++ptr<res.size() && res[ptr]!='\n') {}
}
void lexer::err_char() {
++column;
char c = res[ptr++];
err.err("lexer",
{line, column-1, line, column, filename},
"invalid character 0x" + util::char_to_hex(c)
);
++invalid_char;
}
void lexer::open(const std::string& file) {
if (repl::info::instance()->in_repl_mode &&
repl::info::instance()->repl_file_name==file) {
err.load(file);
filename = file;
res = repl::info::instance()->repl_file_source;
return;
}
if (file.empty()) {
err.err("lexer", "empty input file");
err.chkerr();
}
// check file exsits and it is a regular file
if (!fs::is_regular(file)) {
err.err("lexer", "<"+file+"> is not a regular file");
err.chkerr();
}
// load
filename = file;
std::ifstream in(file, std::ios::binary);
if (in.fail()) {
err.err("lexer", "failed to open <" + file + ">");
res = "";
return;
}
err.load(file);
std::stringstream ss;
ss << in.rdbuf();
res = ss.str();
}
tok lexer::get_type(const std::string& str) {
// search token type from mapper
// if cannot find, just return null
return token_mapper.count(str)? token_mapper.at(str):tok::tk_null;
}
std::string lexer::utf8_gen() {
std::string str = "";
while(ptr<res.size() && res[ptr]<0) {
std::string tmp = "";
u32 nbytes = util::utf8_hdchk(res[ptr]);
if (!nbytes) {
++ptr;
++column;
continue;
}
tmp += res[ptr++];
for(u32 i = 0; i<nbytes; ++i, ++ptr) {
if (ptr<res.size() && (res[ptr]&0xc0)==0x80) {
tmp += res[ptr];
}
}
// utf8 character's total length is 1+nbytes
if (tmp.length()!=1+nbytes) {
++column;
std::string utf_info = "0x" + util::char_to_hex(tmp[0]);
for(u32 i = 1; i<tmp.size(); ++i) {
utf_info += " 0x" + util::char_to_hex(tmp[i]);
}
err.err("lexer",
{line, column-1, line, column, filename},
"invalid utf-8 <"+utf_info+">"
);
++invalid_char;
}
str += tmp;
// may have some problems because not all the unicode takes 2 space
column += 2;
}
return str;
}
token lexer::id_gen() {
u64 begin_line = line;
u64 begin_column = column;
std::string str = "";
while(ptr<res.size() && (is_id(res[ptr]) || is_dec(res[ptr]))) {
if (res[ptr]<0) { // utf-8
str += utf8_gen();
} else { // ascii
str += res[ptr++];
++column;
}
}
tok type = get_type(str);
return {
{begin_line, begin_column, line, column, filename},
(type!=tok::tk_null)? type:tok::tk_id,
str
};
}
token lexer::num_gen() {
u64 begin_line = line;
u64 begin_column = column;
// generate hex number
if (ptr+1<res.size() && res[ptr]=='0' && res[ptr+1]=='x') {
std::string str = "0x";
ptr += 2;
while(ptr<res.size() && is_hex(res[ptr])) {
str += res[ptr++];
}
column += str.length();
// "0x"
if (str.length()<3) {
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"invalid number `"+str+"`"
);
}
return {
{begin_line, begin_column, line, column, filename},
tok::tk_num,
str
};
} else if (ptr+1<res.size() && res[ptr]=='0' && res[ptr+1]=='o') { // generate oct number
std::string str = "0o";
ptr += 2;
while(ptr<res.size() && is_oct(res[ptr])) {
str += res[ptr++];
}
bool erfmt = false;
while(ptr<res.size() && (is_dec(res[ptr]) || is_hex(res[ptr]))) {
erfmt = true;
str += res[ptr++];
}
column += str.length();
if (str.length()==2 || erfmt) {
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"invalid number `"+str+"`"
);
}
return {
{begin_line, begin_column, line, column, filename},
tok::tk_num,
str
};
}
// generate dec number
// dec number -> [0~9][0~9]*(.[0~9]*)(e|E(+|-)0|[1~9][0~9]*)
std::string str = "";
while(ptr<res.size() && is_dec(res[ptr])) {
str += res[ptr++];
}
if (ptr<res.size() && res[ptr]=='.') {
str += res[ptr++];
while(ptr<res.size() && is_dec(res[ptr])) {
str += res[ptr++];
}
// "xxxx." is not a correct number
if (str.back()=='.') {
column += str.length();
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"invalid number `"+str+"`"
);
return {
{begin_line, begin_column, line, column, filename},
tok::tk_num,
"0"
};
}
}
if (ptr<res.size() && (res[ptr]=='e' || res[ptr]=='E')) {
str += res[ptr++];
if (ptr<res.size() && (res[ptr]=='-' || res[ptr]=='+')) {
str += res[ptr++];
}
while(ptr<res.size() && is_dec(res[ptr])) {
str += res[ptr++];
}
// "xxxe(-|+)" is not a correct number
if (str.back()=='e' || str.back()=='E' || str.back()=='-' || str.back()=='+') {
column += str.length();
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"invalid number `"+str+"`"
);
return {
{begin_line, begin_column, line, column, filename},
tok::tk_num,
"0"
};
}
}
column += str.length();
return {
{begin_line, begin_column, line, column, filename},
tok::tk_num,
str
};
}
token lexer::str_gen() {
u64 begin_line = line;
u64 begin_column = column;
std::string str = "";
const char begin = res[ptr];
++column;
while(++ptr<res.size() && res[ptr]!=begin) {
++column;
if (res[ptr]=='\n') {
column = 0;
++line;
}
if (res[ptr]=='\\' && ptr+1<res.size()) {
++column;
++ptr;
switch(res[ptr]) {
case '0': str += '\0'; break;
case 'a': str += '\a'; break;
case 'b': str += '\b'; break;
case 'e': str += '\033'; break;
case 't': str += '\t'; break;
case 'n': str += '\n'; break;
case 'v': str += '\v'; break;
case 'f': str += '\f'; break;
case 'r': str += '\r'; break;
case '?': str += '\?'; break;
case '\\':str += '\\'; break;
case '\'':str += '\''; break;
case '\"':str += '\"'; break;
default: str += res[ptr];break;
}
if (res[ptr]=='\n') {
column = 0;
++line;
}
continue;
}
str += res[ptr];
}
// check if this string ends with a " or '
if (ptr++>=res.size()) {
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"get EOF when generating string"
);
return {
{begin_line, begin_column, line, column, filename},
tok::tk_str,
str
};
}
++column;
// if is not utf8, 1+utf8_hdchk should be 1
if (begin=='`' && str.length()!=1+util::utf8_hdchk(str[0])) {
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"\'`\' is used for string including one character"
);
}
return {
{begin_line, begin_column, line, column, filename},
tok::tk_str,
str
};
}
token lexer::quesmark_gen() {
u64 begin_line = line;
u64 begin_column = column;
std::string str(1, res[ptr]);
++column;
++ptr;
if (ptr < res.size() && (res[ptr]=='?' || res[ptr]=='.')) {
str += res[ptr];
++column;
++ptr;
}
return {
{begin_line, begin_column, line, column, filename},
get_type(str),
str
};
}
token lexer::single_opr() {
u64 begin_line = line;
u64 begin_column = column;
std::string str(1, res[ptr]);
++column;
tok type = get_type(str);
if (type==tok::tk_null) {
err.err("lexer",
{begin_line, begin_column, line, column, filename},
"invalid operator `"+str+"`"
);
}
++ptr;
return {{begin_line, begin_column, line, column, filename}, type, str};
}
token lexer::dots() {
u64 begin_line = line;
u64 begin_column = column;
std::string str = ".";
if (ptr+2<res.size() && res[ptr+1]=='.' && res[ptr+2]=='.') {
str += "..";
}
ptr += str.length();
column += str.length();
return {{begin_line, begin_column, line, column, filename}, get_type(str), str};
}
token lexer::calc_opr() {
u64 begin_line = line;
u64 begin_column = column;
// get calculation operator
std::string str(1, res[ptr++]);
if (ptr<res.size() && res[ptr]=='=') {
str += res[ptr++];
}
column += str.length();
return {{begin_line, begin_column, line, column, filename}, get_type(str), str};
}
const error& lexer::scan(const std::string& file) {
line = 1;
column = 0;
ptr = 0;
toks = {};
open(file);
while(ptr<res.size()) {
while(ptr<res.size() && skip(res[ptr])) {
// these characters will be ignored, and '\n' will cause ++line
++column;
if (res[ptr++]=='\n') {
++line;
column = 0;
}
}
if (ptr>=res.size()) {
break;
}
if (is_id(res[ptr])) {
toks.push_back(id_gen());
} else if (is_dec(res[ptr])) {
toks.push_back(num_gen());
} else if (is_str(res[ptr])) {
toks.push_back(str_gen());
} else if (is_quesmark(res[ptr])) {
toks.push_back(quesmark_gen());
} else if (is_single_opr(res[ptr])) {
toks.push_back(single_opr());
} else if (res[ptr]=='.') {
toks.push_back(dots());
} else if (is_calc_opr(res[ptr])) {
toks.push_back(calc_opr());
} else if (res[ptr]=='#') {
skip_note();
} else {
err_char();
}
if (invalid_char>10) {
err.err("lexer", "too many invalid characters, stop");
break;
}
}
if (toks.size()) {
// eof token's location is the last token's location
toks.push_back({toks.back().loc, tok::tk_eof, "<eof>"});
} else {
// if token sequence is empty, generate a default location
toks.push_back({
{line, column, line, column, filename},
tok::tk_eof,
"<eof>"
});
}
res = "";
return err;
}
}

198
src/nasal_lexer.h Normal file
View File

@@ -0,0 +1,198 @@
#pragma once
#ifdef _MSC_VER
#pragma warning (disable:4244)
#pragma warning (disable:4267)
#pragma warning (disable:4102)
#endif
#include <cstring>
#include <sstream>
#include <vector>
#include <unordered_map>
#include "nasal.h"
#include "nasal_err.h"
namespace nasal {
enum class tok {
tk_null = 0, // null token (default token type)
tk_num, // number literal
tk_str, // string literal
tk_id, // identifier
tk_true, // keyword true
tk_false, // keyword false
tk_use, // keyword use
tk_for, // loop keyword for
tk_forindex, // loop keyword forindex
tk_foreach, // loop keyword foreach
tk_while, // loop keyword while
tk_var, // keyword for definition
tk_func, // keyword for definition of function
tk_brk, // loop keyword break
tk_cont, // loop keyword continue
tk_ret, // function keyword return
tk_if, // condition expression keyword if
tk_elsif, // condition expression keyword elsif
tk_else, // condition expression keyword else
tk_nil, // nil literal
tk_lcurve, // (
tk_rcurve, // )
tk_lbracket, // [
tk_rbracket, // ]
tk_lbrace, // {
tk_rbrace, // }
tk_semi, // ;
tk_and, // operator and
tk_or, // operator or
tk_comma, // ,
tk_dot, // .
tk_ellipsis, // ...
tk_quesmark, // ?
tk_quesques, // ??
tk_quesdot, // ?.
tk_colon, // :
tk_add, // operator +
tk_sub, // operator -
tk_mult, // operator *
tk_div, // operator /
tk_floater, // operator ~ and binary operator ~
tk_btand, // bitwise operator &
tk_btor, // bitwise operator |
tk_btxor, // bitwise operator ^
tk_not, // operator !
tk_eq, // operator =
tk_addeq, // operator +=
tk_subeq, // operator -=
tk_multeq, // operator *=
tk_diveq, // operator /=
tk_lnkeq, // operator ~=
tk_btandeq, // operator &=
tk_btoreq, // operator |=
tk_btxoreq, // operator ^=
tk_cmpeq, // operator ==
tk_neq, // operator !=
tk_less, // operator <
tk_leq, // operator <=
tk_grt, // operator >
tk_geq, // operator >=
tk_eof // <eof> end of token list
};
struct token {
span loc; // location
tok type; // token type
std::string str; // content
token() = default;
token(const token&) = default;
};
class lexer {
private:
u64 line;
u64 column;
usize ptr;
std::string filename;
std::string res;
private:
error err;
u64 invalid_char;
std::vector<token> toks;
private:
const std::unordered_map<std::string, tok> token_mapper = {
{"use" , tok::tk_use },
{"true" , tok::tk_true },
{"false" , tok::tk_false },
{"for" , tok::tk_for },
{"forindex", tok::tk_forindex},
{"foreach" , tok::tk_foreach },
{"while" , tok::tk_while },
{"var" , tok::tk_var },
{"func" , tok::tk_func },
{"break" , tok::tk_brk },
{"continue", tok::tk_cont },
{"return" , tok::tk_ret },
{"if" , tok::tk_if },
{"elsif" , tok::tk_elsif },
{"else" , tok::tk_else },
{"nil" , tok::tk_nil },
{"(" , tok::tk_lcurve },
{")" , tok::tk_rcurve },
{"[" , tok::tk_lbracket},
{"]" , tok::tk_rbracket},
{"{" , tok::tk_lbrace },
{"}" , tok::tk_rbrace },
{";" , tok::tk_semi },
{"and" , tok::tk_and },
{"or" , tok::tk_or },
{"," , tok::tk_comma },
{"." , tok::tk_dot },
{"..." , tok::tk_ellipsis},
{"?" , tok::tk_quesmark},
{"??" , tok::tk_quesques},
{"?." , tok::tk_quesdot },
{":" , tok::tk_colon },
{"+" , tok::tk_add },
{"-" , tok::tk_sub },
{"*" , tok::tk_mult },
{"/" , tok::tk_div },
{"~" , tok::tk_floater },
{"&" , tok::tk_btand },
{"|" , tok::tk_btor },
{"^" , tok::tk_btxor },
{"!" , tok::tk_not },
{"=" , tok::tk_eq },
{"+=" , tok::tk_addeq },
{"-=" , tok::tk_subeq },
{"*=" , tok::tk_multeq },
{"/=" , tok::tk_diveq },
{"~=" , tok::tk_lnkeq },
{"&=" , tok::tk_btandeq },
{"|=" , tok::tk_btoreq },
{"^=" , tok::tk_btxoreq },
{"==" , tok::tk_cmpeq },
{"!=" , tok::tk_neq },
{"<" , tok::tk_less },
{"<=" , tok::tk_leq },
{">" , tok::tk_grt },
{">=" , tok::tk_geq }
};
private:
tok get_type(const std::string&);
bool skip(char);
bool is_id(char);
bool is_hex(char);
bool is_oct(char);
bool is_dec(char);
bool is_str(char);
bool is_quesmark(char);
bool is_single_opr(char);
bool is_calc_opr(char);
void skip_note();
void err_char();
void open(const std::string&);
std::string utf8_gen();
token id_gen();
token num_gen();
token str_gen();
token quesmark_gen();
token single_opr();
token dots();
token calc_opr();
public:
lexer(): line(1), column(0), ptr(0),
filename(""), res(""),
invalid_char(0) {}
const error& scan(const std::string&);
const auto& result() const {return toks;}
};
}

140
src/nasal_opcode.cpp Normal file
View File

@@ -0,0 +1,140 @@
#include "nasal_opcode.h"
#include "util/util.h"
namespace nasal {
void codestream::set(const f64* number_list,
const std::string* string_list,
const nasal_builtin_table* native_table,
const std::string* file_list) {
const_number = number_list;
const_string = string_list;
natives = native_table;
files = file_list;
}
void codestream::dump(std::ostream& out) const {
using std::setw;
using std::setfill;
using std::hex;
using std::dec;
const auto op = code.op;
const auto num = code.num;
// dump operand index and bytecode(hex format)
out << hex << "0x"
<< setw(8) << setfill('0') << index << " "
<< setw(2) << setfill('0') << static_cast<u32>(op) << ":" << dec;
// dump immediate number(hex format)
for(i32 i = 64-8; i>=0; i -= 8) {
out << hex << setw(2) << setfill('0') << ((num>>i)&0xff) << dec << " ";
}
// dump operand name
out << " " << operand_name_table.at(static_cast<op_code_type>(op)) << " ";
switch(op) {
case op_addeq:
case op_subeq:
case op_muleq:
case op_diveq:
case op_lnkeq:
case op_meq:
case op_btandeq:
case op_btoreq:
case op_btxoreq:
out << hex << "0x" << num << dec << " sp-" << num;
break;
case op_addeqc:
case op_subeqc:
case op_muleqc:
case op_diveqc:
out << hex << "0x" << num << dec;
out << " (" << const_number[num] << ")";
break;
case op_lnkeqc:
out << hex << "0x" << num << dec;
out << " (" << util::rawstr(const_string[num], 16) << ")";
break;
case op_addecp:
case op_subecp:
case op_mulecp:
case op_divecp:
out << hex << "0x" << num << dec;
out << " (" << const_number[num] << ") sp-1";
break;
case op_lnkecp:
out << hex << "0x" << num << dec;
out << " (" << util::rawstr(const_string[num], 16) << ") sp-1";
break;
case op_addc:
case op_subc:
case op_mulc:
case op_divc:
case op_lessc:
case op_leqc:
case op_grtc:
case op_geqc:
case op_pnum:
out << hex << "0x" << num << dec;
out << " (" << const_number[num] << ")";
break;
case op_callvi:
case op_newv:
case op_callfv:
case op_repl:
case op_intl:
case op_findex:
case op_feach:
case op_newf:
case op_jmp:
case op_jt:
case op_jf:
case op_callg:
case op_mcallg:
case op_loadg:
case op_calll:
case op_mcalll:
case op_loadl:
out << hex << "0x" << num << dec; break;
case op_callb:
out << hex << "0x" << num << " <" << natives[num].name
<< "@0x" << reinterpret_cast<u64>(natives[num].func)
<< dec << ">"; break;
case op_upval:
case op_mupval:
case op_loadu:
out << hex << "0x" << ((num>>16)&0xffff)
<< "[0x" << (num&0xffff) << "]" << dec; break;
case op_happ:
case op_pstr:
case op_lnkc:
case op_callh:
case op_mcallh:
case op_para:
case op_deft:
case op_dyn:
out << hex << "0x" << num << dec;
out << " (" << util::rawstr(const_string[num], 16) << ")";
break;
default:
if (files) {
out << hex << "0x" << num << dec;
}
break;
}
// if file list is loaded, dump file location info
if (files) {
out << " (" << files[code.fidx] << ":" << code.line << ")";
}
}
std::ostream& operator<<(std::ostream& out, const codestream& ins) {
ins.dump(out);
return out;
}
}

222
src/nasal_opcode.h Normal file
View File

@@ -0,0 +1,222 @@
#pragma once
#include "nasal.h"
#include "natives/builtin.h"
#include <iostream>
namespace nasal {
enum op_code_type: u8 {
op_exit, // stop the virtual machine
op_repl, // in repl mode: print value on stack top
op_intl, // local scope size
op_loadg, // load global value
op_loadl, // load local value
op_loadu, // load upvalue
op_dup, // copy value on stack top
op_pnum, // push constant number to the stack
op_pnil, // push constant nil to the stack
op_pstr, // push constant std::string to the stack
op_newv, // push new vector with initial values from stack
op_newh, // push new hash to the stack
op_newf, // push new function to the stack
op_happ, // hash append
op_para, // normal parameter
op_deft, // default parameter
op_dyn, // dynamic parameter
op_lnot, // ! logical negation
op_usub, // - negation
op_bnot, // ~ bitwise not static_cast<i32>
op_btor, // | bitwise or
op_btxor, // ^ bitwise xor
op_btand, // & bitwise and
op_add, // +
op_sub, // -
op_mul, // *
op_div, // /
op_lnk, // ~
op_addc, // + const
op_subc, // - const
op_mulc, // * const
op_divc, // / const
op_lnkc, // ~ const
op_addeq, // += maybe pop stack top
op_subeq, // -= maybe pop stack top
op_muleq, // *= maybe pop stack top
op_diveq, // /= maybe pop stack top
op_lnkeq, // ~= maybe pop stack top
op_btandeq, // &= maybe pop stack top
op_btoreq, // |= maybe pop stack top
op_btxoreq, // ^= maybe pop stack top
op_addeqc, // += const don't pop stack top
op_subeqc, // -= const don't pop stack top
op_muleqc, // *= const don't pop stack top
op_diveqc, // /= const don't pop stack top
op_lnkeqc, // ~= const don't pop stack top
op_addecp, // += const and pop stack top
op_subecp, // -= const and pop stack top
op_mulecp, // *= const and pop stack top
op_divecp, // /= const and pop stack top
op_lnkecp, // ~= concat const std::string and pop stack top
op_meq, // = maybe pop stack top
op_eq, // == compare operator
op_neq, // != compare operator
op_less, // < compare operator
op_leq, // <= compare operator
op_grt, // > compare operator
op_geq, // >= compare operator
op_lessc, // < const compare operator
op_leqc, // <= const compare operator
op_grtc, // > const compare operator
op_geqc, // >= const compare operator
op_pop, // pop a value out of stack top
op_jmp, // jump absolute address with no condition
op_jt, // used in operator and/or, jmp when condition is true and DO NOT POP
op_jf, // used in conditional/loop, jmp when condition is false and POP STACK
op_cnt, // add counter for forindex/foreach
op_findex, // index counter on the top of forindex_stack plus 1
op_feach, // index counter on the top of forindex_stack plus 1 and get the value in vector
op_callg, // get value in global scope
op_calll, // get value in local scope
op_upval, // get value in closure, high 16 as the index of upval, low 16 as the index of local
op_callv, // call vec[index]
op_callvi, // call vec[immediate] (used in multi-assign/multi-define)
op_callh, // call hash.label
op_callfv, // call function(vector as parameters)
op_callfh, // call function(hash as parameters)
op_callb, // call native functions
op_slcbeg, // begin of slice like: vec[1, 2, 3:6, 0, -1]
op_slcend, // end of slice
op_slc, // slice like vec[1]
op_slc2, // slice like vec[nil:10]
op_mcallg, // get memory space of value in global scope
op_mcalll, // get memory space of value in local scope
op_mupval, // get memory space of value in closure
op_mcallv, // get memory space of vec[index]
op_mcallh, // get memory space of hash.label
op_ret // return
};
static std::unordered_map<op_code_type, std::string> operand_name_table = {
{op_code_type::op_exit, "exit "},
{op_code_type::op_repl, "repl "},
{op_code_type::op_intl, "intl "},
{op_code_type::op_loadg, "loadg "},
{op_code_type::op_loadl, "loadl "},
{op_code_type::op_loadu, "loadu "},
{op_code_type::op_dup, "dup "},
{op_code_type::op_pnum, "pnum "},
{op_code_type::op_pnil, "pnil "},
{op_code_type::op_pstr, "pstr "},
{op_code_type::op_newv, "newv "},
{op_code_type::op_newh, "newh "},
{op_code_type::op_newf, "newf "},
{op_code_type::op_happ, "happ "},
{op_code_type::op_para, "para "},
{op_code_type::op_deft, "def "},
{op_code_type::op_dyn, "dyn "},
{op_code_type::op_lnot, "lnot "},
{op_code_type::op_usub, "usub "},
{op_code_type::op_bnot, "bitnot"},
{op_code_type::op_btor, "bitor "},
{op_code_type::op_btxor, "bitxor"},
{op_code_type::op_btand, "bitand"},
{op_code_type::op_add, "add "},
{op_code_type::op_sub, "sub "},
{op_code_type::op_mul, "mult "},
{op_code_type::op_div, "div "},
{op_code_type::op_lnk, "lnk "},
{op_code_type::op_addc, "addc "},
{op_code_type::op_subc, "subc "},
{op_code_type::op_mulc, "multc "},
{op_code_type::op_divc, "divc "},
{op_code_type::op_lnkc, "lnkc "},
{op_code_type::op_addeq, "addeq "},
{op_code_type::op_subeq, "subeq "},
{op_code_type::op_muleq, "muleq "},
{op_code_type::op_diveq, "diveq "},
{op_code_type::op_lnkeq, "lnkeq "},
{op_code_type::op_btandeq, "bandeq"},
{op_code_type::op_btoreq, "boreq "},
{op_code_type::op_btxoreq, "bxoreq"},
{op_code_type::op_addeqc, "addeqc"},
{op_code_type::op_subeqc, "subeqc"},
{op_code_type::op_muleqc, "muleqc"},
{op_code_type::op_diveqc, "diveqc"},
{op_code_type::op_lnkeqc, "lnkeqc"},
{op_code_type::op_addecp, "addecp"},
{op_code_type::op_subecp, "subecp"},
{op_code_type::op_mulecp, "mulecp"},
{op_code_type::op_divecp, "divecp"},
{op_code_type::op_lnkecp, "lnkecp"},
{op_code_type::op_meq, "meq "},
{op_code_type::op_eq, "eq "},
{op_code_type::op_neq, "neq "},
{op_code_type::op_less, "less "},
{op_code_type::op_leq, "leq "},
{op_code_type::op_grt, "grt "},
{op_code_type::op_geq, "geq "},
{op_code_type::op_lessc, "lessc "},
{op_code_type::op_leqc, "leqc "},
{op_code_type::op_grtc, "grtc "},
{op_code_type::op_geqc, "geqc "},
{op_code_type::op_pop, "pop "},
{op_code_type::op_jmp, "jmp "},
{op_code_type::op_jt, "jt "},
{op_code_type::op_jf, "jf "},
{op_code_type::op_cnt, "cnt "},
{op_code_type::op_findex, "findx "},
{op_code_type::op_feach, "feach "},
{op_code_type::op_callg, "callg "},
{op_code_type::op_calll, "calll "},
{op_code_type::op_upval, "upval "},
{op_code_type::op_callv, "callv "},
{op_code_type::op_callvi, "callvi"},
{op_code_type::op_callh, "callh "},
{op_code_type::op_callfv, "callfv"},
{op_code_type::op_callfh, "callfh"},
{op_code_type::op_callb, "callb "},
{op_code_type::op_slcbeg, "slcbeg"},
{op_code_type::op_slcend, "slcend"},
{op_code_type::op_slc, "slice "},
{op_code_type::op_slc2, "slice2"},
{op_code_type::op_mcallg, "mcallg"},
{op_code_type::op_mcalll, "mcalll"},
{op_code_type::op_mupval, "mupval"},
{op_code_type::op_mcallv, "mcallv"},
{op_code_type::op_mcallh, "mcallh"},
{op_code_type::op_ret, "ret "}
};
struct opcode {
u8 op; // opcode
u16 fidx; // source code file index
u64 num; // immediate num
u64 line; // location line of source code
opcode() = default;
opcode(const opcode&) = default;
opcode& operator=(const opcode&) = default;
};
class codestream {
private:
opcode code;
const u64 index;
inline static const f64* const_number = nullptr;
inline static const std::string* const_string = nullptr;
inline static const nasal_builtin_table* natives = nullptr;
inline static const std::string* files = nullptr;
public:
codestream(const opcode& c, const u64 i): code(c), index(i) {}
static void set(const f64*,
const std::string*,
const nasal_builtin_table*,
const std::string* file_list = nullptr);
void dump(std::ostream&) const;
};
std::ostream& operator<<(std::ostream&, const codestream&);
}

1145
src/nasal_parse.cpp Normal file

File diff suppressed because it is too large Load Diff

168
src/nasal_parse.h Normal file
View File

@@ -0,0 +1,168 @@
#pragma once
#include <unordered_map>
#include "nasal.h"
#include "nasal_ast.h"
#include "nasal_lexer.h"
#include "nasal_err.h"
namespace nasal {
class parse {
#define thisspan (toks[ptr].loc)
#define prevspan (ptr!=0? toks[ptr-1].loc:toks[ptr].loc)
private:
u64 ptr;
u64 in_func_depth; // count function block
u64 in_loop_depth; // count loop block
const token* toks;
code_block* root;
error err;
private:
const std::unordered_map<tok, std::string> token_name_mapper = {
{tok::tk_true , "true" },
{tok::tk_false , "false" },
{tok::tk_use , "use" },
{tok::tk_for , "for" },
{tok::tk_forindex, "forindex"},
{tok::tk_foreach , "foreach" },
{tok::tk_while , "while" },
{tok::tk_var , "var" },
{tok::tk_func , "func" },
{tok::tk_brk , "break" },
{tok::tk_cont , "continue"},
{tok::tk_ret , "return" },
{tok::tk_if , "if" },
{tok::tk_elsif , "elsif" },
{tok::tk_else , "else" },
{tok::tk_nil , "nil" },
{tok::tk_lcurve , "(" },
{tok::tk_rcurve , ")" },
{tok::tk_lbracket, "[" },
{tok::tk_rbracket, "]" },
{tok::tk_lbrace , "{" },
{tok::tk_rbrace , "}" },
{tok::tk_semi , ";" },
{tok::tk_and , "and" },
{tok::tk_or , "or" },
{tok::tk_comma , "," },
{tok::tk_dot , "." },
{tok::tk_ellipsis, "..." },
{tok::tk_quesmark, "?" },
{tok::tk_quesques, "??" },
{tok::tk_quesdot , "?." },
{tok::tk_colon , ":" },
{tok::tk_add , "+" },
{tok::tk_sub , "-" },
{tok::tk_mult , "*" },
{tok::tk_div , "/" },
{tok::tk_floater , "~" },
{tok::tk_btand , "&" },
{tok::tk_btor , "|" },
{tok::tk_btxor , "^" },
{tok::tk_not , "!" },
{tok::tk_eq , "=" },
{tok::tk_addeq , "+=" },
{tok::tk_subeq , "-=" },
{tok::tk_multeq , "*=" },
{tok::tk_diveq , "/=" },
{tok::tk_lnkeq , "~=" },
{tok::tk_btandeq , "&=" },
{tok::tk_btoreq , "|=" },
{tok::tk_btxoreq , "^=" },
{tok::tk_cmpeq , "==" },
{tok::tk_neq , "!=" },
{tok::tk_less , "<" },
{tok::tk_leq , "<=" },
{tok::tk_grt , ">" },
{tok::tk_geq , ">=" }
};
private:
void die(const span&, const std::string&);
void next();
void match(tok, const char* info = nullptr);
bool lookahead(tok);
bool is_call(tok);
bool check_comma(const tok*);
bool check_tuple();
bool check_func_end(expr*);
bool check_in_curve_multi_definition();
bool check_special_call();
bool need_semi_check(expr*);
void update_location(expr*);
private:
use_stmt* use_stmt_gen();
null_expr* null();
nil_expr* nil();
number_literal* num();
string_literal* str();
identifier* id();
bool_literal* bools();
vector_expr* vec();
hash_expr* hash();
hash_pair* pair();
function* func();
void params(function*);
expr* lcurve_expr();
expr* expression();
code_block* expression_block();
expr* calc();
expr* bitwise_or();
expr* bitwise_xor();
expr* bitwise_and();
expr* or_expr();
expr* and_expr();
expr* cmp_expr();
expr* null_chain_expr();
expr* additive_expr();
expr* multive_expr();
unary_operator* unary();
expr* scalar();
call* call_scalar();
call_hash* callh();
null_access* null_access_call();
call_vector* callv();
call_function* callf();
slice_vector* subvec();
expr* definition();
multi_identifier* incurve_def();
multi_identifier* outcurve_def();
multi_identifier* multi_id();
tuple_expr* multi_scalar();
multi_assign* multi_assignment();
expr* loop();
while_expr* while_loop();
for_expr* for_loop();
forei_expr* forei_loop();
iter_expr* iter_gen();
condition_expr* cond();
continue_expr* continue_expression();
break_expr* break_expression();
return_expr* return_expression();
public:
code_block* tree() {return root;}
// swap root pointer with another pointer(maybe nullptr)
code_block* swap(code_block* another) {
auto res = root;
root = another;
return res;
}
public:
parse(): ptr(0), in_func_depth(0),
in_loop_depth(0), toks(nullptr),
root(nullptr) {}
~parse() {delete root;}
const error& compile(const lexer&);
static void easter_egg();
};
}

392
src/nasal_type.cpp Normal file
View File

@@ -0,0 +1,392 @@
#include "nasal_type.h"
#include "util/util.h"
#include <cstring>
#include <sstream>
namespace nasal {
var nas_vec::get_value(const i32 index) {
i32 size = elems.size();
if (index<-size || index>=size) {
return var::none();
}
return elems[index>=0? index:index+size];
}
var* nas_vec::get_memory(const i32 index) {
i32 size = elems.size();
if (index<-size || index>=size) {
return nullptr;
}
return &elems[index>=0? index:index+size];
}
std::ostream& operator<<(std::ostream& out, nas_vec& vec) {
if (!vec.elems.size() || vec.printed) {
out << (vec.elems.size()? "[..]":"[]");
return out;
}
vec.printed = true;
usize iter = 0, size = vec.elems.size();
out << "[";
for(auto& i:vec.elems) {
out << i << ",]"[(++iter)==size];
}
vec.printed = false;
return out;
}
var nas_hash::get_value(const std::string& key) {
if (elems.count(key)) {
return elems.at(key);
} else if (!elems.count("parents")) {
return var::none();
}
auto ret = var::none();
auto& val = elems.at("parents");
if (!val.is_vec()) {
return ret;
}
for(auto& i : val.vec().elems) {
if (i.is_hash()) {
ret = i.hash().get_value(key);
}
if (!ret.is_none()) {
return ret;
}
}
return ret;
}
var* nas_hash::get_memory(const std::string& key) {
if (elems.count(key)) {
return &elems.at(key);
} else if (!elems.count("parents")) {
return nullptr;
}
var* addr = nullptr;
var& val = elems.at("parents");
if (!val.is_vec()) {
return addr;
}
for(auto& i : val.vec().elems) {
// recursively search key in `parents`
if (i.is_hash()) {
addr = i.hash().get_memory(key);
}
if (addr) {
return addr;
}
}
return addr;
}
std::ostream& operator<<(std::ostream& out, nas_hash& hash) {
if (!hash.elems.size() || hash.printed) {
out << (hash.elems.size()? "{..}":"{}");
return out;
}
hash.printed = true;
usize iter = 0, size = hash.elems.size();
out << "{";
for(auto& i : hash.elems) {
out << i.first << ":" << i.second << ",}"[(++iter)==size];
}
hash.printed = false;
return out;
}
std::ostream& operator<<(std::ostream& out, nas_func& func) {
out << "func(";
std::vector<std::string> argument_list = {};
argument_list.resize(func.keys.size());
for(const auto& key : func.keys) {
argument_list[key.second-1] = key.first;
}
for(const auto& key : argument_list) {
out << key;
if (key != argument_list.back()) {
out << ", ";
}
}
if (func.dynamic_parameter_index>=0) {
out << (argument_list.size()? ", ":"");
out << func.dynamic_parameter_name << "...";
}
out << ") {..}";
return out;
}
void nas_func::clear() {
dynamic_parameter_index = -1;
local.clear();
upval.clear();
keys.clear();
}
void nas_ghost::set(const std::string& ghost_type_name,
destructor destructor_pointer,
marker gc_marker_pointer,
void* ghost_pointer) {
type_name = ghost_type_name;
destructor_function = destructor_pointer;
gc_mark_function = gc_marker_pointer;
pointer = ghost_pointer;
}
void nas_ghost::clear() {
// do nothing if pointer is null
if (!pointer) {
return;
}
// do clear pointer if destructor function pointer is null
if (!destructor_function) {
type_name = "";
pointer = nullptr;
return;
}
// do destruction
destructor_function(pointer);
type_name = "";
pointer = nullptr;
destructor_function = nullptr;
gc_mark_function = nullptr;
}
std::ostream& operator<<(std::ostream& out, const nas_ghost& ghost) {
out << "<object " << ghost.get_ghost_name();
out << " at 0x" << std::hex;
out << reinterpret_cast<u64>(ghost.pointer) << std::dec << ">";
return out;
}
void nas_co::clear() {
if (!ctx.stack) {
return;
}
for(u32 i = 0; i<VM_STACK_DEPTH; ++i) {
ctx.stack[i] = var::nil();
}
ctx.pc = 0;
ctx.localr = nullptr;
ctx.memr = nullptr;
ctx.canary = ctx.stack+VM_STACK_DEPTH-1;
ctx.top = ctx.stack;
ctx.funcr = var::nil();
ctx.upvalr = var::nil();
status = status::suspended;
}
std::ostream& operator<<(std::ostream& out, const nas_co& co) {
out << "<coroutine at 0x" << std::hex;
out << reinterpret_cast<u64>(&co) << std::dec << ">";
return out;
}
var nas_map::get_value(const std::string& key) {
if (mapper.count(key)) {
return *mapper.at(key);
}
return var::none();
}
var* nas_map::get_memory(const std::string& key) {
if (mapper.count(key)) {
return mapper.at(key);
}
return nullptr;
}
std::ostream& operator<<(std::ostream& out, nas_map& mp) {
if (!mp.mapper.size() || mp.printed) {
out << (mp.mapper.size()? "{..}":"{}");
return out;
}
mp.printed = true;
usize iter = 0, size = mp.mapper.size();
out << "{";
for(auto& i : mp.mapper) {
out << i.first << ":" << *i.second << ",}"[(++iter)==size];
}
mp.printed = false;
return out;
}
nas_val::nas_val(vm_type val_type) {
mark = gc_status::collected;
type = val_type;
immutable = 0;
switch(val_type) {
case vm_type::vm_str: ptr.str = new std::string; break;
case vm_type::vm_vec: ptr.vec = new nas_vec; break;
case vm_type::vm_hash: ptr.hash = new nas_hash; break;
case vm_type::vm_func: ptr.func = new nas_func; break;
case vm_type::vm_upval: ptr.upval = new nas_upval; break;
case vm_type::vm_ghost: ptr.obj = new nas_ghost; break;
case vm_type::vm_co: ptr.co = new nas_co; break;
case vm_type::vm_map: ptr.map = new nas_map; break;
default: break;
}
}
nas_val::~nas_val() {
switch(type) {
case vm_type::vm_str: delete ptr.str; break;
case vm_type::vm_vec: delete ptr.vec; break;
case vm_type::vm_hash: delete ptr.hash; break;
case vm_type::vm_func: delete ptr.func; break;
case vm_type::vm_upval: delete ptr.upval; break;
case vm_type::vm_ghost: delete ptr.obj; break;
case vm_type::vm_co: delete ptr.co; break;
case vm_type::vm_map: delete ptr.map; break;
default: break;
}
type = vm_type::vm_nil;
}
void nas_val::clear() {
switch(type) {
case vm_type::vm_str: ptr.str->clear(); break;
case vm_type::vm_vec: ptr.vec->elems.clear(); break;
case vm_type::vm_hash: ptr.hash->elems.clear(); break;
case vm_type::vm_func: ptr.func->clear(); break;
case vm_type::vm_upval: ptr.upval->clear(); break;
case vm_type::vm_ghost: ptr.obj->clear(); break;
case vm_type::vm_co: ptr.co->clear(); break;
case vm_type::vm_map: ptr.map->clear(); break;
default: break;
}
}
f64 var::to_num() {
return type!=vm_type::vm_str? val.num:util::str_to_num(str().c_str());
}
std::string var::to_str() {
if (type==vm_type::vm_str) {
return str();
} else if (type==vm_type::vm_num) {
auto tmp = std::to_string(num());
tmp.erase(tmp.find_last_not_of('0')+1, std::string::npos);
tmp.erase(tmp.find_last_not_of('.')+1, std::string::npos);
return tmp;
}
std::stringstream ss;
ss << *this;
return ss.str();
}
std::ostream& operator<<(std::ostream& out, var& ref) {
switch(ref.type) {
case vm_type::vm_none: out << "undefined"; break;
case vm_type::vm_nil: out << "nil"; break;
case vm_type::vm_num: out << ref.val.num; break;
case vm_type::vm_str: out << ref.str(); break;
case vm_type::vm_vec: out << ref.vec(); break;
case vm_type::vm_hash: out << ref.hash(); break;
case vm_type::vm_func: out << ref.func(); break;
case vm_type::vm_ghost: out << ref.ghost(); break;
case vm_type::vm_co: out << ref.co(); break;
case vm_type::vm_map: out << ref.map(); break;
default: break;
}
return out;
}
bool var::object_check(const std::string& name) {
return is_ghost() && ghost().type_name==name && ghost().pointer;
}
var var::none() {
return {vm_type::vm_none, static_cast<u64>(0)};
}
var var::nil() {
return {vm_type::vm_nil, static_cast<u64>(0)};
}
var var::ret(u64 pc) {
return {vm_type::vm_ret, pc};
}
var var::cnt(i64 n) {
return {vm_type::vm_cnt, n};
}
var var::num(f64 n) {
return {vm_type::vm_num, n};
}
var var::gcobj(nas_val* p) {
return {p->type, p};
}
var var::addr(var* p) {
return {vm_type::vm_addr, p};
}
var* var::addr() {
return val.addr;
}
u64 var::ret() const {
return val.ret;
}
i64& var::cnt() {
return val.cnt;
}
f64 var::num() const {
return val.num;
}
std::string& var::str() {
return *val.gcobj->ptr.str;
}
nas_vec& var::vec() {
return *val.gcobj->ptr.vec;
}
nas_hash& var::hash() {
return *val.gcobj->ptr.hash;
}
nas_func& var::func() {
return *val.gcobj->ptr.func;
}
nas_upval& var::upval() {
return *val.gcobj->ptr.upval;
}
nas_ghost& var::ghost() {
return *val.gcobj->ptr.obj;
}
nas_co& var::co() {
return *val.gcobj->ptr.co;
}
nas_map& var::map() {
return *val.gcobj->ptr.map;
}
var nas_err(const std::string& error_function_name, const std::string& info) {
std::cerr << "[vm] " << error_function_name << ": " << info << "\n";
return var::none();
}
}

302
src/nasal_type.h Normal file
View File

@@ -0,0 +1,302 @@
#pragma once
#include "nasal.h"
#include <cstring>
#include <sstream>
#include <iostream>
#include <vector>
#include <unordered_map>
namespace nasal {
enum class vm_type: u8 {
/* none-gc object */
vm_none = 0, // error type
vm_cnt, // counter for forindex/foreach loop
vm_addr, // var* address
vm_ret, // return addres(program counter)
vm_nil, // nil
vm_num, // number
/* gc object */
vm_str, // string
vm_vec, // vector
vm_hash, // hashmap(dict)
vm_func, // function(lambda)
vm_upval, // upvalue
vm_ghost, // ghost type
vm_co, // coroutine
vm_map, // for globals and namespaces
/* mark type range */
vm_type_size_max
};
// size of gc object type
const u32 gc_type_size =
static_cast<u32>(vm_type::vm_type_size_max) -
static_cast<u32>(vm_type::vm_str);
// basic types
struct nas_vec; // vector
struct nas_hash; // hashmap(dict)
struct nas_func; // function(lambda)
struct nas_upval; // upvalue
struct nas_ghost; // objects
struct nas_co; // coroutine
struct nas_map; // mapper
// union type
struct nas_val; // nas_val includes gc-managed types
struct var {
public:
vm_type type = vm_type::vm_none;
union {
u64 ret;
i64 cnt;
f64 num;
var* addr;
nas_val* gcobj;
} val;
private:
var(vm_type t, u64 pc) {type = t; val.ret = pc;}
var(vm_type t, i64 ct) {type = t; val.cnt = ct;}
var(vm_type t, f64 n) {type = t; val.num = n;}
var(vm_type t, var* p) {type = t; val.addr = p;}
var(vm_type t, nas_val* p) {type = t; val.gcobj = p;}
public:
var() = default;
var(const var&) = default;
bool operator==(const var& nr) const {
return type==nr.type && val.gcobj==nr.val.gcobj;
}
bool operator!=(const var& nr) const {
return type!=nr.type || val.gcobj!=nr.val.gcobj;
}
// number and string can be translated to each other
f64 to_num();
std::string to_str();
bool object_check(const std::string&);
public:
// create new var object
static var none();
static var nil();
static var ret(u64);
static var cnt(i64);
static var num(f64);
static var gcobj(nas_val*);
static var addr(var*);
public:
// get value
var* addr();
u64 ret() const;
i64& cnt();
f64 num() const;
std::string& str();
nas_vec& vec();
nas_hash& hash();
nas_func& func();
nas_upval& upval();
nas_ghost& ghost();
nas_co& co();
nas_map& map();
public:
bool is_none() const { return type==vm_type::vm_none; }
bool is_cnt() const { return type==vm_type::vm_cnt; }
bool is_addr() const { return type==vm_type::vm_addr; }
bool is_ret() const { return type==vm_type::vm_ret; }
bool is_nil() const { return type==vm_type::vm_nil; }
bool is_num() const { return type==vm_type::vm_num; }
bool is_str() const { return type==vm_type::vm_str; }
bool is_vec() const { return type==vm_type::vm_vec; }
bool is_hash() const { return type==vm_type::vm_hash; }
bool is_func() const { return type==vm_type::vm_func; }
bool is_upval() const { return type==vm_type::vm_upval; }
bool is_ghost() const { return type==vm_type::vm_ghost; }
bool is_coroutine() const { return type==vm_type::vm_co; }
bool is_map() const { return type==vm_type::vm_map; }
};
struct nas_vec {
std::vector<var> elems;
// mark if this is printed, avoid stack overflow
bool printed = false;
usize size() const {return elems.size();}
var get_value(const i32);
var* get_memory(const i32);
};
struct nas_hash {
std::unordered_map<std::string, var> elems;
// mark if this is printed, avoid stack overflow
bool printed = false;
usize size() const {return elems.size();}
var get_value(const std::string&);
var* get_memory(const std::string&);
};
struct nas_func {
i64 dynamic_parameter_index; // dynamic parameter name index in hash.
u64 entry; // pc will set to entry-1 to call this function
u32 parameter_size; // used to load default parameters to a new function
u64 local_size; // used to expand memory space for local values on stack
std::vector<var> local; // local scope with default value(var)
std::vector<var> upval; // closure
// parameter table, u32 begins from 1
std::unordered_map<std::string, u32> keys;
// dynamic parameter name
std::string dynamic_parameter_name;
nas_func():
dynamic_parameter_index(-1), entry(0),
parameter_size(0), local_size(0),
dynamic_parameter_name("") {}
void clear();
};
struct nas_upval {
public:
/* on stack, use these variables */
bool on_stack;
u64 size;
var* stack_frame_offset;
/* not on stack, use this */
std::vector<var> elems;
public:
nas_upval(): on_stack(true), size(0), stack_frame_offset(nullptr) {}
var& operator[](usize n) {
return on_stack? stack_frame_offset[n]:elems[n];
}
void clear() {
on_stack = true;
elems.clear();
size = 0;
}
};
struct nas_ghost {
private:
using destructor = void (*)(void*);
using marker = void (*)(void*, std::vector<var>*);
public:
std::string type_name;
destructor destructor_function;
marker gc_mark_function;
void* pointer;
public:
nas_ghost():
type_name(""), destructor_function(nullptr),
gc_mark_function(nullptr), pointer(nullptr) {}
~nas_ghost() { clear(); }
void set(const std::string&, destructor, marker, void*);
void clear();
public:
const auto& get_ghost_name() const { return type_name; }
};
struct context {
u64 pc = 0;
var* localr = nullptr;
var* memr = nullptr;
var funcr = var::nil();
var upvalr = var::nil();
var* canary = nullptr;
var* stack = nullptr;
var* top = nullptr;
};
struct nas_co {
enum class status:u32 {
suspended,
running,
dead
};
context ctx;
status status;
nas_co() {
ctx.stack = new var[VM_STACK_DEPTH];
clear();
}
~nas_co() {
delete[] ctx.stack;
}
void clear();
};
struct nas_map {
bool printed = false;
std::unordered_map<std::string, var*> mapper;
void clear() {
mapper.clear();
}
var get_value(const std::string&);
var* get_memory(const std::string&);
};
struct nas_val {
enum class gc_status: u8 {
uncollected = 0,
collected,
found
};
gc_status mark;
vm_type type; // value type
u8 immutable; // used to mark if a string is immutable
union {
std::string* str;
nas_vec* vec;
nas_hash* hash;
nas_func* func;
nas_upval* upval;
nas_ghost* obj;
nas_co* co;
nas_map* map;
} ptr;
nas_val(vm_type);
~nas_val();
void clear();
};
std::ostream& operator<<(std::ostream&, nas_vec&);
std::ostream& operator<<(std::ostream&, nas_hash&);
std::ostream& operator<<(std::ostream&, nas_func&);
std::ostream& operator<<(std::ostream&, nas_map&);
std::ostream& operator<<(std::ostream&, const nas_ghost&);
std::ostream& operator<<(std::ostream&, const nas_co&);
std::ostream& operator<<(std::ostream&, var&);
const var zero = var::num(0);
const var one = var::num(1);
const var nil = var::nil();
// use to print error log and return error value
var nas_err(const std::string&, const std::string&);
}

658
src/nasal_vm.cpp Normal file
View File

@@ -0,0 +1,658 @@
#include "nasal_vm.h"
#include "util/util.h"
namespace nasal {
void vm::vm_init_enrty(const std::vector<std::string>& strs,
const std::vector<f64>& nums,
const std::vector<nasal_builtin_table>& natives,
const std::vector<opcode>& code,
const std::unordered_map<std::string, u64>& global_symbol,
const std::vector<std::string>& filenames,
const std::vector<std::string>& argv) {
const_number = nums.data();
const_string = strs.data();
bytecode = code.data();
files = filenames.data();
global_size = global_symbol.size();
/* set native functions */
native_function = natives;
/* set context and global */
if (!is_repl_mode || first_exec_flag) {
context_and_global_init();
first_exec_flag = false;
}
/* init gc */
ngc.set(&ctx, global, global_size);
ngc.init(strs, argv);
/* init vm globals */
auto map_instance = ngc.alloc(vm_type::vm_map);
global[global_symbol.at("globals")] = map_instance;
for(const auto& i : global_symbol) {
map_instance.map().mapper[i.first] = global+i.second;
}
/* init vm arg */
auto arg_instance = ngc.alloc(vm_type::vm_vec);
global[global_symbol.at("arg")] = arg_instance;
arg_instance.vec().elems = ngc.env_argv;
}
void vm::context_and_global_init() {
/* set canary and program counter */
ctx.pc = 0;
ctx.localr = nullptr;
ctx.memr = nullptr;
ctx.funcr = nil;
ctx.upvalr = nil;
/* set canary = stack[VM_STACK_DEPTH-1] */
ctx.canary = ctx.stack+VM_STACK_DEPTH-1;
/* nothing is on stack */
ctx.top = ctx.stack - 1;
/* clear main stack and global */
for(u32 i = 0; i<VM_STACK_DEPTH; ++i) {
ctx.stack[i] = nil;
global[i] = nil;
}
}
void vm::value_info(var& val) {
const auto p = reinterpret_cast<u64>(val.val.gcobj);
switch(val.type) {
case vm_type::vm_none: std::clog << "| null |"; break;
case vm_type::vm_ret: std::clog << "| pc | 0x" << std::hex
<< val.ret() << std::dec; break;
case vm_type::vm_addr: std::clog << "| addr | 0x" << std::hex
<< reinterpret_cast<u64>(val.addr())
<< std::dec; break;
case vm_type::vm_cnt: std::clog << "| cnt | " << val.cnt(); break;
case vm_type::vm_nil: std::clog << "| nil |"; break;
case vm_type::vm_num: std::clog << "| num | " << val.num(); break;
case vm_type::vm_str: std::clog << "| str | <0x" << std::hex << p
<< "> \"" << util::rawstr(val.str(), 16)
<< "\"" << std::dec; break;
case vm_type::vm_func: std::clog << "| func | <0x" << std::hex << p
<< std::dec << "> " << val.func();
break;
case vm_type::vm_upval:std::clog << "| upval| <0x" << std::hex << p
<< std::dec << "> [" << val.upval().size
<< " val]"; break;
case vm_type::vm_vec: std::clog << "| vec | <0x" << std::hex << p
<< std::dec << "> [" << val.vec().size()
<< " val]"; break;
case vm_type::vm_hash: std::clog << "| hash | <0x" << std::hex << p
<< std::dec << "> {" << val.hash().size()
<< " val}"; break;
case vm_type::vm_ghost:std::clog << "| obj | <0x" << std::hex << p
<< "> obj:0x"
<< reinterpret_cast<u64>(val.ghost().pointer)
<< std::dec; break;
case vm_type::vm_co: std::clog << "| co | <0x" << std::hex << p
<< std::dec << "> coroutine"; break;
case vm_type::vm_map: std::clog << "| nmspc| <0x" << std::hex << p
<< std::dec << "> namespace ["
<< val.map().mapper.size()
<< " val]"; break;
default: std::clog << "| err | <0x" << std::hex << p
<< std::dec << "> unknown object"; break;
}
std::clog << "\n";
}
void vm::function_detail_info(const nas_func& func) {
std::clog << "func@0x";
std::clog << std::hex << reinterpret_cast<u64>(&func) << std::dec;
std::vector<std::string> argument_list = {};
argument_list.resize(func.keys.size());
for(const auto& key : func.keys) {
argument_list[key.second-1] = key.first;
}
std::clog << "(";
for(const auto& key : argument_list) {
std::clog << key;
if (key != argument_list.back()) {
std::clog << ", ";
}
}
if (func.dynamic_parameter_index>=0) {
std::clog << (argument_list.size()? ", ":"");
std::clog << func.dynamic_parameter_name << "...";
}
std::clog << ") ";
const auto& code = bytecode[func.entry];
std::clog << "{ entry: " << files[code.fidx] << ":" << code.line << " }";
}
void vm::function_call_trace() {
var* bottom = ctx.stack;
var* top = ctx.top;
// generate trace back
std::stack<const nas_func*> functions;
for(var* i = bottom; i<=top; ++i) {
if (i->is_func() && i-1>=bottom && (i-1)->is_ret()) {
functions.push(&i->func());
}
}
if (functions.empty()) {
return;
}
std::clog << "\ncall trace " << (ngc.cort? "(coroutine)":"(main)") << "\n";
const nas_func* last = nullptr;
u32 same = 0;
for(auto func = last; !functions.empty(); functions.pop()) {
func = functions.top();
if (last==func) {
++same;
continue;
} else if (same) {
std::clog << " --> " << same << " same call(s)\n";
same = 0;
}
last = func;
std::clog << " call ";
function_detail_info(*func);
std::clog << "\n";
}
if (same) {
std::clog << " --> " << same << " same call(s)\n";
}
}
void vm::trace_back() {
// var* bottom = ctx.stack;
// var* top = ctx.top;
// generate trace back
std::stack<u32> ret;
for(var* i = ctx.stack; i<=ctx.top; ++i) {
if (i->is_ret() && i->ret()!=0) {
ret.push(i->ret());
}
}
ret.push(ctx.pc); // store the position program crashed
std::clog << "\ntrace back " << (ngc.cort? "(coroutine)":"(main)") << "\n";
codestream::set(const_number, const_string, native_function.data(), files);
for(u32 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) {
if ((p = ret.top())==prev) {
++same;
continue;
}
if (same) {
std::clog << " 0x" << std::hex
<< std::setw(8) << std::setfill('0')
<< prev << std::dec << " "
<< same << " same call(s)\n";
same = 0;
}
std::clog << " " << codestream(bytecode[p], p) << "\n";
}
// the first called place has no same calls
}
void vm::stack_info(const u64 limit = 16) {
var* top = ctx.top;
var* bottom = ctx.stack;
const auto stack_address = reinterpret_cast<u64>(bottom);
std::clog << "\nvm stack (0x" << std::hex << stack_address << std::dec;
std::clog << ", limit " << limit << ", total ";
std::clog << (top<bottom? 0:static_cast<i64>(top-bottom+1)) << ")\n";
for(u32 i = 0; i<limit && top>=bottom; ++i, --top) {
std::clog << " 0x" << std::hex
<< std::setw(8) << std::setfill('0')
<< static_cast<u64>(top-bottom) << std::dec
<< " ";
value_info(top[0]);
}
}
void vm::register_info() {
std::clog << "\nregisters (" << (ngc.cort? "coroutine":"main") << ")\n";
std::clog << std::hex
<< " [ pc ] | pc | 0x" << ctx.pc << "\n"
<< " [ global ] | addr | 0x"
<< reinterpret_cast<u64>(global) << "\n"
<< " [ local ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.localr) << "\n"
<< " [ memr ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.memr) << "\n"
<< " [ canary ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.canary) << "\n"
<< " [ top ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.top) << "\n"
<< std::dec;
std::clog << " [ funcr ] "; value_info(ctx.funcr);
std::clog << " [ upval ] "; value_info(ctx.upvalr);
}
void vm::global_state() {
if (!global_size || global[0].is_none()) {
return;
}
std::clog << "\nglobal (0x" << std::hex
<< reinterpret_cast<u64>(global) << ")\n" << std::dec;
for(usize i = 0; i<global_size; ++i) {
std::clog << " 0x" << std::hex << std::setw(8)
<< std::setfill('0') << i << std::dec
<< " ";
value_info(global[i]);
}
}
void vm::local_state() {
if (!ctx.localr || !ctx.funcr.func().local_size) {
return;
}
const u32 lsize = ctx.funcr.func().local_size;
std::clog << "\nlocal (0x" << std::hex << reinterpret_cast<u64>(ctx.localr)
<< " <+" << static_cast<u64>(ctx.localr-ctx.stack)
<< ">)\n" << std::dec;
for(u32 i = 0; i<lsize; ++i) {
std::clog << " 0x" << std::hex << std::setw(8)
<< std::setfill('0') << i << std::dec
<< " ";
value_info(ctx.localr[i]);
}
}
void vm::upvalue_state() {
if (ctx.funcr.is_nil() || ctx.funcr.func().upval.empty()) {
return;
}
std::clog << "\nupvalue\n";
auto& upval = ctx.funcr.func().upval;
for(u32 i = 0; i<upval.size(); ++i) {
std::clog << " -> upval[" << i << "]:\n";
auto& uv = upval[i].upval();
for(u32 j = 0; j<uv.size; ++j) {
std::clog << " 0x" << std::hex << std::setw(8)
<< std::setfill('0') << j << std::dec
<< " ";
value_info(uv[j]);
}
}
}
void vm::all_state_detail() {
register_info();
global_state();
local_state();
upvalue_state();
}
std::string vm::report_lack_arguments(u32 argc, const nas_func& func) const {
auto result = std::string("lack argument(s) when calling function:\n func(");
std::vector<std::string> argument_list = {};
argument_list.resize(func.keys.size());
for(const auto& i : func.keys) {
argument_list[i.second-1] = i.first;
}
for(u32 i = 0; i<argument_list.size(); ++i) {
result += argument_list[i];
if (i<argc) {
result += "[get]";
}
if (i!=argument_list.size()-1) {
result += ", ";
}
}
if (func.dynamic_parameter_index>=0) {
result += argument_list.size()? ", ":"";
result += const_string[func.dynamic_parameter_index] + "[dynamic]";
}
result += ") ";
std::stringstream out;
const auto& code = bytecode[func.entry];
out << "{ entry: " << files[code.fidx] << ":" << code.line << " }";
out << " @ 0x" << std::hex << reinterpret_cast<u64>(&func) << std::dec;
return result + out.str();
}
std::string vm::report_special_call_lack_arguments(
var* local, const nas_func& func) const {
auto result = std::string("lack argument(s) when calling function:\n func(");
std::vector<std::string> argument_list = {};
argument_list.resize(func.keys.size());
for(const auto& i : func.keys) {
argument_list[i.second-1] = i.first;
}
for(const auto& key : argument_list) {
if (local[func.keys.at(key)].is_none()) {
result += key + ", ";
} else {
result += key + "[get], ";
}
}
result = result.substr(0, result.length()-2);
result += ") ";
std::stringstream out;
const auto& code = bytecode[func.entry];
out << "{ entry: " << files[code.fidx] << ":" << code.line << " }";
out << " @ 0x" << std::hex << reinterpret_cast<u64>(&func) << std::dec;
return result + out.str();
}
std::string vm::report_key_not_found(
const std::string& not_found, const nas_hash& hash) const {
auto result = "member \"" + not_found + "\" doesn't exist in hash {";
for(const auto& i : hash.elems) {
result += i.first + ", ";
}
if (hash.elems.size()) {
result = result.substr(0, result.length()-2);
}
result += "}";
return result;
}
std::string vm::report_out_of_range(f64 index, usize real_size) const {
auto result = "index out of range: " + std::to_string(index);
result += " but max size is " + std::to_string(real_size);
if (!real_size) {
return result;
}
result += ", index range is -" + std::to_string(real_size);
result += "~" + std::to_string(real_size-1);
return result;
}
std::string vm::type_name_string(const var& value) const {
switch(value.type) {
case vm_type::vm_none: return "none";
case vm_type::vm_cnt: return "counter";
case vm_type::vm_addr: return "address";
case vm_type::vm_ret: return "program counter";
case vm_type::vm_nil: return "nil";
case vm_type::vm_num: return "number";
case vm_type::vm_str: return "string";
case vm_type::vm_vec: return "vector";
case vm_type::vm_hash: return "hash";
case vm_type::vm_func: return "function";
case vm_type::vm_upval: return "upvalue";
case vm_type::vm_ghost: return "ghost type";
case vm_type::vm_co: return "coroutine";
case vm_type::vm_map: return "namespace";
default: break;
}
return "unknown";
}
void vm::die(const std::string& str) {
std::cerr << "[vm] error: " << str << "\n";
function_call_trace();
trace_back();
stack_info();
// show verbose crash info
if (verbose) {
all_state_detail();
}
if (!ngc.cort) {
// in main context, exit directly
std::exit(1);
} else {
// in coroutine, shut down the coroutine and return to main context
ctx.pc = 0; // mark coroutine 'dead'
ngc.context_reserve(); // switch context to main
ctx.top[0] = nil; // generate return value 'nil'
}
}
void vm::run(const codegen& gen,
const linker& linker,
const std::vector<std::string>& argv) {
vm_init_enrty(
gen.strs(),
gen.nums(),
gen.natives(),
gen.codes(),
gen.globals(),
linker.get_file_list(),
argv
);
#ifndef _MSC_VER
// using labels as values/computed goto
const void* oprs[] = {
&&vmexit,
&&repl,
&&intl,
&&loadg,
&&loadl,
&&loadu,
&&dup,
&&pnum,
&&pnil,
&&pstr,
&&newv,
&&newh,
&&newf,
&&happ,
&&para,
&&deft,
&&dyn,
&&lnot,
&&usub,
&&bnot,
&&btor,
&&btxor,
&&btand,
&&add,
&&sub,
&&mul,
&&div,
&&lnk,
&&addc,
&&subc,
&&mulc,
&&divc,
&&lnkc,
&&addeq,
&&subeq,
&&muleq,
&&diveq,
&&lnkeq,
&&bandeq,
&&boreq,
&&bxoreq,
&&addeqc,
&&subeqc,
&&muleqc,
&&diveqc,
&&lnkeqc,
&&addecp,
&&subecp,
&&mulecp,
&&divecp,
&&lnkecp,
&&meq,
&&eq,
&&neq,
&&less,
&&leq,
&&grt,
&&geq,
&&lessc,
&&leqc,
&&grtc,
&&geqc,
&&pop,
&&jmp,
&&jt,
&&jf,
&&cnt,
&&findex,
&&feach,
&&callg,
&&calll,
&&upval,
&&callv,
&&callvi,
&&callh,
&&callfv,
&&callfh,
&&callb,
&&slcbeg,
&&slcend,
&&slc,
&&slc2,
&&mcallg,
&&mcalll,
&&mupval,
&&mcallv,
&&mcallh,
&&ret
};
std::vector<const void*> code;
for(const auto& i : gen.codes()) {
code.push_back(oprs[i.op]);
imm.push_back(i.num);
}
// goto the first operand
goto *code[ctx.pc];
#else
std::vector<nasal_vm_func> code;
for(const auto& i : gen.codes()) {
code.push_back(operand_function[i.op]);
imm.push_back(i.num);
}
while(code[ctx.pc]) {
(this->*code[ctx.pc])();
if (ctx.top>=ctx.canary) {
die("stack overflow");
}
++ctx.pc;
}
#endif
// all nasal programs should end here
vmexit:
if (verbose) {
ngc.info();
}
imm.clear();
if (!is_repl_mode) {
ngc.clear();
}
return;
#ifndef _MSC_VER
// may cause stackoverflow
#define exec_check(op) {\
op();\
if (ctx.top<ctx.canary)\
goto *code[++ctx.pc];\
die("stack overflow");\
goto *code[++ctx.pc];\
}
// do not cause stackoverflow
#define exec_nodie(op) {\
op();\
goto *code[++ctx.pc];\
}
repl: exec_nodie(o_repl ); // 0
intl: exec_nodie(o_intl ); // -0
loadg: exec_nodie(o_loadg ); // -1
loadl: exec_nodie(o_loadl ); // -1
loadu: exec_nodie(o_loadu ); // -1
dup: exec_check(o_dup ); // +1
pnum: exec_check(o_pnum ); // +1
pnil: exec_check(o_pnil ); // +1
pstr: exec_check(o_pstr ); // +1
newv: exec_check(o_newv ); // +1-imm[pc]
newh: exec_check(o_newh ); // +1
newf: exec_check(o_newf ); // +1
happ: exec_nodie(o_happ ); // -1
para: exec_nodie(o_para ); // -0
deft: exec_nodie(o_deft ); // -1
dyn: exec_nodie(o_dyn ); // -0
lnot: exec_nodie(o_lnot ); // -0
usub: exec_nodie(o_usub ); // -0
bnot: exec_nodie(o_bnot ); // -0
btor: exec_nodie(o_btor ); // -1
btxor: exec_nodie(o_btxor ); // -1
btand: exec_nodie(o_btand ); // -1
add: exec_nodie(o_add ); // -1
sub: exec_nodie(o_sub ); // -1
mul: exec_nodie(o_mul ); // -1
div: exec_nodie(o_div ); // -1
lnk: exec_nodie(o_lnk ); // -1
addc: exec_nodie(o_addc ); // -0
subc: exec_nodie(o_subc ); // -0
mulc: exec_nodie(o_mulc ); // -0
divc: exec_nodie(o_divc ); // -0
lnkc: exec_nodie(o_lnkc ); // -0
addeq: exec_nodie(o_addeq ); // -1
subeq: exec_nodie(o_subeq ); // -1
muleq: exec_nodie(o_muleq ); // -1
diveq: exec_nodie(o_diveq ); // -1
lnkeq: exec_nodie(o_lnkeq ); // -1
bandeq: exec_nodie(o_bandeq); // -1
boreq: exec_nodie(o_boreq ); // -1
bxoreq: exec_nodie(o_bxoreq); // -1
addeqc: exec_nodie(o_addeqc); // -0
subeqc: exec_nodie(o_subeqc); // -0
muleqc: exec_nodie(o_muleqc); // -0
diveqc: exec_nodie(o_diveqc); // -0
lnkeqc: exec_nodie(o_lnkeqc); // -0
addecp: exec_nodie(o_addecp); // -1
subecp: exec_nodie(o_subecp); // -1
mulecp: exec_nodie(o_mulecp); // -1
divecp: exec_nodie(o_divecp); // -1
lnkecp: exec_nodie(o_lnkecp); // -1
meq: exec_nodie(o_meq ); // -1
eq: exec_nodie(o_eq ); // -1
neq: exec_nodie(o_neq ); // -1
less: exec_nodie(o_less ); // -1
leq: exec_nodie(o_leq ); // -1
grt: exec_nodie(o_grt ); // -1
geq: exec_nodie(o_geq ); // -1
lessc: exec_nodie(o_lessc ); // -0
leqc: exec_nodie(o_leqc ); // -0
grtc: exec_nodie(o_grtc ); // -0
geqc: exec_nodie(o_geqc ); // -0
pop: exec_nodie(o_pop ); // -1
jmp: exec_nodie(o_jmp ); // -0
jt: exec_nodie(o_jt ); // -0
jf: exec_nodie(o_jf ); // -1
cnt: exec_nodie(o_cnt ); // -0
findex: exec_check(o_findex); // +1
feach: exec_check(o_feach ); // +1
callg: exec_check(o_callg ); // +1
calll: exec_check(o_calll ); // +1
upval: exec_check(o_upval ); // +1
callv: exec_nodie(o_callv ); // -0
callvi: exec_nodie(o_callvi); // -0
callh: exec_nodie(o_callh ); // -0
callfv: exec_nodie(o_callfv); // check in the function
callfh: exec_nodie(o_callfh); // check in the function
callb: exec_nodie(o_callb ); // -0
slcbeg: exec_check(o_slcbeg); // +1
slcend: exec_nodie(o_slcend); // -1
slc: exec_nodie(o_slc ); // -1
slc2: exec_nodie(o_slc2 ); // -2
mcallg: exec_check(o_mcallg); // +1
mcalll: exec_check(o_mcalll); // +1
mupval: exec_check(o_mupval); // +1
mcallv: exec_nodie(o_mcallv); // -0
mcallh: exec_nodie(o_mcallh); // -0
ret: exec_nodie(o_ret ); // -2
#endif
}
}

1172
src/nasal_vm.h Normal file

File diff suppressed because it is too large Load Diff

164
src/natives/bits_lib.cpp Normal file
View File

@@ -0,0 +1,164 @@
#include "natives/bits_lib.h"
namespace nasal {
var builtin_u32xor(context* ctx, gc* ngc) {
auto local = ctx->localr;
return var::num(static_cast<f64>(
static_cast<u32>(local[1].num()) ^
static_cast<u32>(local[2].num())
));
}
var builtin_u32and(context* ctx, gc* ngc) {
auto local = ctx->localr;
return var::num(static_cast<f64>(
static_cast<u32>(local[1].num()) &
static_cast<u32>(local[2].num())
));
}
var builtin_u32or(context* ctx, gc* ngc) {
auto local = ctx->localr;
return var::num(static_cast<f64>(
static_cast<u32>(local[1].num()) |
static_cast<u32>(local[2].num())
));
}
var builtin_u32nand(context* ctx, gc* ngc) {
auto local = ctx->localr;
return var::num(static_cast<f64>(~(
static_cast<u32>(local[1].num()) &
static_cast<u32>(local[2].num())
)));
}
var builtin_u32not(context* ctx, gc* ngc) {
return var::num(static_cast<f64>(
~static_cast<u32>(ctx->localr[1].num())
));
}
var builtin_fld(context* ctx, gc* ngc) {
// bits.fld(s, 0, 3);
// if s stores 10100010(162)
// will get 101(5)
auto local = ctx->localr;
auto str = local[1];
auto startbit = local[2];
auto length = local[3];
if (!str.is_str() || str.val.gcobj->immutable) {
return nas_err("bits::fld", "\"str\" must be mutable string");
}
if (!startbit.is_num() || !length.is_num()) {
return nas_err("bits::fld", "\"startbit\", \"len\" must be number");
}
u32 bit = static_cast<u32>(startbit.num());
u32 len = static_cast<u32>(length.num());
if (bit+len>8*str.str().length()) {
return nas_err("bits::fld", "bitfield out of bounds");
}
u32 res = 0;
auto& s = str.str();
for(u32 i = bit; i<bit+len; ++i) {
if (s[i>>3]&(1<<(7-(i&7)))) {
res |= 1<<(bit+len-i-1);
}
}
return var::num(static_cast<f64>(res));
}
var builtin_sfld(context* ctx, gc* ngc) {
// bits.sfld(s, 0, 3);
// if s stores 10100010(162)
// will get 101(5) then this will be signed extended to
// 11111101(-3)
auto local = ctx->localr;
auto str = local[1];
auto startbit = local[2];
auto length = local[3];
if (!str.is_str() || str.val.gcobj->immutable) {
return nas_err("bits::sfld", "\"str\" must be mutable string");
}
if (!startbit.is_num() || !length.is_num()) {
return nas_err("bits::sfld", "\"startbit\",\"len\" must be number");
}
u32 bit = static_cast<u32>(startbit.num());
u32 len = static_cast<u32>(length.num());
if (bit+len>8*str.str().length()) {
return nas_err("bits::sfld", "bitfield out of bounds");
}
u32 res = 0;
auto& s = str.str();
for(u32 i = bit; i<bit+len; ++i) {
if (s[i>>3]&(1<<(7-(i&7)))) {
res |= 1<<(bit+len-i-1);
}
}
if (res&(1<<(len-1))) {
res |= ~((1<<len)-1);
}
return var::num(static_cast<f64>(static_cast<i32>(res)));
}
var builtin_setfld(context* ctx, gc* ngc) {
// bits.setfld(s, 0, 8, 69);
// set 01000101(69) to string will get this:
// 10100010(162)
// so s[0]=162
auto local = ctx->localr;
auto str = local[1];
auto startbit = local[2];
auto length = local[3];
auto value = local[4];
if (!str.is_str() || str.val.gcobj->immutable) {
return nas_err("bits::setfld", "\"str\" must be mutable string");
}
if (!startbit.is_num() || !length.is_num() || !value.is_num()) {
return nas_err("bits::setfld",
"\"startbit\", \"len\", \"val\" must be number"
);
}
u32 bit = static_cast<u32>(startbit.num());
u32 len = static_cast<u32>(length.num());
u64 val = static_cast<u64>(value.num());
if (bit+len>8*str.str().length()) {
return nas_err("bits::setfld", "bitfield out of bounds");
}
auto& s = str.str();
for(u32 i = bit; i<bit+len; ++i) {
if (val&(1<<(i-bit))) {
s[i>>3] |= (1<<(7-(i&7)));
} else {
s[i>>3] &= ~(1<<(7-(i&7)));
}
}
return nil;
}
var builtin_buf(context* ctx, gc* ngc) {
var length = ctx->localr[1];
if (!length.is_num() || length.num()<=0) {
return nas_err("bits::buf", "\"len\" must be number greater than 0");
}
var str = ngc->alloc(vm_type::vm_str);
auto& s = str.str();
s.resize(length.num(), '\0');
return str;
}
nasal_builtin_table bits_native[] = {
{"__u32xor", builtin_u32xor},
{"__u32and", builtin_u32and},
{"__u32or", builtin_u32or},
{"__u32nand", builtin_u32nand},
{"__u32not", builtin_u32not},
{"__fld", builtin_fld},
{"__sfld", builtin_sfld},
{"__setfld", builtin_setfld},
{"__buf", builtin_buf},
{nullptr, nullptr}
};
}

21
src/natives/bits_lib.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
namespace nasal {
var builtin_u32xor(context*, gc*);
var builtin_u32and(context*, gc*);
var builtin_u32or(context*, gc*);
var builtin_u32nand(context*, gc*);
var builtin_u32not(context*, gc*);
var builtin_fld(context*, gc*);
var builtin_sfld(context*, gc*);
var builtin_setfld(context*, gc*);
var builtin_buf(context*, gc*);
extern nasal_builtin_table bits_native[];
}

785
src/natives/builtin.cpp Normal file
View File

@@ -0,0 +1,785 @@
#include "natives/builtin.h"
#include "util/util.h"
#include <chrono>
#ifdef _WIN32
#include <windows.h>
#endif
namespace nasal {
var builtin_unsafe(context* ctx, gc* ngc) {
return nas_err(
"unsafe_redirect",
"you are using unsafe system api under limited mode!"
);
}
var builtin_print(context* ctx, gc* ngc) {
for(auto& i : ctx->localr[1].vec().elems) {
std::cout << i;
}
std::cout << std::flush;
return nil;
}
var builtin_println(context* ctx, gc* ngc) {
for(auto& i : ctx->localr[1].vec().elems) {
std::cout << i;
}
std::cout << std::endl;
return nil;
}
var builtin_exit(context* ctx, gc* ngc) {
std::exit(ctx->localr[1].num());
return nil;
}
var builtin_abort(context* ctx, gc* ngc) {
std::abort();
return nil;
}
var builtin_append(context* ctx, gc* ngc) {
auto local = ctx->localr;
var vec = local[1];
var elem = local[2];
if (!vec.is_vec()) {
return nas_err("native::append", "\"vec\" must be vector");
}
auto& v = vec.vec().elems;
for(auto& i : elem.vec().elems) {
v.push_back(i);
}
return nil;
}
var builtin_setsize(context* ctx, gc* ngc) {
auto local = ctx->localr;
var vec = local[1];
var size = local[2];
if (!vec.is_vec()) {
return nas_err("native::setsize", "\"vec\" must be vector");
}
if (!size.is_num() || size.num()<0) {
return nil;
}
vec.vec().elems.resize(static_cast<i64>(size.num()), nil);
return nil;
}
var builtin_system(context* ctx, gc* ngc) {
auto str = ctx->localr[1];
if (!str.is_str()) {
return var::num(-1);
}
return var::num(static_cast<f64>(system(str.str().c_str())));
}
var builtin_input(context* ctx, gc* ngc) {
auto local = ctx->localr;
var end = local[1];
var ret = ngc->alloc(vm_type::vm_str);
if (!end.is_str() || end.str().length()>1 || !end.str().length()) {
std::cin >> ret.str();
} else {
std::getline(std::cin, ret.str(), end.str()[0]);
}
return ret;
}
var builtin_split(context* ctx, gc* ngc) {
auto local = ctx->localr;
var delimeter = local[1];
var str = local[2];
if (!delimeter.is_str()) {
return nas_err("native::split", "\"separator\" must be string");
}
if (!str.is_str()) {
return nas_err("native::split", "\"str\" must be string");
}
const auto& deli = delimeter.str();
const auto& s = str.str();
// avoid being sweeped
auto res = ngc->temp = ngc->alloc(vm_type::vm_vec);
auto& vec = res.vec().elems;
if (!deli.length()) {
for(auto i : s) {
vec.push_back(ngc->newstr(i));
}
ngc->temp = nil;
return res;
}
usize last = 0;
usize pos = s.find(deli, 0);
while(pos!=std::string::npos) {
if (pos>last) {
vec.push_back(ngc->newstr(s.substr(last, pos-last)));
}
last = pos+deli.length();
pos = s.find(deli, last);
}
if (last!=s.length()) {
vec.push_back(ngc->newstr(s.substr(last)));
}
ngc->temp = nil;
return res;
}
var builtin_rand(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
if (!val.is_num() && !val.is_nil()) {
return nas_err("native::rand", "\"seed\" must be nil or number");
}
if (val.is_num()) {
srand(static_cast<u32>(val.num()));
return nil;
}
f64 num = 0;
for(u32 i = 0; i<5; ++i) {
num = (num+rand())*(1.0/(RAND_MAX+1.0));
}
return var::num(num);
}
var builtin_id(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
std::stringstream ss;
ss << "0";
if (val.type>vm_type::vm_num) {
ss << "x" << std::hex;
ss << reinterpret_cast<u64>(val.val.gcobj) << std::dec;
}
return ngc->newstr(ss.str());
}
var builtin_int(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
if (!val.is_num() && !val.is_str()) {
return nil;
}
return var::num(static_cast<f64>(static_cast<i32>(val.to_num())));
}
var builtin_floor(context* ctx, gc* ngc) {
auto value = ctx->localr[1];
return var::num(std::floor(value.num()));
}
var builtin_ceil(context* ctx, gc* ngc) {
auto value = ctx->localr[1];
return var::num(std::ceil(value.num()));
}
var builtin_num(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
if (val.is_num()) {
return val;
}
if (!val.is_str()) {
return nil;
}
auto res = val.to_num();
if (std::isnan(res)) {
return nil;
}
return var::num(res);
}
var builtin_pop(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
if (!val.is_vec()) {
return nas_err("native::pop", "\"vec\" must be vector");
}
auto& vec = val.vec().elems;
if (vec.size()) {
auto tmp = vec.back();
vec.pop_back();
return tmp;
}
return nil;
}
var builtin_str(context* ctx, gc* ngc) {
return ngc->newstr(ctx->localr[1].to_str());
}
var builtin_size(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
usize num = 0;
switch(val.type) {
case vm_type::vm_num: return val;
case vm_type::vm_str: num = val.str().length(); break;
case vm_type::vm_vec: num = val.vec().size(); break;
case vm_type::vm_hash: num = val.hash().size(); break;
case vm_type::vm_map: num = val.map().mapper.size(); break;
default: break;
}
return var::num(static_cast<f64>(num));
}
var builtin_time(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
if (!val.is_num()) {
return nas_err("native::time", "\"begin\" must be number");
}
auto begin = static_cast<time_t>(val.num());
return var::num(static_cast<f64>(time(&begin)));
}
var builtin_contains(context* ctx, gc* ngc) {
auto local = ctx->localr;
var hash = local[1];
var key = local[2];
if (!hash.is_hash() || !key.is_str()) {
return zero;
}
return hash.hash().elems.count(key.str())? one:zero;
}
var builtin_delete(context* ctx, gc* ngc) {
auto local = ctx->localr;
var hash = local[1];
var key = local[2];
if (!hash.is_hash()) {
return nas_err("native::delete", "\"hash\" must be hash");
}
if (!key.is_str()) {
return nil;
}
if (hash.hash().elems.count(key.str())) {
hash.hash().elems.erase(key.str());
}
return nil;
}
var builtin_keys(context* ctx, gc* ngc) {
auto hash = ctx->localr[1];
if (!hash.is_hash() && !hash.is_map()) {
return nas_err("native::keys", "\"hash\" must be hash");
}
// avoid being sweeped
auto res = ngc->temp = ngc->alloc(vm_type::vm_vec);
auto& vec = res.vec().elems;
if (hash.is_hash()) {
for(const auto& iter : hash.hash().elems) {
vec.push_back(ngc->newstr(iter.first));
}
} else {
for(const auto& iter : hash.map().mapper) {
vec.push_back(ngc->newstr(iter.first));
}
}
ngc->temp = nil;
return res;
}
var builtin_die(context* ctx, gc* ngc) {
return nas_err("native::error", ctx->localr[1].to_str());
}
var builtin_find(context* ctx, gc* ngc) {
auto local = ctx->localr;
var needle = local[1];
var haystack = local[2];
usize pos = haystack.to_str().find(needle.to_str());
if (pos==std::string::npos) {
return var::num(-1.0);
}
return var::num(static_cast<f64>(pos));
}
var builtin_type(context* ctx, gc* ngc) {
switch(ctx->localr[1].type) {
case vm_type::vm_none: return ngc->newstr("undefined");
case vm_type::vm_nil: return ngc->newstr("nil");
case vm_type::vm_num: return ngc->newstr("num");
case vm_type::vm_str: return ngc->newstr("str");
case vm_type::vm_vec: return ngc->newstr("vec");
case vm_type::vm_hash: return ngc->newstr("hash");
case vm_type::vm_func: return ngc->newstr("func");
case vm_type::vm_ghost: return ngc->newstr("ghost");
case vm_type::vm_co: return ngc->newstr("coroutine");
case vm_type::vm_map: return ngc->newstr("namespace");
default: break;
}
return nil;
}
var builtin_substr(context* ctx, gc* ngc) {
auto local = ctx->localr;
var str = local[1];
var beg = local[2];
var len = local[3];
if (!str.is_str()) {
return nas_err("native::substr", "\"str\" must be string");
}
if (!beg.is_num() || beg.num()<0) {
return nas_err("native::substr", "\"begin\" should be number >= 0");
}
if (!len.is_num() || len.num()<0) {
return nas_err("native::substr", "\"length\" should be number >= 0");
}
auto begin = static_cast<usize>(beg.num());
auto length = static_cast<usize>(len.num());
if (begin>=str.str().length()) {
return nas_err("native::susbtr", "begin index out of range: "+std::to_string(begin));
}
return ngc->newstr(str.str().substr(begin, length));
}
var builtin_streq(context* ctx, gc* ngc) {
auto local = ctx->localr;
var a = local[1];
var b = local[2];
return var::num(static_cast<f64>(
(!a.is_str() || !b.is_str())? 0:(a.str()==b.str())
));
}
var builtin_left(context* ctx, gc* ngc) {
auto local = ctx->localr;
var str = local[1];
var len = local[2];
if (!str.is_str()) {
return nas_err("native::left", "\"string\" must be string");
}
if (!len.is_num()) {
return nas_err("native::left", "\"length\" must be number");
}
if (len.num()<0) {
return ngc->newstr("");
}
return ngc->newstr(str.str().substr(0, len.num()));
}
var builtin_right(context* ctx, gc* ngc) {
auto local = ctx->localr;
var str = local[1];
var len = local[2];
if (!str.is_str()) {
return nas_err("native::right", "\"string\" must be string");
}
if (!len.is_num()) {
return nas_err("native::right", "\"length\" must be number");
}
i32 length = static_cast<i32>(len.num());
i32 srclen = static_cast<i32>(str.str().length());
if (length>srclen) {
length = srclen;
}
if (length<0) {
length = 0;
}
return ngc->newstr(str.str().substr(srclen-length, srclen));
}
var builtin_cmp(context* ctx, gc* ngc) {
auto local = ctx->localr;
var a = local[1];
var b = local[2];
if (!a.is_str() || !b.is_str()) {
return nas_err("native::cmp", "\"a\" and \"b\" must be string");
}
return var::num(static_cast<f64>(strcmp(
a.str().c_str(),
b.str().c_str()
)));
}
var builtin_chr(context* ctx, gc* ngc) {
const char* extend[] = {
"", " ", "", "ƒ", "", "", "", "",
"ˆ", "", "Š", "", "Œ", " ", "Ž", " ",
" ", "", "", "", "", "", "", "",
"˜", "", "š", "", "œ", " ", "ž", "Ÿ",
" ", "¡", "¢", "£", "¤", "¥", "¦", "§",
"¨", "©", "ª", "«", "¬", " ", "®", "¯",
"°", "±", "²", "³", "´", "µ", "", "·",
"¸", "¹", "º", "»", "¼", "½", "¾", "¿",
"À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç",
"È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï",
"Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×",
"Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß",
"à", "á", "â", "ã", "ä", "å", "æ", "ç",
"è", "é", "ê", "ë", "ì", "í", "î", "ï",
"ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷",
"ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ"
};
auto num = static_cast<i32>(ctx->localr[1].num());
if (0<=num && num<128) {
return ngc->newstr(static_cast<char>(num));
} else if (128<=num && num<256) {
return ngc->newstr(extend[num-128]);
}
return ngc->newstr(" ");
}
var builtin_char(context* ctx, gc* ngc) {
return ngc->newstr(static_cast<unsigned char>(ctx->localr[1].num()));
}
var builtin_values(context* ctx, gc* ngc) {
auto hash = ctx->localr[1];
if (!hash.is_hash() && !hash.is_map()) {
return nas_err("native::values", "\"hash\" must be hash or namespace");
}
auto vec = ngc->alloc(vm_type::vm_vec);
auto& v = vec.vec().elems;
if (hash.is_hash()) {
for(auto& i : hash.hash().elems) {
v.push_back(i.second);
}
} else {
for(auto& i : hash.map().mapper) {
v.push_back(*i.second);
}
}
return vec;
}
var builtin_sleep(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
if (!val.is_num()) {
return nil;
}
#if defined(_WIN32) && !defined(_GLIBCXX_HAS_GTHREADS)
// mingw-w64 win32 thread model has no std::this_thread
// also msvc will use this
Sleep(static_cast<i64>(val.num()*1e3));
#else
std::this_thread::sleep_for(
std::chrono::microseconds(static_cast<i64>(val.num()*1e6))
);
#endif
return nil;
}
var builtin_platform(context* ctx, gc* ngc) {
return ngc->newstr(util::get_platform());
}
var builtin_arch(context* ctx, gc* ngc) {
return ngc->newstr(util::get_arch());
}
// md5 related functions
std::string tohex(u32 num) {
const char str16[] = "0123456789abcdef";
std::string str = "";
for(u32 i = 0; i<4; i++, num >>= 8) {
std::string tmp = "";
for(u32 j = 0, b = num&0xff; j<2; j++, b >>= 4) {
tmp.insert(0, 1, str16[b&0xf]);
}
str += tmp;
}
return str;
}
std::string md5(const std::string& src) {
std::vector<u32> buff;
usize num = ((src.length()+8)>>6)+1;
usize buffsize = num<<4;
buff.resize(buffsize, 0);
for(usize i = 0; i<src.length(); i++) {
buff[i>>2] |= (static_cast<u8>(src[i]))<<((i&0x3)<<3);
}
buff[src.length()>>2] |= 0x80<<(((src.length()%4))<<3);
buff[buffsize-2] = (src.length()<<3)&0xffffffff;
buff[buffsize-1] = ((src.length()<<3)>>32)&0xffffffff;
// u32(abs(sin(i+1))*(2pow32))
const u32 k[] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
// left shift bits
const u32 s[] = {
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
// index
const u32 idx[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // g=i
1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, // g=(5*i+1)%16;
5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, // g=(3*i+5)%16;
0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 // g=(7*i)%16;
};
#define shift(x, n) (((x)<<(n))|((x)>>(32-(n)))) // cycle left shift
#define md5f(x, y, z) (((x)&(y))|((~x)&(z)))
#define md5g(x, y, z) (((x)&(z))|((y)&(~z)))
#define md5h(x, y, z) ((x)^(y)^(z))
#define md5i(x, y, z) ((y)^((x)|(~z)))
u32 atmp = 0x67452301, btmp = 0xefcdab89;
u32 ctmp = 0x98badcfe, dtmp = 0x10325476;
for(u32 i = 0; i<buffsize; i += 16) {
u32 f, a = atmp, b = btmp, c = ctmp, d = dtmp;
for(u32 j = 0; j<64; j++) {
if (j<16) f = md5f(b, c, d);
else if (j<32) f = md5g(b, c, d);
else if (j<48) f = md5h(b, c, d);
else f = md5i(b, c, d);
u32 tmp = d;
d = c;
c = b;
b = b+shift((a+f+k[j]+buff[i+idx[j]]), s[j]);
a = tmp;
}
atmp += a;
btmp += b;
ctmp += c;
dtmp += d;
}
return tohex(atmp)+tohex(btmp)+tohex(ctmp)+tohex(dtmp);
}
var builtin_md5(context* ctx, gc* ngc) {
auto str = ctx->localr[1];
if (!str.is_str()) {
return nas_err("native::md5", "\"str\" must be string");
}
return ngc->newstr(md5(str.str()));
}
class time_stamp {
private:
std::chrono::high_resolution_clock::time_point stamp;
public:
time_stamp() {
stamp = std::chrono::high_resolution_clock::now();
}
void make_stamp() {
stamp = std::chrono::high_resolution_clock::now();
}
auto elapsed_milliseconds() const {
const auto duration = std::chrono::high_resolution_clock::now() - stamp;
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
auto elapsed_microseconds() const {
const auto duration = std::chrono::high_resolution_clock::now() - stamp;
return std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
}
};
void time_stamp_destructor(void* ptr) {
delete static_cast<time_stamp*>(ptr);
}
var builtin_maketimestamp(context* ctx, gc* ngc) {
auto res = ngc->alloc(vm_type::vm_ghost);
res.ghost().set(
"nasal-time-stamp",
time_stamp_destructor,
nullptr,
new time_stamp
);
return res;
}
var builtin_time_stamp(context* ctx, gc* ngc) {
auto object = ctx->localr[1];
if (!object.object_check("nasal-time-stamp")) {
return nil;
}
auto stamp = static_cast<time_stamp*>(object.ghost().pointer);
stamp->make_stamp();
return nil;
}
var builtin_elapsed_millisecond(context* ctx, gc* ngc) {
auto object = ctx->localr[1];
if (!object.object_check("nasal-time-stamp")) {
return var::num(-1);
}
auto stamp = static_cast<time_stamp*>(object.ghost().pointer);
return var::num(static_cast<f64>(stamp->elapsed_milliseconds()));
}
var builtin_elapsed_microsecond(context* ctx, gc* ngc) {
auto object = ctx->localr[1];
if (!object.object_check("nasal-time-stamp")) {
return var::num(-1);
}
auto stamp = static_cast<time_stamp*>(object.ghost().pointer);
return var::num(static_cast<f64>(stamp->elapsed_microseconds()));
}
var builtin_gcextend(context* ctx, gc* ngc) {
auto type = ctx->localr[1];
if (!type.is_str()) {
return nil;
}
const auto& s = type.str();
if (s=="str") {
ngc->extend(vm_type::vm_str);
} else if (s=="vec") {
ngc->extend(vm_type::vm_vec);
} else if (s=="hash") {
ngc->extend(vm_type::vm_hash);
} else if (s=="func") {
ngc->extend(vm_type::vm_func);
} else if (s=="upval") {
ngc->extend(vm_type::vm_upval);
} else if (s=="ghost") {
ngc->extend(vm_type::vm_ghost);
} else if (s=="co") {
ngc->extend(vm_type::vm_co);
}
return nil;
}
var builtin_gcinfo(context* ctx, gc* ngc) {
const auto den = std::chrono::high_resolution_clock::duration::period::den;
var res = ngc->alloc(vm_type::vm_hash);
f64 total = 0;
for(u32 i = 0; i<gc_type_size; ++i) {
total += static_cast<f64>(ngc->gcnt[i]);
}
auto& map = res.hash().elems;
const auto worktime = static_cast<f64>(ngc->worktime);
const auto max_time = static_cast<f64>(ngc->max_time);
const auto max_mark_time = static_cast<f64>(ngc->max_mark_time);
const auto max_sweep_time = static_cast<f64>(ngc->max_sweep_time);
// using ms
map["total"] = var::num(worktime/den*1000);
map["average"] = var::num(worktime/den*1000/total);
map["max_gc"] = var::num(max_time/den*1000);
map["max_mark"] = var::num(max_mark_time/den*1000);
map["max_sweep"] = var::num(max_sweep_time/den*1000);
return res;
}
var builtin_logtime(context* ctx, gc* ngc) {
time_t t = time(nullptr);
tm* tm_t = localtime(&t);
char s[64];
snprintf(
s, 64, "%d-%.2d-%.2d %.2d:%.2d:%.2d",
tm_t->tm_year+1900,
tm_t->tm_mon+1,
tm_t->tm_mday,
tm_t->tm_hour,
tm_t->tm_min,
tm_t->tm_sec
);
return ngc->newstr(s);
}
var builtin_ghosttype(context* ctx, gc* ngc) {
auto arg = ctx->localr[1];
if (!arg.is_ghost()) {
return nas_err("native::ghosttype", "this is not a ghost object.");
}
const auto& name = arg.ghost().get_ghost_name();
// https://wiki.flightgear.org/Nasal_library#ghosttype()
// tolds us if no name has been set,
// return a unique id (the pointer to the instance)
if (!name.length()) {
std::stringstream ss;
ss << "0x" << std::hex;
ss << reinterpret_cast<u64>(arg.ghost().pointer) << std::dec;
return ngc->newstr(ss.str());
}
return ngc->newstr(name);
}
var builtin_set_utf8_output(context* ctx, gc* ngc) {
#ifdef _WIN32
// allow 65001 code page
SetConsoleOutputCP(CP_UTF8);
#endif
// do nothing on other platforms
return nil;
}
nasal_builtin_table builtin[] = {
{"__print", builtin_print},
{"__println", builtin_println},
{"__exit", builtin_exit},
{"__abort", builtin_abort},
{"__append", builtin_append},
{"__setsize", builtin_setsize},
{"__system", builtin_system},
{"__input", builtin_input},
{"__split", builtin_split},
{"__rand", builtin_rand},
{"__id", builtin_id},
{"__int", builtin_int},
{"__floor", builtin_floor},
{"__ceil", builtin_ceil},
{"__num", builtin_num},
{"__pop", builtin_pop},
{"__str", builtin_str},
{"__size", builtin_size},
{"__time", builtin_time},
{"__contains", builtin_contains},
{"__delete", builtin_delete},
{"__keys", builtin_keys},
{"__die", builtin_die},
{"__find", builtin_find},
{"__type", builtin_type},
{"__substr", builtin_substr},
{"__streq", builtin_streq},
{"__left", builtin_left},
{"__right", builtin_right},
{"__cmp", builtin_cmp},
{"__chr", builtin_chr},
{"__char", builtin_char},
{"__values", builtin_values},
{"__sleep", builtin_sleep},
{"__platform", builtin_platform},
{"__arch", builtin_arch},
{"__md5", builtin_md5},
{"__maketimestamp", builtin_maketimestamp},
{"__time_stamp", builtin_time_stamp},
{"__elapsed_millisecond", builtin_elapsed_millisecond},
{"__elapsed_microsecond", builtin_elapsed_microsecond},
{"__gcextd", builtin_gcextend},
{"__gcinfo", builtin_gcinfo},
{"__logtime", builtin_logtime},
{"__ghosttype", builtin_ghosttype},
{"__set_utf8_output", builtin_set_utf8_output},
{nullptr, nullptr}
};
}

96
src/natives/builtin.h Normal file
View File

@@ -0,0 +1,96 @@
#pragma once
#include "nasal.h"
#include "nasal_type.h"
#include "nasal_gc.h"
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef _MSC_VER
#pragma warning (disable:4566) // i know i'm using utf-8, fuck you
#pragma warning (disable:4244)
#pragma warning (disable:4267)
#pragma warning (disable:4996)
#define _CRT_SECURE_NO_DEPRECATE 1
#define _CRT_NONSTDC_NO_DEPRECATE 1
#endif
#include <sstream>
#include <cmath>
#include <thread>
// for environ
#if defined __APPLE__
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
#endif
namespace nasal {
var builtin_unsafe(context*, gc*);
var builtin_print(context*, gc*);
var builtin_println(context*, gc*);
var builtin_exit(context*, gc*);
var builtin_abort(context*, gc*);
var builtin_append(context*, gc*);
var builtin_setsize(context*, gc*);
var builtin_system(context*, gc*);
var builtin_input(context*, gc*);
var builtin_split(context*, gc*);
var builtin_rand(context*, gc*);
var builtin_id(context*, gc*);
var builtin_int(context*, gc*);
var builtin_floor(context*, gc*);
var builtin_ceil(context*, gc*);
var builtin_num(context*, gc*);
var builtin_pop(context*, gc*);
var builtin_str(context*, gc*);
var builtin_size(context*, gc*);
var builtin_time(context*, gc*);
var builtin_contains(context*, gc*);
var builtin_delete(context*, gc*);
var builtin_keys(context*, gc*);
var builtin_die(context*, gc*);
var builtin_find(context*, gc*);
var builtin_type(context*, gc*);
var builtin_substr(context*, gc*);
var builtin_streq(context*, gc*);
var builtin_left(context*, gc*);
var builtin_right(context*, gc*);
var builtin_cmp(context*, gc*);
var builtin_chr(context*, gc*);
var builtin_char(context*, gc*);
var builtin_values(context*, gc*);
var builtin_sleep(context*, gc*);
var builtin_platform(context*, gc*);
var builtin_arch(context*, gc*);
// md5 related functions
std::string tohex(u32);
std::string md5(const std::string&);
var builtin_md5(context*, gc*);
var builtin_maketimestamp(context*, gc*);
var builtin_time_stamp(context*, gc*);
var builtin_elapsed_millisecond(context*, gc*);
var builtin_elapsed_microsecond(context*, gc*);
var builtin_gcextend(context*, gc*);
var builtin_gcinfo(context*, gc*);
var builtin_logtime(context*, gc*);
var builtin_ghosttype(context*, gc*);
// only useful on windows platform
var builtin_set_utf8_output(context*, gc*);
// register builtin function's name and it's address here in this table below
// this table must end with {nullptr, nullptr}
struct nasal_builtin_table {
const char* name;
var (*func)(context*, gc*);
};
extern nasal_builtin_table builtin[];
}

141
src/natives/coroutine.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "natives/coroutine.h"
namespace nasal {
var builtin_cocreate(context* ctx, gc* ngc) {
// ```
// +-------------+
// | old pc | <- top[0]
// +-------------+
// | old localr | <- top[-1]
// +-------------+
// | old upvalr | <- top[-2]
// +-------------+
// | local scope |
// | ... |
// +-------------+ <- local pointer stored in localr
// | old funcr | <- old function stored in funcr
// +-------------+
// ```
auto coroutine_function = ctx->localr[1];
if (!coroutine_function.is_func()) {
return nas_err(
"coroutine::create",
"must use a function to create coroutine"
);
}
if (ngc->cort) {
return nas_err(
"coroutine::create",
"cannot create another coroutine in a coroutine"
);
}
auto coroutine_object = ngc->alloc(vm_type::vm_co);
auto& coroutine = coroutine_object.co();
coroutine.ctx.pc = coroutine_function.func().entry-1;
coroutine.ctx.top[0] = nil;
coroutine.ctx.localr = coroutine.ctx.top+1;
coroutine.ctx.top = coroutine.ctx.localr +
coroutine_function.func().local_size;
coroutine.ctx.localr[0] = coroutine_function.func().local[0];
// store old upvalr on stack
coroutine.ctx.top[0] = nil;
coroutine.ctx.top++;
// store old localr on stack
coroutine.ctx.top[0] = var::addr(nullptr);
coroutine.ctx.top++;
// store old pc on stack
// set to zero to make op_ret recognizing this as coroutine function
coroutine.ctx.top[0] = var::ret(0);
// make sure the coroutine function can use correct upvalues
coroutine.ctx.funcr = coroutine_function;
coroutine.status = nas_co::status::suspended;
return coroutine_object;
}
var builtin_coresume(context* ctx, gc* ngc) {
if (ngc->cort) {
return nas_err(
"coroutine::resume",
"cannot start another coroutine when one is running"
);
}
auto main_local_frame = ctx->localr;
auto coroutine_object = main_local_frame[1];
// return nil if is not a coroutine object or coroutine exited
if (!coroutine_object.is_coroutine() ||
coroutine_object.co().status==nas_co::status::dead) {
return nil;
}
// change to coroutine context
ngc->context_change(&coroutine_object.co());
// fetch coroutine's stack top and return
// then coroutine's stack top will catch this return value
// so the coroutine's stack top in fact is not changed
if (ngc->running_context->top[0].is_ret()) {
// when first calling this coroutine, the stack top must be vm_ret
return ngc->running_context->top[0];
}
// after first calling the coroutine, each time coroutine.yield triggered
// a new space will be reserved on stack with value nil
// so we could fill this place with args
// the coroutine seems like coroutine.yield returns the value
// but in fact coroutine.yield stop the coroutine
// until main context calls the coroutine.resume
return main_local_frame[2];
}
var builtin_coyield(context* ctx, gc* ngc) {
if (!ngc->cort) {
return nas_err("coroutine::yield", "no coroutine is running");
}
// get coroutine local frame
auto coroutine_local_frame = ctx->localr;
// vm context will set to main context
ngc->context_reserve();
// then this will return value to main's stack top[0]
// the procedure seems like coroutine.resume returns the value
// but in fact coroutine.resume stop the main context
// until coroutine calls the coroutine.yield
return coroutine_local_frame[1];
}
var builtin_costatus(context* ctx, gc* ngc) {
auto coroutine_object = ctx->localr[1];
if (!coroutine_object.is_coroutine()) {
return ngc->newstr("error");
}
switch(coroutine_object.co().status) {
case nas_co::status::suspended: return ngc->newstr("suspended");
case nas_co::status::running: return ngc->newstr("running");
case nas_co::status::dead: return ngc->newstr("dead");
}
return nil;
}
var builtin_corun(context* ctx, gc* ngc) {
return ngc->cort? one:zero;
}
nasal_builtin_table coroutine_native[] = {
{"__cocreate", builtin_cocreate},
{"__coresume", builtin_coresume},
{"__coyield", builtin_coyield},
{"__costatus", builtin_costatus},
{"__corun", builtin_corun},
{nullptr, nullptr}
};
}

17
src/natives/coroutine.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
namespace nasal {
var builtin_cocreate(context*, gc*);
var builtin_coresume(context*, gc*);
var builtin_coyield(context*, gc*);
var builtin_costatus(context*, gc*);
var builtin_corun(context*, gc*);
extern nasal_builtin_table coroutine_native[];
}

142
src/natives/dylib_lib.cpp Normal file
View File

@@ -0,0 +1,142 @@
#include "natives/dylib_lib.h"
namespace nasal {
const auto dynamic_library_type_name = "dylib";
const auto function_address_type_name = "faddr";
void dynamic_library_destructor(void* pointer) {
#ifdef _WIN32
FreeLibrary(static_cast<HMODULE>(pointer));
#else
dlclose(pointer);
#endif
}
var builtin_dlopen(context* ctx, gc* ngc) {
auto dlname = ctx->localr[1];
if (!dlname.is_str()) {
return nas_err("dylib::dlopen", "\"libname\" must be string");
}
// get library pointer
#ifdef _WIN32
wchar_t* wide_string = new wchar_t[dlname.str().size()+1];
if (!wide_string) {
return nas_err("dylib::dlopen", "malloc failed");
}
memset(wide_string, 0, sizeof(wchar_t) * dlname.str().size() + 1);
mbstowcs(wide_string, dlname.str().c_str(), dlname.str().size() + 1);
// load library by using wide string name
void* dynamic_library_pointer = LoadLibraryA(dlname.str().c_str());
delete []wide_string;
#else
void* dynamic_library_pointer = dlopen(
dlname.str().c_str(), RTLD_LOCAL|RTLD_LAZY
);
#endif
// check library pointer and insert into returned hashmap
if (!dynamic_library_pointer) {
return nas_err("dylib::dlopen",
"cannot open dynamic lib <" + dlname.str() + ">"
);
}
auto return_hash = ngc->temp = ngc->alloc(vm_type::vm_hash);
auto library_object = ngc->alloc(vm_type::vm_ghost);
library_object.ghost().set(
dynamic_library_type_name,
dynamic_library_destructor,
nullptr,
dynamic_library_pointer
);
return_hash.hash().elems["lib"] = library_object;
// get "get" function, to get the register table
#ifdef _WIN32
void* register_table_get_function = reinterpret_cast<void*>(GetProcAddress(
static_cast<HMODULE>(library_object.ghost().pointer), "get"
));
#else
void* register_table_get_function = dlsym(
library_object.ghost().pointer, "get"
);
#endif
if (!register_table_get_function) {
return nas_err("dylib::dlopen", "cannot find <get> function");
}
// get function pointer by name
auto table = reinterpret_cast<get_func_ptr>(register_table_get_function)();
if (!table) {
return nas_err("dylib::dlopen", "failed to get module functions");
}
for(u32 i = 0; table[i].name; ++i) {
auto function_pointer = reinterpret_cast<void*>(table[i].fd);
auto function_object = ngc->alloc(vm_type::vm_ghost);
function_object.ghost().set(
function_address_type_name,
nullptr,
nullptr,
function_pointer
);
return_hash.hash().elems[table[i].name] = function_object;
}
ngc->temp = nil;
return return_hash;
}
var builtin_dlclose(context* ctx, gc* ngc) {
auto library_pointer = ctx->localr[1];
if (!library_pointer.object_check(dynamic_library_type_name)) {
return nas_err("dylib::dlclose", "\"lib\" is not a valid dynamic lib");
}
library_pointer.ghost().clear();
return nil;
}
var builtin_dlcallv(context* ctx, gc* ngc) {
auto function_object = ctx->localr[1];
auto arguments = ctx->localr[2];
if (!function_object.object_check(function_address_type_name)) {
return nas_err("dylib::dlcall",
"\"ptr\" is not a valid function pointer"
);
}
auto& vec = arguments.vec().elems;
return reinterpret_cast<module_func>(function_object.ghost().pointer)(
vec.data(),
vec.size(),
ngc
);
}
var builtin_dlcall(context* ctx, gc* ngc) {
auto function_object = ctx->localr[1];
if (!function_object.object_check(function_address_type_name)) {
return nas_err("dylib::dlcall",
"\"ptr\" is not a valid function pointer"
);
}
// function pointer is at ctx->localr[1]
// so arguments starts from ctx->localr[2]
var* local_frame_start = ctx->localr + 2;
usize local_frame_size = ngc->running_context->top - local_frame_start;
return reinterpret_cast<module_func>(function_object.ghost().pointer)(
local_frame_start,
local_frame_size,
ngc
);
}
nasal_builtin_table dylib_lib_native[] = {
{"__dlopen", builtin_dlopen},
{"__dlclose", builtin_dlclose},
{"__dlcallv", builtin_dlcallv},
{"__dlcall", builtin_dlcall},
{nullptr, nullptr}
};
}

25
src/natives/dylib_lib.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#include <sys/wait.h>
#endif
namespace nasal {
void dynamic_library_destructor(void*);
var builtin_dlopen(context*, gc*);
var builtin_dlclose(context*, gc*);
var builtin_dlcallv(context*, gc*);
var builtin_dlcall(context*, gc*);
extern nasal_builtin_table dylib_lib_native[];
}

41
src/natives/fg_props.cpp Normal file
View File

@@ -0,0 +1,41 @@
#include "natives/fg_props.h"
#include <fstream>
namespace nasal {
var builtin_logprint(context* ctx, gc* ngc) {
auto local = ctx->localr;
auto level = local[1];
auto elems = local[2];
if (!elems.is_vec()) {
return nas_err("fg_env::logprint", "received argument is not vector.");
}
std::ofstream out("fgfs.log", std::ios::app);
switch (static_cast<u32>(level.num())) {
case SG_LOG_BULK: out << "[LOG_BULK]"; break;
case SG_LOG_DEBUG: out << "[LOG_DEBUG]"; break;
case SG_LOG_INFO: out << "[LOG_INFO]"; break;
case SG_LOG_WARN: out << "[LOG_WARN]"; break;
case SG_LOG_ALERT: out << "[LOG_ALERT]"; break;
case SG_DEV_WARN: out << "[DEV_WARN]"; break;
case SG_DEV_ALERT: out << "[DEV_ALERT]"; break;
case SG_MANDATORY_INFO: out << "[MANDATORY_INFO]"; break;
default:
return nas_err("fg_env::logprint",
"incorrect log level " + std::to_string(level.num())
);
}
for(auto& value : elems.vec().elems) {
out << value << " ";
}
out << "\n";
return nil;
}
nasal_builtin_table flight_gear_native[] = {
{"_logprint", builtin_logprint},
{nullptr, nullptr}
};
}

22
src/natives/fg_props.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
namespace nasal {
#define SG_LOG_BULK 1
#define SG_LOG_DEBUG 2
#define SG_LOG_INFO 3
#define SG_LOG_WARN 4
#define SG_LOG_ALERT 5
#define SG_DEV_WARN 7
#define SG_DEV_ALERT 8
#define SG_MANDATORY_INFO 9
var builtin_logprint(context*, gc*);
extern nasal_builtin_table flight_gear_native[];
}

249
src/natives/io_lib.cpp Normal file
View File

@@ -0,0 +1,249 @@
#include "natives/io_lib.h"
#include "util/fs.h"
#include <fstream>
#include <sys/stat.h>
namespace nasal {
const auto file_type_name = "file";
void filehandle_destructor(void* ptr) {
fclose(static_cast<FILE*>(ptr));
}
var builtin_readfile(context* ctx, gc* ngc) {
auto filename = ctx->localr[1];
if (!filename.is_str()) {
return nas_err("io::readfile", "\"filename\" must be string");
}
std::ifstream in(filename.str(), std::ios::binary);
std::stringstream rd;
if (!in.fail()) {
rd << in.rdbuf();
}
return ngc->newstr(rd.str());
}
var builtin_fout(context* ctx, gc* ngc) {
auto local = ctx->localr;
auto filename = local[1];
auto source = local[2];
if (!filename.is_str()) {
return nas_err("io::fout", "\"filename\" must be string");
}
std::ofstream out(filename.str());
if (out.fail()) {
return nas_err("io::fout", "cannot open <" + filename.str() + ">");
}
out << source;
return nil;
}
var builtin_exists(context* ctx, gc* ngc) {
auto filename = ctx->localr[1];
if (!filename.is_str()) {
return zero;
}
return fs::exists(filename.str())? one:zero;
}
var builtin_open(context* ctx, gc* ngc) {
auto local = ctx->localr;
auto name = local[1];
auto mode = local[2];
if (!name.is_str()) {
return nas_err("io::open", "\"filename\" must be string");
}
if (!mode.is_str()) {
return nas_err("io::open", "\"mode\" must be string");
}
auto file_descriptor = fopen(name.str().c_str(), mode.str().c_str());
if (!file_descriptor) {
return nas_err("io::open", "failed to open file <" + name.str() + ">");
}
var return_object = ngc->alloc(vm_type::vm_ghost);
return_object.ghost().set(
file_type_name, filehandle_destructor, nullptr, file_descriptor
);
return return_object;
}
var builtin_close(context* ctx, gc* ngc) {
var file_descriptor = ctx->localr[1];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::close", "not a valid filehandle");
}
file_descriptor.ghost().clear();
return nil;
}
var builtin_read(context* ctx, gc* ngc) {
auto local = ctx->localr;
auto file_descriptor = local[1];
auto buffer = local[2];
auto length = local[3];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::read", "not a valid filehandle");
}
if (!buffer.is_str() || buffer.val.gcobj->immutable) {
return nas_err("io::read", "\"buf\" must be mutable string");
}
if (!length.is_num()) {
return nas_err("io::read", "\"len\" must be number");
}
if (length.num()<=0 || length.num()>=(1<<30)) {
return nas_err("io::read", "\"len\" less than 1 or too large");
}
auto temp_buffer = new char[static_cast<usize>(length.num())+1];
if (!temp_buffer) {
return nas_err("io::read", "malloc failed");
}
auto read_size = fread(
temp_buffer, 1, length.num(),
static_cast<FILE*>(file_descriptor.ghost().pointer)
);
buffer.str() = temp_buffer;
buffer.val.gcobj->immutable = true;
delete []temp_buffer;
return var::num(read_size);
}
var builtin_write(context* ctx, gc* ngc) {
auto local = ctx->localr;
auto file_descriptor = local[1];
auto source = local[2];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::write", "not a valid filehandle");
}
if (!source.is_str()) {
return nas_err("io::write", "\"str\" must be string");
}
return var::num(static_cast<f64>(fwrite(
source.str().c_str(), 1, source.str().length(),
static_cast<FILE*>(file_descriptor.ghost().pointer)
)));
}
var builtin_seek(context* ctx, gc* ngc) {
auto local = ctx->localr;
auto file_descriptor = local[1];
auto position = local[2];
auto whence = local[3];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::seek", "not a valid filehandle");
}
return var::num(static_cast<f64>(fseek(
static_cast<FILE*>(file_descriptor.ghost().pointer),
position.num(),
whence.num()
)));
}
var builtin_tell(context* ctx, gc* ngc) {
auto file_descriptor = ctx->localr[1];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::tell", "not a valid filehandle");
}
return var::num(static_cast<f64>(
ftell(static_cast<FILE*>(file_descriptor.ghost().pointer))
));
}
var builtin_readln(context* ctx, gc* ngc) {
auto file_descriptor = ctx->localr[1];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::readln", "not a valid filehandle");
}
auto result = ngc->alloc(vm_type::vm_str);
char c;
while((c = fgetc(static_cast<FILE*>(file_descriptor.ghost().pointer)))!=EOF) {
if (c=='\r') {
continue;
}
if (c=='\n') {
return result;
}
result.str().push_back(c);
}
if (result.str().length()) {
return result;
}
return nil;
}
var builtin_stat(context* ctx, gc* ngc) {
auto name = ctx->localr[1];
if (!name.is_str()) {
return nas_err("io::stat", "\"filename\" must be string");
}
struct stat buffer;
if (stat(name.str().c_str(), &buffer)<0) {
return nas_err("io::stat", "failed to open file <" + name.str() + ">");
}
auto result = ngc->alloc(vm_type::vm_vec);
result.vec().elems = {
var::num(static_cast<f64>(buffer.st_dev)),
var::num(static_cast<f64>(buffer.st_ino)),
var::num(static_cast<f64>(buffer.st_mode)),
var::num(static_cast<f64>(buffer.st_nlink)),
var::num(static_cast<f64>(buffer.st_uid)),
var::num(static_cast<f64>(buffer.st_gid)),
var::num(static_cast<f64>(buffer.st_rdev)),
var::num(static_cast<f64>(buffer.st_size)),
var::num(static_cast<f64>(buffer.st_atime)),
var::num(static_cast<f64>(buffer.st_mtime)),
var::num(static_cast<f64>(buffer.st_ctime))
};
return result;
}
var builtin_eof(context* ctx, gc* ngc) {
auto file_descriptor = ctx->localr[1];
if (!file_descriptor.object_check(file_type_name)) {
return nas_err("io::readln", "not a valid filehandle");
}
return var::num(static_cast<f64>(
feof(static_cast<FILE*>(file_descriptor.ghost().pointer))
));
}
var builtin_stdin(context* ctx, gc* ngc) {
auto file_descriptor = ngc->alloc(vm_type::vm_ghost);
file_descriptor.ghost().set(file_type_name, nullptr, nullptr, stdin);
return file_descriptor;
}
var builtin_stdout(context* ctx, gc* ngc) {
auto file_descriptor = ngc->alloc(vm_type::vm_ghost);
file_descriptor.ghost().set(file_type_name, nullptr, nullptr, stdout);
return file_descriptor;
}
var builtin_stderr(context* ctx, gc* ngc) {
auto file_descriptor = ngc->alloc(vm_type::vm_ghost);
file_descriptor.ghost().set(file_type_name, nullptr, nullptr, stderr);
return file_descriptor;
}
nasal_builtin_table io_lib_native[] = {
{"__readfile", builtin_readfile},
{"__fout", builtin_fout},
{"__exists", builtin_exists},
{"__open", builtin_open},
{"__close", builtin_close},
{"__read", builtin_read},
{"__write", builtin_write},
{"__seek", builtin_seek},
{"__tell", builtin_tell},
{"__readln", builtin_readln},
{"__stat", builtin_stat},
{"__eof", builtin_eof},
{"__stdin", builtin_stdin},
{"__stdout", builtin_stdout},
{"__stderr", builtin_stderr},
{nullptr, nullptr}
};
}

35
src/natives/io_lib.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
#ifndef _MSC_VER
#include <unistd.h>
#else
#include <io.h>
#endif
namespace nasal {
void filehandle_destructor(void*);
var builtin_readfile(context*, gc*);
var builtin_fout(context*, gc*);
var builtin_exists(context*, gc*);
var builtin_open(context*, gc*);
var builtin_close(context*, gc*);
var builtin_read(context*, gc*);
var builtin_write(context*, gc*);
var builtin_seek(context*, gc*);
var builtin_tell(context*, gc*);
var builtin_readln(context*, gc*);
var builtin_stat(context*, gc*);
var builtin_eof(context*, gc*);
var builtin_stdin(context*, gc*);
var builtin_stdout(context*, gc*);
var builtin_stderr(context*, gc*);
extern nasal_builtin_table io_lib_native[];
}

406
src/natives/json_lib.cpp Normal file
View File

@@ -0,0 +1,406 @@
#include "natives/json_lib.h"
#include "util/util.h"
#include <iostream>
#include <cstring>
#include <sstream>
#include <vector>
namespace nasal {
enum class json_token_type {
tok_eof,
tok_lbrace,
tok_rbrace,
tok_lbrkt,
tok_rbrkt,
tok_comma,
tok_colon,
tok_str,
tok_num,
tok_id,
tok_bool
};
std::string get_content(json_token_type type) {
switch(type) {
case json_token_type::tok_eof: return "eof";
case json_token_type::tok_lbrace: return "`{`";
case json_token_type::tok_rbrace: return "`}`";
case json_token_type::tok_lbrkt: return "`[`";
case json_token_type::tok_rbrkt: return "`]`";
case json_token_type::tok_comma: return "`,`";
case json_token_type::tok_colon: return "`:`";
case json_token_type::tok_str: return "string";
case json_token_type::tok_num: return "number";
case json_token_type::tok_id: return "identifier";
case json_token_type::tok_bool: return "boolean";
}
// unreachable
return "";
}
struct token {
json_token_type type;
std::string content;
};
class json {
private:
std::string text = "";
usize line = 1;
usize ptr = 0;
token this_token;
var temp_stack = nil;
std::string info = "";
private:
std::string var_generate(var&);
std::string vector_generate(nas_vec&);
std::string hash_generate(nas_hash&);
private:
bool is_num(char c) {
return std::isdigit(c);
}
bool is_id(char c) {
return std::isalpha(c) || c=='_';
}
bool check(char c) {
return c=='{' || c=='}' || c=='[' || c==']' ||
c==':' || c==',' || c=='"' || c=='\'' ||
c=='-' || c=='+' || is_num(c) || is_id(c);
}
void next();
void match(json_token_type);
void vector_member(nas_vec&, gc*);
var vector_object_generate(gc*);
void hash_member(nas_hash&, gc*);
var hash_object_generate(gc*);
void check_eof();
std::string& error_info() {
return info;
}
public:
std::string stringify(var&);
var parse(const std::string&, gc*);
const std::string& get_error() { return error_info(); }
};
std::string json::var_generate(var& value) {
switch(value.type) {
case vm_type::vm_num: {
std::stringstream out;
out << value.num();
if (std::isnan(value.num())) {
error_info() += "json::stringify: cannot generate number nan\n";
}
if (std::isinf(value.num())) {
error_info() += "json::stringify: cannot generate number inf\n";
}
return out.str();
}
case vm_type::vm_str: return "\"" + value.str() + "\"";
case vm_type::vm_vec: return vector_generate(value.vec());
case vm_type::vm_hash: return hash_generate(value.hash());
case vm_type::vm_func:
error_info() += "json::stringify: cannot generate function\n"; break;
case vm_type::vm_ghost:
error_info() += "json::stringify: cannot generate ghost type\n"; break;
case vm_type::vm_map:
error_info() += "json::stringify: cannot generate namespace type\n"; break;
default: break;
}
return "\"undefined\"";
}
std::string json::vector_generate(nas_vec& vect) {
// avoid stackoverflow
if (vect.printed) {
error_info() += "json::stringify: get vector containing itself\n";
return "undefined";
}
vect.printed = true;
std::string out = "[";
for(auto& i : vect.elems) {
out += var_generate(i) + ",";
}
if (out.back()==',') {
out.pop_back();
}
out += "]";
vect.printed = false;
return out;
}
std::string json::hash_generate(nas_hash& hash) {
// avoid stackoverflow
if (hash.printed) {
error_info() += "json::stringify: get hash containing itself\n";
return "undefined";
}
hash.printed = true;
std::string out = "{";
for(auto& i : hash.elems) {
out += "\"" + i.first + "\":";
out += var_generate(i.second) + ",";
}
if (out.back()==',') {
out.pop_back();
}
out += "}";
hash.printed = false;
return out;
}
std::string json::stringify(var& object) {
error_info() = "";
if (object.is_vec()) {
return vector_generate(object.vec());
} else if (object.is_hash()) {
return hash_generate(object.hash());
}
return "[]";
}
void json::next() {
while(ptr<text.length() && !check(text[ptr])) {
if (text[ptr]=='\n') {
++line;
} else if (text[ptr]!=' ' && text[ptr]!='\t' && text[ptr]!='\r') {
error_info() += "json::parse: line " + std::to_string(line);
error_info() += ": invalid character `0x";
error_info() += util::char_to_hex(text[ptr]);
error_info() += "`\n";
}
++ptr;
}
if (ptr>=text.length()) {
this_token = {json_token_type::tok_eof, "eof"};
return;
}
auto c = text[ptr];
switch(c) {
case '{': this_token = {json_token_type::tok_lbrace, "{"}; ++ptr; return;
case '}': this_token = {json_token_type::tok_rbrace, "}"}; ++ptr; return;
case '[': this_token = {json_token_type::tok_lbrkt, "["}; ++ptr; return;
case ']': this_token = {json_token_type::tok_rbrkt, "]"}; ++ptr; return;
case ',': this_token = {json_token_type::tok_comma, ","}; ++ptr; return;
case ':': this_token = {json_token_type::tok_colon, ":"}; ++ptr; return;
default: break;
}
if (is_num(c) || c=='-' || c=='+') {
auto temp = std::string(1, c);
++ptr;
while(ptr<text.length() && (
is_num(text[ptr]) ||
text[ptr]=='.' ||
text[ptr]=='e' ||
text[ptr]=='-' ||
text[ptr]=='+')) {
temp += text[ptr];
++ptr;
}
--ptr;
this_token = {json_token_type::tok_num, temp};
} else if (is_id(c)) {
auto temp = std::string(1, c);
++ptr;
while(ptr<text.length() && (is_id(text[ptr]) || is_num(text[ptr]))) {
temp += text[ptr];
++ptr;
}
--ptr;
if (temp=="true" || temp=="false") {
this_token = {json_token_type::tok_bool, temp};
} else {
this_token = {json_token_type::tok_id, temp};
}
} else if (c=='"' || c=='\'') {
auto begin = c;
auto temp = std::string("");
++ptr;
while(ptr<text.length() && text[ptr]!=begin) {
temp += text[ptr];
++ptr;
if (text[ptr-1]=='\\' && ptr<text.length()) {
temp += text[ptr];
++ptr;
}
}
this_token = {json_token_type::tok_str, temp};
}
++ptr;
return;
}
void json::match(json_token_type type) {
if (this_token.type!=type) {
error_info() += "json::parse: line " + std::to_string(line);
error_info() += ": expect " + get_content(type) + " but get `";
error_info() += this_token.content + "`.\n";
}
next();
return;
}
void json::vector_member(nas_vec& vec, gc* ngc) {
if (this_token.type==json_token_type::tok_lbrace) {
vec.elems.push_back(hash_object_generate(ngc));
} else if (this_token.type==json_token_type::tok_lbrkt) {
vec.elems.push_back(vector_object_generate(ngc));
} else if (this_token.type==json_token_type::tok_str) {
vec.elems.push_back(ngc->newstr(this_token.content));
next();
} else if (this_token.type==json_token_type::tok_num) {
vec.elems.push_back(var::num(util::str_to_num(this_token.content.c_str())));
next();
}
}
var json::vector_object_generate(gc* ngc) {
auto vect_object = ngc->alloc(vm_type::vm_vec);
temp_stack.vec().elems.push_back(vect_object);
match(json_token_type::tok_lbrkt);
vector_member(vect_object.vec(), ngc);
while(this_token.type==json_token_type::tok_comma) {
match(json_token_type::tok_comma);
vector_member(vect_object.vec(), ngc);
}
match(json_token_type::tok_rbrkt);
temp_stack.vec().elems.pop_back();
return vect_object;
}
void json::hash_member(nas_hash& hash, gc* ngc) {
const auto name = this_token.content;
if (this_token.type==json_token_type::tok_rbrace) {
return;
}
if (this_token.type==json_token_type::tok_str) {
match(json_token_type::tok_str);
} else {
match(json_token_type::tok_id);
}
match(json_token_type::tok_colon);
if (this_token.type==json_token_type::tok_lbrace) {
hash.elems.insert({name, hash_object_generate(ngc)});
} else if (this_token.type==json_token_type::tok_lbrkt) {
hash.elems.insert({name, vector_object_generate(ngc)});
} else if (this_token.type==json_token_type::tok_str ||
this_token.type==json_token_type::tok_bool) {
hash.elems.insert({name, ngc->newstr(this_token.content)});
next();
} else if (this_token.type==json_token_type::tok_num) {
hash.elems.insert({name, var::num(util::str_to_num(this_token.content.c_str()))});
next();
}
}
var json::hash_object_generate(gc* ngc) {
auto hash_object = ngc->alloc(vm_type::vm_hash);
temp_stack.vec().elems.push_back(hash_object);
match(json_token_type::tok_lbrace);
hash_member(hash_object.hash(), ngc);
while(this_token.type==json_token_type::tok_comma) {
match(json_token_type::tok_comma);
hash_member(hash_object.hash(), ngc);
}
match(json_token_type::tok_rbrace);
temp_stack.vec().elems.pop_back();
return hash_object;
}
void json::check_eof() {
next();
if (this_token.type==json_token_type::tok_eof) {
return;
}
while (this_token.type!=json_token_type::tok_eof) {
error_info() += "json::parse: line " + std::to_string(line);
error_info() += ": expect " + get_content(json_token_type::tok_eof);
error_info() += " but get `" + this_token.content + "`.\n";
next();
}
}
var json::parse(const std::string& input, gc* ngc) {
line = 1;
ptr = 0;
this_token = {json_token_type::tok_eof, ""};
error_info() = "";
if (input.empty()) {
error_info() += "json::parse: empty string.\n";
return nil;
}
text = input;
next();
if (this_token.type==json_token_type::tok_lbrkt) {
ngc->temp = temp_stack = ngc->alloc(vm_type::vm_vec);
auto result = vector_object_generate(ngc);
check_eof();
ngc->temp = temp_stack = nil;
return result;
} else {
ngc->temp = temp_stack = ngc->alloc(vm_type::vm_vec);
auto result = hash_object_generate(ngc);
check_eof();
ngc->temp = temp_stack = nil;
return result;
}
return nil;
}
void json_destructor(void* ptr) {
delete static_cast<json*>(ptr);
}
var builtin_json_new(context* ctx, gc* ngc) {
var res = ngc->alloc(vm_type::vm_ghost);
res.ghost().set("nasal::json", json_destructor, nullptr, new json);
return res;
}
var builtin_json_stringify(context* ctx, gc* ngc) {
auto json_object = ctx->localr[1];
auto object = ctx->localr[2];
if (!json_object.object_check("nasal::json")) {
return nas_err("json::stringify", "expect a json object.");
}
auto json_ptr = static_cast<json*>(json_object.ghost().pointer);
return ngc->newstr(json_ptr->stringify(object));
}
var builtin_json_parse(context* ctx, gc* ngc) {
auto json_object = ctx->localr[1];
auto input_string = ctx->localr[2];
if (!json_object.object_check("nasal::json")) {
return nas_err("json::parse", "expect a json object.");
}
if (!input_string.is_str()) {
return nas_err("json::parse", "require string as the input.");
}
auto json_ptr = static_cast<json*>(json_object.ghost().pointer);
return json_ptr->parse(input_string.str(), ngc);
}
var builtin_json_get_error(context* ctx, gc* ngc) {
auto json_object = ctx->localr[1];
if (!json_object.object_check("nasal::json")) {
return nas_err("json::get_error", "expect a json object.");
}
auto json_ptr = static_cast<json*>(json_object.ghost().pointer);
return ngc->newstr(json_ptr->get_error());
}
nasal_builtin_table json_lib_native[] = {
{"_json_new", builtin_json_new},
{"_json_stringify", builtin_json_stringify},
{"_json_parse", builtin_json_parse},
{"_json_get_error", builtin_json_get_error},
{nullptr, nullptr}
};
}

16
src/natives/json_lib.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
namespace nasal {
var builtin_json_new(context*, gc*);
var builtin_json_stringify(context*, gc*);
var builtin_json_parse(context*, gc*);
var builtin_json_get_error(context*, gc*);
extern nasal_builtin_table json_lib_native[];
}

77
src/natives/math_lib.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include "natives/math_lib.h"
namespace nasal {
var builtin_pow(context* ctx, gc* ngc) {
auto x = ctx->localr[1];
auto y = ctx->localr[2];
if (!x.is_num() || !y.is_num()) {
return var::num(std::nan(""));
}
return var::num(std::pow(x.num(), y.num()));
}
var builtin_sin(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? sin(val.num()):std::nan(""));
}
var builtin_cos(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? cos(val.num()):std::nan(""));
}
var builtin_tan(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? tan(val.num()):std::nan(""));
}
var builtin_exp(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? exp(val.num()):std::nan(""));
}
var builtin_lg(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? log(val.num())/log(10.0):std::nan(""));
}
var builtin_ln(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? log(val.num()):std::nan(""));
}
var builtin_sqrt(context* ctx, gc* ngc) {
auto val = ctx->localr[1];
return var::num(val.is_num()? sqrt(val.num()):std::nan(""));
}
var builtin_atan2(context* ctx, gc* ngc) {
auto x = ctx->localr[1];
auto y = ctx->localr[2];
if (!x.is_num() || !y.is_num()) {
return var::num(std::nan(""));
}
return var::num(atan2(y.num(), x.num()));
}
var builtin_isnan(context* ctx, gc* ngc) {
auto x = ctx->localr[1];
return (x.is_num() && std::isnan(x.num()))? one:zero;
}
nasal_builtin_table math_lib_native[] = {
{"__pow", builtin_pow},
{"__sin", builtin_sin},
{"__cos", builtin_cos},
{"__tan", builtin_tan},
{"__exp", builtin_exp},
{"__lg", builtin_lg},
{"__ln", builtin_ln},
{"__sqrt", builtin_sqrt},
{"__atan2", builtin_atan2},
{"__isnan", builtin_isnan},
{nullptr, nullptr}
};
}

22
src/natives/math_lib.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
namespace nasal {
var builtin_pow(context*, gc*);
var builtin_sin(context*, gc*);
var builtin_cos(context*, gc*);
var builtin_tan(context*, gc*);
var builtin_exp(context*, gc*);
var builtin_lg(context*, gc*);
var builtin_ln(context*, gc*);
var builtin_sqrt(context*, gc*);
var builtin_atan2(context*, gc*);
var builtin_isnan(context*, gc*);
extern nasal_builtin_table math_lib_native[];
}

99
src/natives/regex_lib.cpp Normal file
View File

@@ -0,0 +1,99 @@
#include "natives/regex_lib.h"
namespace nasal {
var builtin_regex_match(context* ctx, gc* ngc) {
auto source = ctx->localr[1];
auto reg_str = ctx->localr[2];
if (!source.is_str()) {
return nas_err("regex::match", "\"src\" must be a string");
}
if (!reg_str.is_str()) {
return nas_err("regex::match", "\"reg\" must be a format string");
}
try {
auto res = std::regex_match(source.str(), std::regex(reg_str.str()));
return res? one:zero;
} catch(const std::regex_error& e) {
return nas_err("regex::match", e.what());
}
return zero;
}
var builtin_regex_search(context* ctx, gc* ngc) {
auto source = ctx->localr[1];
auto reg_str = ctx->localr[2];
if (!source.is_str()) {
return nas_err("regex::search", "\"src\" must be a string");
}
if (!reg_str.is_str()) {
return nas_err("regex::search", "\"reg\" must be a format string");
}
try {
auto res = std::regex_search(source.str(), std::regex(reg_str.str()));
return res? one:zero;
} catch(const std::regex_error& e) {
return nas_err("regex::search", e.what());
}
return nil;
}
var builtin_regex_replace(context* ctx, gc* ngc) {
auto source = ctx->localr[1];
auto reg_str = ctx->localr[2];
auto fmt = ctx->localr[3];
if (!source.is_str()) {
return nas_err("regex::replace", "\"src\" must be a string");
}
if (!reg_str.is_str()) {
return nas_err("regex::replace", "\"reg\" must be a format string");
}
if (!fmt.is_str()) {
return nas_err("regex::replace", "\"fmt\" must be a format string");
}
try {
auto res = std::regex_replace(
source.str(),
std::regex(reg_str.str()),
fmt.str()
);
return ngc->newstr(res);
} catch(const std::regex_error& e) {
return nas_err("regex::replace", e.what());
}
return ngc->newstr(source.str());
}
var builtin_regex_match_all(context* ctx, gc* ngc) {
auto source = ctx->localr[1];
auto reg_str = ctx->localr[2];
if (!source.is_str()) {
return nas_err("regex::match_all", "\"src\" must be a string");
}
if (!reg_str.is_str()) {
return nas_err("regex::match_all", "\"reg\" must be a format string");
}
auto res = ngc->temp = ngc->alloc(vm_type::vm_vec);
try {
const auto& src = source.str();
auto words_regex = std::regex(reg_str.str());
auto begin = std::sregex_iterator(src.begin(), src.end(), words_regex);
auto end = std::sregex_iterator();
for (auto i = begin; i!=end; ++i) {
res.vec().elems.push_back(ngc->newstr((*i).str()));
}
} catch(const std::regex_error& e) {
return nas_err("regex::match_all", e.what());
}
return res;
}
nasal_builtin_table regex_lib_native[] = {
{"__regex_match", builtin_regex_match},
{"__regex_search", builtin_regex_search},
{"__regex_replace", builtin_regex_replace},
{"__regex_match_all", builtin_regex_match_all},
{nullptr, nullptr}
};
}

18
src/natives/regex_lib.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <regex>
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
namespace nasal {
var builtin_regex_match(context*, gc*);
var builtin_regex_search(context*, gc*);
var builtin_regex_replace(context*, gc*);
var builtin_regex_match_all(context*, gc*);
extern nasal_builtin_table regex_lib_native[];
}

155
src/natives/unix_lib.cpp Normal file
View File

@@ -0,0 +1,155 @@
#include "natives/unix_lib.h"
namespace nasal {
const auto dir_type_name = "dir";
void dir_entry_destructor(void* ptr) {
#ifndef _MSC_VER
closedir(static_cast<DIR*>(ptr));
#else
FindClose(ptr);
#endif
}
var builtin_pipe(context* ctx, gc* ngc) {
#ifndef _WIN32
i32 fd[2];
var res = ngc->alloc(vm_type::vm_vec);
if (pipe(fd)==-1) {
return nas_err("unix::pipe", "failed to create pipe");
}
res.vec().elems.push_back(var::num(static_cast<f64>(fd[0])));
res.vec().elems.push_back(var::num(static_cast<f64>(fd[1])));
return res;
#endif
return nas_err("unix::pipe", "not supported on windows");
}
var builtin_fork(context* ctx, gc* ngc) {
#ifndef _WIN32
f64 res = fork();
if (res<0) {
return nas_err("unix::fork", "failed to fork a process");
}
return var::num(static_cast<f64>(res));
#endif
return nas_err("unix::fork", "not supported on windows");
}
var builtin_waitpid(context* ctx, gc* ngc) {
auto pid = ctx->localr[1];
auto nohang = ctx->localr[2];
if (!pid.is_num() || !nohang.is_num()) {
return nas_err("unix::waitpid", "pid and nohang must be number");
}
#ifndef _WIN32
i32 ret_pid, status;
ret_pid = waitpid(pid.num(), &status, nohang.num()==0? 0:WNOHANG);
var vec = ngc->alloc(vm_type::vm_vec);
vec.vec().elems.push_back(var::num(static_cast<f64>(ret_pid)));
vec.vec().elems.push_back(var::num(static_cast<f64>(status)));
return vec;
#endif
return nas_err("unix::waitpid", "not supported on windows");
}
var builtin_opendir(context* ctx, gc* ngc) {
auto path = ctx->localr[1];
if (!path.is_str()) {
return nas_err("unix::opendir", "\"path\" must be string");
}
#ifdef _MSC_VER
WIN32_FIND_DATAA data;
HANDLE p;
p = FindFirstFileA((path.str()+"\\*.*").c_str(), &data);
if (p==INVALID_HANDLE_VALUE) {
return nas_err("unix::opendir", "cannot open dir <"+path.str()+">");
}
#else
DIR* p = opendir(path.str().c_str());
if (!p) {
return nas_err("unix::opendir", "cannot open dir <"+path.str()+">");
}
#endif
var ret = ngc->alloc(vm_type::vm_ghost);
ret.ghost().set(dir_type_name, dir_entry_destructor, nullptr, p);
return ret;
}
var builtin_readdir(context* ctx, gc* ngc) {
auto handle = ctx->localr[1];
if (!handle.object_check(dir_type_name)) {
return nas_err("unix::readdir", "not a valid dir handle");
}
#ifdef _MSC_VER
WIN32_FIND_DATAA data;
if (!FindNextFileA(handle.ghost().pointer, &data)) {
return nil;
}
return ngc->newstr(data.cFileName);
#else
dirent* p = readdir(static_cast<DIR*>(handle.ghost().pointer));
return p? ngc->newstr(p->d_name):nil;
#endif
}
var builtin_closedir(context* ctx, gc* ngc) {
auto handle = ctx->localr[1];
if (!handle.object_check(dir_type_name)) {
return nas_err("unix::closedir", "not a valid dir handle");
}
handle.ghost().clear();
return nil;
}
var builtin_chdir(context* ctx, gc* ngc) {
auto path = ctx->localr[1];
if (!path.is_str()) {
return var::num(-1.0);
}
return var::num(static_cast<f64>(chdir(path.str().c_str())));
}
var builtin_environ(context* ctx, gc* ngc) {
var res = ngc->temp = ngc->alloc(vm_type::vm_vec);
auto& vec = res.vec().elems;
for(char** env = environ; *env; ++env) {
vec.push_back(ngc->newstr(*env));
}
ngc->temp = nil;
return res;
}
var builtin_getcwd(context* ctx, gc* ngc) {
char buf[1024];
if (!getcwd(buf, sizeof(buf))) {
return nil;
}
return ngc->newstr(buf);
}
var builtin_getenv(context* ctx, gc* ngc) {
auto envvar = ctx->localr[1];
if (!envvar.is_str()) {
return nas_err("unix::getenv", "\"envvar\" must be string");
}
char* res = getenv(envvar.str().c_str());
return res? ngc->newstr(res):nil;
}
nasal_builtin_table unix_lib_native[] = {
{"__pipe", builtin_pipe},
{"__fork", builtin_fork},
{"__waitpid", builtin_waitpid},
{"__opendir", builtin_opendir},
{"__readdir", builtin_readdir},
{"__closedir", builtin_closedir},
{"__chdir", builtin_chdir},
{"__environ", builtin_environ},
{"__getcwd", builtin_getcwd},
{"__getenv", builtin_getenv},
{nullptr, nullptr}
};
}

40
src/natives/unix_lib.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "natives/builtin.h"
#ifndef _MSC_VER
#include <unistd.h>
#include <dirent.h>
#else
#define _CRT_SECURE_NO_DEPRECATE 1
#define _CRT_NONSTDC_NO_DEPRECATE 1
#include <io.h>
#include <direct.h>
#endif
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/wait.h>
#endif
namespace nasal {
void dir_entry_destructor(void*);
var builtin_pipe(context*, gc*);
var builtin_fork(context*, gc*);
var builtin_waitpid(context*, gc*);
var builtin_opendir(context*, gc*);
var builtin_readdir(context*, gc*);
var builtin_closedir(context*, gc*);
var builtin_chdir(context*, gc*);
var builtin_environ(context*, gc*);
var builtin_getcwd(context*, gc*);
var builtin_getenv(context*, gc*);
extern nasal_builtin_table unix_lib_native[];
}

155
src/optimizer.cpp Normal file
View File

@@ -0,0 +1,155 @@
#include "optimizer.h"
namespace nasal {
void optimizer::const_string(
binary_operator* node,
string_literal* left_node,
string_literal* right_node) {
if (node->get_operator_type()!=binary_operator::kind::concat) {
return;
}
const auto& left = left_node->get_content();
const auto& right = right_node->get_content();
node->set_optimized_string(
new string_literal(node->get_location(), left+right)
);
}
void optimizer::const_number(
binary_operator* node,
number_literal* left_node,
number_literal* right_node) {
const auto left = left_node->get_number();
const auto right = right_node->get_number();
f64 res;
switch(node->get_operator_type()) {
case binary_operator::kind::add: res = left+right; break;
case binary_operator::kind::sub: res = left-right; break;
case binary_operator::kind::mult: res = left*right; break;
case binary_operator::kind::div: res = left/right; break;
case binary_operator::kind::less: res = left<right; break;
case binary_operator::kind::leq: res = left<=right; break;
case binary_operator::kind::grt: res = left>right; break;
case binary_operator::kind::geq: res = left>=right; break;
case binary_operator::kind::bitwise_or:
res = static_cast<i32>(left)|static_cast<i32>(right); break;
case binary_operator::kind::bitwise_xor:
res = static_cast<i32>(left)^static_cast<i32>(right); break;
case binary_operator::kind::bitwise_and:
res = static_cast<i32>(left)&static_cast<i32>(right); break;
default: return;
}
if (std::isinf(res) || std::isnan(res)) {
return;
}
node->set_optimized_number(
new number_literal(node->get_location(), res)
);
}
void optimizer::const_number(
unary_operator* node,
number_literal* value_node) {
auto res = value_node->get_number();
switch(node->get_operator_type()) {
case unary_operator::kind::negative:
res = -res; break;
case unary_operator::kind::bitwise_not:
res = ~static_cast<i32>(res); break;
case unary_operator::kind::logical_not:
res = !res; break;
}
if (std::isinf(res) || std::isnan(res)) {
return;
}
node->set_optimized_number(
new number_literal(node->get_location(), res)
);
}
bool optimizer::visit_binary_operator(binary_operator* node) {
auto left_node = node->get_left();
auto right_node = node->get_right();
left_node->accept(this);
right_node->accept(this);
number_literal* left_num_node = nullptr;
number_literal* right_num_node = nullptr;
string_literal* left_str_node = nullptr;
string_literal* right_str_node = nullptr;
if (left_node->get_type()==expr_type::ast_num) {
left_num_node = reinterpret_cast<number_literal*>(left_node);
} else if (left_node->get_type()==expr_type::ast_binary &&
reinterpret_cast<binary_operator*>(left_node)->get_optimized_number()) {
auto optimized = reinterpret_cast<binary_operator*>(left_node);
left_num_node = optimized->get_optimized_number();
} else if (left_node->get_type()==expr_type::ast_unary &&
reinterpret_cast<unary_operator*>(left_node)->get_optimized_number()) {
auto optimized = reinterpret_cast<unary_operator*>(left_node);
left_num_node = optimized->get_optimized_number();
}
if (right_node->get_type()==expr_type::ast_num) {
right_num_node = reinterpret_cast<number_literal*>(right_node);
} else if (right_node->get_type()==expr_type::ast_binary &&
reinterpret_cast<binary_operator*>(right_node)->get_optimized_number()) {
auto optimized = reinterpret_cast<binary_operator*>(right_node);
right_num_node = optimized->get_optimized_number();
} else if (right_node->get_type()==expr_type::ast_unary &&
reinterpret_cast<unary_operator*>(right_node)->get_optimized_number()) {
auto optimized = reinterpret_cast<unary_operator*>(right_node);
right_num_node = optimized->get_optimized_number();
}
if (left_node->get_type()==expr_type::ast_str) {
left_str_node = reinterpret_cast<string_literal*>(left_node);
} else if (left_node->get_type()==expr_type::ast_binary &&
reinterpret_cast<binary_operator*>(left_node)->get_optimized_string()) {
auto optimized = reinterpret_cast<binary_operator*>(left_node);
left_str_node = optimized->get_optimized_string();
}
if (right_node->get_type()==expr_type::ast_str) {
right_str_node = reinterpret_cast<string_literal*>(right_node);
} else if (right_node->get_type()==expr_type::ast_binary &&
reinterpret_cast<binary_operator*>(right_node)->get_optimized_string()) {
auto optimized = reinterpret_cast<binary_operator*>(right_node);
right_str_node = optimized->get_optimized_string();
}
if (left_num_node && right_num_node) {
const_number(node, left_num_node, right_num_node);
return true;
}
if (left_str_node && right_str_node) {
const_string(node, left_str_node, right_str_node);
return true;
}
return true;
}
bool optimizer::visit_unary_operator(unary_operator* node) {
auto value = node->get_value();
value->accept(this);
number_literal* num_node = nullptr;
if (value->get_type()==expr_type::ast_num) {
num_node = reinterpret_cast<number_literal*>(value);
} else if (value->get_type()==expr_type::ast_binary &&
reinterpret_cast<binary_operator*>(value)->get_optimized_number()) {
auto optimized = reinterpret_cast<binary_operator*>(value);
num_node = optimized->get_optimized_number();
} else if (value->get_type()==expr_type::ast_unary &&
reinterpret_cast<unary_operator*>(value)->get_optimized_number()) {
auto optimized = reinterpret_cast<unary_operator*>(value);
num_node = optimized->get_optimized_number();
}
if (num_node) {
const_number(node, num_node);
}
return true;
}
void optimizer::do_optimization(code_block* root) {
root->accept(this);
}
}

25
src/optimizer.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include <cmath>
#include "nasal_ast.h"
#include "ast_visitor.h"
namespace nasal {
// experimental optimizer for constant calculation
class optimizer: public ast_visitor {
private:
void const_string(binary_operator*, string_literal*, string_literal*);
void const_number(binary_operator*, number_literal*, number_literal*);
void const_number(unary_operator*, number_literal*);
public:
bool visit_binary_operator(binary_operator*) override;
bool visit_unary_operator(unary_operator*) override;
public:
void do_optimization(code_block*);
};
}

Some files were not shown because too many files have changed in this diff Show More