304 Commits

Author SHA1 Message Date
ValK
c2d82d4806 Merge pull request #78 from ValKmjolnir/develop
🐛 fix #77
2025-10-22 00:10:56 +08:00
ValKmjolnir
87b3ec06a2 🎨 support return statement outside function block 2025-10-22 00:08:13 +08:00
ValKmjolnir
cfb6f92e3f 📝 update README 2025-10-10 20:45:42 +08:00
ValK
0310faba3c Merge pull request #75 from ValKmjolnir/develop
🚀 enable windows nightly build
2025-10-10 20:42:48 +08:00
ValKmjolnir
3032070d1a 🎨 enable windows nightly build 2025-10-10 20:40:52 +08:00
ValKmjolnir
12cdd1e9ea 🐛 fix title 2025-10-10 20:33:03 +08:00
ValKmjolnir
3cc47b744c 🎨 add windows nightly build 2025-10-10 20:30:05 +08:00
ValK
0f1e5fca91 Merge pull request #74 from ValKmjolnir/develop
🐛 fix segfault when callsite is empty
2025-09-26 23:19:08 +08:00
ValKmjolnir
8ccb5c36b8 🐛 fix segfault when callsite is empty 2025-09-26 22:37:38 +08:00
ValK
6dc102a5dd Merge pull request #73 from ValKmjolnir/develop
🐛 fix segfault
2025-08-15 19:13:42 +08:00
ValKmjolnir
4a03179457 🐛 fix segfault 2025-08-15 19:11:25 +08:00
ValK
9f1d50bfe6 Merge pull request #72 from ValKmjolnir/develop
🐛 fix #69
2025-07-31 20:24:40 +08:00
ValKmjolnir
6b975b4a9c 🎨 improve dump 2025-07-31 20:21:27 +08:00
ValKmjolnir
6034234c87 🐛 fix call trace issue #69 2025-07-30 20:32:25 +08:00
ValK
c08befd0c0 Merge pull request #71 from ValKmjolnir/develop
🐛 fix memory leak
2025-06-05 20:12:20 +08:00
ValKmjolnir
e35410c6de 🐛 fix memory leak 2025-06-05 20:09:37 +08:00
ValK
9f4e33f23d Merge pull request #70 from ValKmjolnir/develop
🎨 improve format
2025-06-02 13:46:04 +08:00
ValKmjolnir
af3d93ab64 🎨 format 2025-06-02 13:43:00 +08:00
ValKmjolnir
2cc5bb8625 🎨 format 2025-06-02 13:27:04 +08:00
ValKmjolnir
240095ab37 📝 loadg also can show variable name 2025-03-17 20:31:48 +08:00
ValK
12afc5422d Merge pull request #68 from ValKmjolnir/develop
 use incremental garbage collector
2025-03-17 01:41:17 +08:00
ValKmjolnir
906f337f1a 🔥 fuck mac 2025-03-16 02:21:47 +08:00
ValKmjolnir
e724aa6ef2 📝 fuck mac 2025-03-16 02:19:17 +08:00
ValKmjolnir
dd9d5baf22 🎨 mcallg also 2025-03-15 18:39:19 +08:00
ValKmjolnir
b5f02de883 🎨 opcode dump can get global variable name 2025-03-15 18:17:35 +08:00
ValKmjolnir
10d2965197 🎨 improve codes 2025-03-15 17:58:13 +08:00
ValKmjolnir
ac8e5c6361 optimize incremental gc 2025-03-15 17:36:07 +08:00
ValKmjolnir
a83978c553 📝 add comments 2025-03-15 02:14:44 +08:00
ValKmjolnir
a85a4e37c1 replace concurrent sweep with incremental sweep 2025-03-15 01:12:23 +08:00
ValKmjolnir
bde152b6e9 optimize 2025-03-13 20:28:21 +08:00
ValKmjolnir
705df8dc1d add concurrent sweep 2025-03-13 00:10:31 +08:00
ValKmjolnir
eeb126ab65 🐛 fix global/local debug index 2025-03-12 21:37:29 +08:00
ValKmjolnir
a7a2a0a369 📝 fix typo 2025-03-08 01:45:54 +08:00
ValKmjolnir
065f6ae162 🎨 open return nil if fails 2025-02-15 14:24:08 +08:00
ValK
9ea97c5608 Merge pull request #67 from ValKmjolnir/develop
🐛 if io.stat fails, return nil
2025-02-12 19:27:24 +08:00
ValKmjolnir
b177a58662 🐛 if stat fails, return nil 2025-02-12 19:13:30 +08:00
ValK
a25f309063 Merge pull request #66 from sidi762/master
Web: improve error handling
2025-02-11 14:14:19 +08:00
LIANG Sidi
8b8b8aef52 Merge branch 'ValKmjolnir:master' into master 2025-02-11 14:07:31 +08:00
Sidi Liang
998fd08353 Web: Improve error handling 2025-02-11 13:57:52 +08:00
ValK
3fd21a62ef Merge pull request #65 from ValKmjolnir/develop
🐛 exec_check also needs CHECK_INTERRUPT
2025-02-06 00:48:39 +08:00
ValKmjolnir
04e30b9c7f 🎨 add memory usage calc 2025-02-05 19:43:02 +08:00
ValKmjolnir
09cd3027e9 🐛 exec_check also needs CHECK_INTERRUPT 2025-02-05 17:56:57 +08:00
ValK
e405c3e6d4 Merge pull request #64 from sidi762/master
Web: fixed timeout not working
2025-02-05 17:48:52 +08:00
Sidi Liang
0077d21330 VM: Fixed compiling error in linux 2025-02-05 15:12:42 +08:00
Sidi Liang
b09e5bb8ad VM: Added a comment back that was accidentally removed in the last commit 2025-02-05 15:09:25 +08:00
Sidi Liang
3ff885abe3 VM: fixed an error introduced in the last merging 2025-02-05 14:58:54 +08:00
LIANG Sidi
0c54f216fc Merge branch 'master' into master 2025-02-05 14:49:56 +08:00
Sidi Liang
b449dcf655 Web: force gc in server.js 2025-02-05 14:46:31 +08:00
Sidi Liang
699c5f7af4 Web: Fix timeout 2025-02-05 14:46:07 +08:00
ValK
749f1ada67 Merge pull request #62 from ValKmjolnir/develop
🐛 fix formated output
2025-01-31 15:37:10 +08:00
ValKmjolnir
3752bd8c07 🐛 fix formated output 2025-01-13 22:35:56 +08:00
ValK
8435ac5b63 Merge pull request #61 from ValKmjolnir/develop
🐛 fix package error
2025-01-11 21:32:42 +08:00
ValKmjolnir
644fbd8647 🐛 fix package error 2025-01-11 21:29:55 +08:00
ValK
fc2bb809aa Merge pull request #60 from ValKmjolnir/develop
 create a demo formater
2025-01-11 21:26:00 +08:00
ValKmjolnir
89826da791 📝 adjust package script 2025-01-11 21:20:44 +08:00
ValKmjolnir
c6840da201 📝 adjust CI 2025-01-11 21:18:38 +08:00
ValKmjolnir
247172a9f2 📝 update minor version 2025-01-11 21:11:55 +08:00
ValKmjolnir
277ddb9c0f add demo format executable 2025-01-11 21:06:57 +08:00
ValKmjolnir
c228dcc149 📝 change makefile 2025-01-11 17:51:34 +08:00
ValKmjolnir
dadc0afa35 🐛 fix build failure 2025-01-03 01:07:46 +08:00
ValKmjolnir
77ec4b0d7c 🐛 add dylib find rule on mac 2025-01-03 01:03:13 +08:00
ValKmjolnir
030a3ad920 📝 remove -fPIC if using MSVC 2025-01-01 18:35:57 +08:00
ValKmjolnir
04097341ac 🎨 dump gc time occupation 2025-01-01 18:28:58 +08:00
ValKmjolnir
fd93fbafdf 🎨 formating console3D test 2025-01-01 16:56:11 +08:00
ValKmjolnir
688fbe8c5d 🎨 formating 2025-01-01 16:39:25 +08:00
ValKmjolnir
4dc4c1d2b7 📝 fix typo 2024-12-27 19:21:07 +08:00
ValK
4035c6c98a Merge pull request #59 from ValKmjolnir/develop
📝 fix typo
2024-12-27 01:01:26 +08:00
ValKmjolnir
f6fa78d33e 📝 fix typo 2024-12-27 01:00:36 +08:00
ValK
4062c40bb3 Merge pull request #58 from ValKmjolnir/develop
🎨 format & adjust cmake for windows build
2024-12-27 00:57:39 +08:00
ValKmjolnir
260f0cb5a3 📝 add doc about how to build on windows 2024-12-26 23:19:34 +08:00
ValKmjolnir
985dee8001 📝 format 2024-12-26 21:02:35 +08:00
ValKmjolnir
94d0ce9c2d 📝 format 2024-12-26 00:45:53 +08:00
ValK
9cbefa1003 Merge pull request #57 from ValKmjolnir/develop
🐛 fix boolify result issue
2024-12-11 20:56:23 +08:00
ValKmjolnir
dea19fe3c3 🔥 move NASAL_EXPORT to nasal.h 2024-12-11 20:52:58 +08:00
ValKmjolnir
1b745ad176 🐛 fix boolify result 2024-12-11 20:46:19 +08:00
ValK
96ad435e23 Merge pull request #56 from ValKmjolnir/develop
📝 adjust CI and build files
2024-12-11 20:21:03 +08:00
ValKmjolnir
c784b7b490 📝 adjust build config 2024-12-11 20:17:37 +08:00
ValKmjolnir
fc6f7c60c7 📝 adjust CI name 2024-12-11 20:15:58 +08:00
ValKmjolnir
c9c6b905ea 📝 adjust CI 2024-12-11 20:13:49 +08:00
ValKmjolnir
0d8a60e4e3 Merge branch 'master' into develop 2024-11-09 16:49:26 +08:00
ValK
0e85bfccab Merge pull request #55 from sidi762/master
Added web app demo
2024-11-09 16:48:20 +08:00
LIANG Sidi
ebca28d0aa Delete package.json 2024-11-09 14:27:58 +08:00
Sidi Liang
c8869080c6 [web] Updated README.md 2024-11-09 14:14:45 +08:00
Sidi Liang
8e7074fd25 [web] Attempt to add timeouts for REPL (not working properly, it works in some cases but causes a segfault, and in other cases the timeouts were ignored) 2024-11-09 13:40:45 +08:00
Sidi Liang
afdaac9670 [web] Switched REPL server demo to koffi 2024-11-08 11:50:14 +08:00
Sidi Liang
eae25eeb9a [web] Fixed failures when creating tmp files on linxu 2024-11-07 11:30:54 +08:00
Sidi Liang
d3d204a704 [web] Switch from ffi-napi to koffi 2024-11-06 23:44:22 +08:00
Sidi Liang
44e7077f46 [web] Added execution time limit for we b app 2024-11-05 22:40:01 +08:00
Sidi Liang
e6b88c9dec [web] Added Web REPL server and frontend demo 2024-11-05 01:25:09 +08:00
Sidi Liang
068da2fb41 [web] Support multiline mode in web REPL 2024-11-05 01:24:47 +08:00
Sidi Liang
1ecd0a6912 [REPL] Modified to support web repl 2024-11-05 01:24:18 +08:00
Sidi Liang
8dc06c085c [web] Make the temporary file name actually unique to handle concurrent requests 2024-11-04 20:46:31 +08:00
Sidi Liang
40976bf0c1 [web] Added REPL support to the library 2024-11-04 12:04:31 +08:00
Sidi Liang
6260cc1665 [web] Added REPL support to the library 2024-11-04 12:04:14 +08:00
Sidi Liang
8ecf309791 [web] Added an option to show execution time on the front end, and improved server.js 2024-11-03 17:31:20 +08:00
Sidi Liang
95031f508b [web] Added an option to display execution time 2024-11-03 17:30:26 +08:00
Sidi Liang
037ac9e79f [web] nasal_web.cpp: remove gc in NasalContext, which is not needed 2024-11-03 17:30:02 +08:00
Sidi Liang
83ffcc3087 [web] enable limit_mode for safety, enabled optimizer 2024-11-03 16:31:37 +08:00
Sidi Liang
1c48e08889 [web] Added nodejs web app demo 2024-11-03 16:14:59 +08:00
Sidi Liang
b7176b39ed [web] Added lib for web app 2024-11-03 16:14:30 +08:00
Sidi Liang
255f10dfae Updated gitignore 2024-11-03 16:13:00 +08:00
ValKmjolnir
20504efcf3 📝 update docs 2024-08-26 00:30:27 +08:00
ValK
0c64090547 Merge pull request #54 from ValKmjolnir/develop
🎨 update logo & docs
2024-08-18 01:03:50 +08:00
ValKmjolnir
d4eafae5f2 📝 update docs 2024-08-18 01:01:39 +08:00
ValKmjolnir
fdbc6cf6da 🎨 update logo 2024-08-18 00:04:11 +08:00
ValK
617598ea40 Merge pull request #53 from ValKmjolnir/develop
🎨 add new logo & sync some fixes
2024-08-16 23:18:35 +08:00
ValKmjolnir
55f8485561 🎨 add new svg logo 2024-08-16 23:02:27 +08:00
ValKmjolnir
52dfd52f39 📝 adjust test files 2024-08-03 17:07:32 +08:00
ValKmjolnir
99298b86ab 🎨 add global variable info in global dump 2024-08-03 14:53:41 +08:00
ValKmjolnir
cf2323623b 🎨 fix typo in report info 2024-08-03 14:18:29 +08:00
ValK
efd403c758 Merge pull request #52 from ValKmjolnir/develop
🎨 add several new native functions
2024-08-02 22:45:47 +08:00
ValKmjolnir
21911f21f0 fix clear_screen on _WIN32 2024-08-02 22:41:03 +08:00
ValKmjolnir
8d3f752429 add native terminal_size 2024-08-02 22:34:10 +08:00
ValKmjolnir
d37dfec225 add string.replace 2024-08-02 22:20:30 +08:00
ValKmjolnir
94114416fe add native function for version info 2024-07-08 22:56:33 +08:00
ValKmjolnir
4da38f686f 🎨 improve report format of ghost type 2024-07-08 22:47:30 +08:00
ValK
1c537f0398 Merge pull request #51 from ValKmjolnir/develop
🐛 fix bug in subprocess and dylib
2024-06-20 00:37:15 +08:00
ValKmjolnir
858ffdcb61 🐛 forgot this 2024-06-20 00:31:58 +08:00
ValKmjolnir
bbd4d1907b 🎨 optimize implementation of dlopen 2024-06-20 00:29:14 +08:00
ValKmjolnir
456ed5c782 🐛 fix error return value after call waitpid 2024-06-18 01:00:22 +08:00
ValKmjolnir
35c8afe56b 🔥 watchdog do not exist until edited 2024-06-17 23:52:34 +08:00
ValKmjolnir
9dcb45d786 🐛 fix trace bug in call trace 2024-06-17 21:36:10 +08:00
ValKmjolnir
4d838dc694 add subprocess.active 2024-06-16 22:41:50 +08:00
ValKmjolnir
1de0874c8d add detailed call trace info 2024-06-16 21:57:23 +08:00
ValKmjolnir
abb587c62b 📝 update 2024-06-16 00:37:56 +08:00
ValKmjolnir
f1fb58ead3 🐛 fix render bug in gc info 2024-06-16 00:05:21 +08:00
ValK
78dd535a72 Merge pull request #50 from ValKmjolnir/develop
🐛 fix operand `lnkeq` when splicing vectors
2024-06-15 00:38:51 +08:00
ValKmjolnir
5da80d1b22 🐛 fix lnkeq on 2 vecs 2024-06-15 00:33:16 +08:00
ValKmjolnir
7cc9a9f5c9 🔥 delete useless native functions 2024-06-15 00:16:10 +08:00
ValK
7c175a2883 Merge pull request #49 from ValKmjolnir/develop
👍 add test file for subprocess
2024-06-14 00:41:54 +08:00
ValKmjolnir
671b1ef212 📝 delete useless report info 2024-06-14 00:35:20 +08:00
ValKmjolnir
ce204352c6 add template get/convert for ghost 2024-06-13 23:47:58 +08:00
ValK
0725352349 Merge pull request #48 from ValKmjolnir/develop
👾 finish subprocess module
2024-06-13 01:06:38 +08:00
ValKmjolnir
9752b8823e 🐛 subprocess::terminate return correct value 2024-06-13 01:00:18 +08:00
ValKmjolnir
40ca6c19bf finish subprocess::create/terminate 2024-06-13 00:43:17 +08:00
ValKmjolnir
ef00209018 🐛 fix compilation error on macOS 2024-06-12 22:08:02 +08:00
ValKmjolnir
bf9f0d6338 add subprocess.fork/kill 2024-06-12 01:25:40 +08:00
ValK
0c216e95e8 Merge pull request #47 from ValKmjolnir/develop
👾 try adding new module `subprocess`
2024-06-10 00:42:30 +08:00
ValKmjolnir
2337994b08 🐛 fix compilation error on MSVC 2024-06-10 00:36:52 +08:00
ValKmjolnir
d48c9f44e8 add native functions of subprocess 2024-06-10 00:30:30 +08:00
ValKmjolnir
7e3c6d06b2 add framework for subprocess 2024-06-09 00:21:11 +08:00
ValKmjolnir
1205c6d788 improve vm::value_info 2024-06-08 00:30:20 +08:00
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
229 changed files with 18310 additions and 10785 deletions

View File

@@ -1,65 +1,128 @@
name: C/C++ CI
name: Nightly Build
on:
schedule:
- cron: "0 16 * * *"
push:
branches: [ master,develop ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
mac-build:
mac-aarch64-build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: make
- uses: actions/checkout@v4
- name: Update Tag
run: |
make -j4
cd module
make all -j4
cd ..
make test
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: |
mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release
make -j6
- 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_macOS
# File to release
files: nasal-mac-nightly.tgz
name: macOS nightly build
tag_name: next_macOS
prerelease: true
draft: false
files: |
nasal-macOS-aarch64.tar
linux-x86_64-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: make
- uses: actions/checkout@v4
- name: Update Tag
run: |
make -j4
cd module
make all -j4
cd ..
make test
touch nasal-linux-x86_64-nightly.tgz
tar -czf nasal-linux-x86_64-nightly.tgz --exclude=nasal-linux-x86_64-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_linux_x86_64
git push -f origin next_linux_x86_64
- name: Build
run: |
mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release
make -j6
- 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: Linux Nightly build
# Name of the tag for the release (will be associated with current branch)
automatic_release_tag: next_linux_x86_64
# File to release
files: nasal-linux-x86_64-nightly.tgz
name: linux nightly build
tag_name: next_linux_x86_64
prerelease: true
draft: false
files: |
nasal-linux-x86_64.tar
windows-x86_64-build-msvc:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Fetch Deps
run: |
choco install -y cmake
- name: Update Tag
run: |
git fetch --tags origin
git tag -f next_windows_x86_64_msvc
git push -f origin next_windows_x86_64_msvc
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v2
- name: Build
run: |
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022"
MSBuild.exe nasal.sln /p:Configuration=Release /p:Platform=x64
- name: Package
run: |
python3 tools/msvc_move_file.py
python3 tools/pack.py msvc
- name: Release
uses: softprops/action-gh-release@v2.0.5
with:
name: windows MSVC nightly build
tag_name: next_windows_x86_64_msvc
prerelease: true
draft: false
files: |
nasal-windows-x86_64-msvc.tar
windows-x86_64-build-mingw:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Fetch Deps
run: |
choco install -y cmake
choco install -y mingw
- name: Update Tag
run: |
git fetch --tags origin
git tag -f next_windows_x86_64_mingw
git push -f origin next_windows_x86_64_mingw
- name: Build
run: |
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
mingw32-make.exe -j4
- name: Package
run: |
python3 tools/mingw_move_file.py
python3 tools/pack.py mingw
- name: Release
uses: softprops/action-gh-release@v2.0.5
with:
name: windows MINGW nightly build
tag_name: next_windows_x86_64_mingw
prerelease: true
draft: false
files: |
nasal-windows-x86_64-mingw.tar

31
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Nasal Interpreter Test
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
mac-aarch64:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release
make -j6
- name: Test
run: make test
linux-x86_64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: |
mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release
make -j6
- name: Test
run: make test

79
.gitignore vendored
View File

@@ -1,3 +1,22 @@
# Build directories
build/
out/
dist/
cmake-build-*
cmake-windows-*
# IDE and editor files
.vscode/
.idea/
.vs/
*.swp
*.swo
*~
.DS_Store
.env
.env.local
# C++ specific
# Prerequisites
*.d
@@ -30,32 +49,60 @@
*.exe
*.out
*.app
nasal
nasal-format
nasal.exe
nasal-format.exe
# VS C++ sln
# Visual Studio specific
*.sln
*.vcxproj
*.vcxproj.filters
*.vcxproj.user
.vs
x64
x64/
CMakePresents.json
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# nasal executable
nasal
nasal.exe
# CMake
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
_deps/
# misc
.vscode
dump
# Node.js specific (for the web app)
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json
# Project specific
dump/
fgfs.log
.temp.*
*.ppm
# build dir
build
out
# Logs and databases
*.log
*.sqlite
*.sqlite3
*.db
# macOS special cache directory
# OS generated files
.DS_Store
# ppm picture generated by ppmgen.nas
*.ppm
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10)
project(nasal VERSION 10.1)
project(nasal VERSION 11.3)
message("CMAKE_HOST_SYSTEM_NAME: ${CMAKE_HOST_SYSTEM_NAME}")
@@ -9,48 +9,75 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_RELEASE_INIT "-Wshadow -Wall")
add_compile_options(-fPIC)
# MSVC does not need -fPIC
if (NOT MSVC)
add_compile_options(-fPIC)
endif()
# generate release executables
set(CMAKE_BUILD_TYPE "Release")
# MSVC needs this command option to really enable utf-8 output
if (MSVC)
add_compile_options(/utf-8)
endif()
if (APPLE)
add_compile_options(-mmacosx-version-min=10.15)
endif()
# 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/subprocess.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/gc_stat.cpp
${CMAKE_SOURCE_DIR}/src/util/util.cpp
${CMAKE_SOURCE_DIR}/src/ast_dumper.cpp
${CMAKE_SOURCE_DIR}/src/ast_format.cpp
${CMAKE_SOURCE_DIR}/src/ast_visitor.cpp
${CMAKE_SOURCE_DIR}/src/nasal_ast.cpp
${CMAKE_SOURCE_DIR}/src/nasal_builtin.cpp
${CMAKE_SOURCE_DIR}/src/coroutine.cpp
${CMAKE_SOURCE_DIR}/src/fg_props.cpp
${CMAKE_SOURCE_DIR}/src/bits_lib.cpp
${CMAKE_SOURCE_DIR}/src/io_lib.cpp
${CMAKE_SOURCE_DIR}/src/math_lib.cpp
${CMAKE_SOURCE_DIR}/src/dylib_lib.cpp
${CMAKE_SOURCE_DIR}/src/unix_lib.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_misc.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
${CMAKE_SOURCE_DIR}/src/repl.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)
# build nasal-format
add_executable(nasal-format ${CMAKE_SOURCE_DIR}/src/format.cpp)
target_link_libraries(nasal-format nasal-object)
# link ldl and lpthread
if(NOT CMAKE_HOST_SYSTEM_NAME MATCHES "Windows")
target_link_libraries(nasal dl)
target_link_libraries(nasal pthread)
target_link_libraries(nasal-format pthread)
endif()
target_include_directories(nasal PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_include_directories(nasal-format 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
@@ -58,15 +85,25 @@ if(NOT CMAKE_HOST_SYSTEM_NAME MATCHES "Windows")
${CMAKE_SOURCE_DIR}/build/nasal
${CMAKE_SOURCE_DIR}/nasal
)
add_custom_command(
TARGET nasal-format POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/build/nasal-format
${CMAKE_SOURCE_DIR}/nasal-format
)
endif()
# build module
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/module)
set(MODULE_USED_OBJECT_SOURCE_FILE
${CMAKE_SOURCE_DIR}/src/nasal_misc.cpp
${CMAKE_SOURCE_DIR}/src/util/util.cpp
${CMAKE_SOURCE_DIR}/src/util/gc_stat.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)
@@ -82,4 +119,21 @@ 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)
if (WIN32)
target_link_libraries(nasock ws2_32)
endif()
target_link_libraries(nasock module-used-object)
# Add web library, not for MSVC now
if (NOT MSVC)
add_library(nasal-web SHARED
src/nasal_web.cpp
)
target_include_directories(nasal-web PRIVATE ${CMAKE_SOURCE_DIR}/src)
target_link_libraries(nasal-web nasal-object)
set_target_properties(nasal-web PROPERTIES
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
endif()

1124
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)
@@ -22,6 +24,7 @@
* [__Release Notes__](#release-notes)
* [v8.0](#version-80-release)
* [v11.0](#version-110-release)
* [v11.1](#version-111-release)
## __Parser__
@@ -37,15 +40,15 @@ These two expressions have the same first set,so `LL(1)` is useless for this lan
Problems mentioned above have been solved for a long time, but recently i found a new problem here:
```javascript
var f=func(x,y,z){return x+y+z}
(a,b,c)=(0,1,2);
var f = func(x, y, z) { return x + y + z }
(a, b, c) = (0, 1, 2);
```
This will be recognized as this:
```javascript
var f=func(x,y,z){return x+y+z}(a,b,c)
=(0,1,2);
var f = func(x, y, z) { return x + y + z }(a, b, c)
= (0, 1, 2);
```
and causes fatal syntax error.
@@ -55,9 +58,9 @@ I think this is a serious design fault.
To avoid this syntax error, change program like this, just add a semicolon:
```javascript
var f=func(x,y,z){return x+y+z};
^ here
(a,b,c)=(0,1,2);
var f = func(x, y, z) { return x + y + z };
^ here
(a, b, c) = (0, 1, 2);
```
### version 1.0 parser (last update 2019/10/14)
@@ -121,7 +124,7 @@ Hope you could help me! :)
There's an example of byte code below:
```javascript
for(var i=0;i<4000000;i+=1);
for (var i=0;i<4000000;i+=1);
```
```x86asm
@@ -172,7 +175,7 @@ In this update i changed global and local scope from `unordered_map` to `vector`
So the bytecode generator changed a lot.
```javascript
for(var i=0;i<4000000;i+=1);
for (var i=0;i<4000000;i+=1);
```
```x86asm
@@ -219,8 +222,8 @@ Better use `callfv` instead of `callfh`,
making this process slow.
```javascript
var f=func(x,y){return x+y;}
f(1024,2048);
var f = func(x, y) { return x + y; }
f(1024, 2048);
```
```x86asm
@@ -527,12 +530,12 @@ func <0x2a3>:
Now we add coroutine in this runtime:
```javascript
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; }
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; }
};
```
@@ -697,3 +700,11 @@ This bug is fixed in `v9.0`. So we suggest that do not use `v8.0`.
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)
## __目录__
@@ -22,6 +24,7 @@
* [__发行日志__](#发行日志)
* [v8.0](#version-80-release)
* [v11.0](#version-110-release)
* [v11.1](#version-111-release)
## __语法分析__
@@ -37,23 +40,23 @@
上面这个问题已经解决很久了,不过我最近发现了一个新的语法问题:
```javascript
var f=func(x,y,z){return x+y+z}
(a,b,c)=(0,1,2);
var f = func(x, y, z) { return x + y + z }
(a, b, c) = (0, 1, 2);
```
这种写法会被错误识别合并成下面这种:
```javascript
var f=func(x,y,z){return x+y+z}(a,b,c)
=(0,1,2);
var f = func(x, y, z) { return x + y + z }(a, b, c)
= (0, 1, 2);
```
语法分析器会认为这是个严重的语法错误。我在Flightgear中也测试了这个代码它内置的语法分析器也认为这是错误语法。当然我认为这是语法设计中的一个比较严重的缺漏。为了避免这个语法问题只需要添加一个分号就可以了:
```javascript
var f=func(x,y,z){return x+y+z};
^ 就是这里
(a,b,c)=(0,1,2);
var f = func(x, y, z) { return x + y + z };
^ 就是这里
(a, b, c) = (0, 1, 2);
```
### version 1.0 parser (last update 2019/10/14)
@@ -109,7 +112,7 @@ __该项目于2019/7/25正式开始__。
下面是生成的字节码的样例:
```javascript
for(var i=0;i<4000000;i+=1);
for (var i=0;i<4000000;i+=1);
```
```x86asm
@@ -158,7 +161,7 @@ for(var i=0;i<4000000;i+=1);
在这次的更新中,我把全局变量和局部变量的存储结构从`unordered_map`变为了`vector`,从而提升执行效率。所以现在生成的字节码大变样了。
```javascript
for(var i=0;i<4000000;i+=1);
for (var i=0;i<4000000;i+=1);
```
```x86asm
@@ -190,15 +193,15 @@ for(var i=0;i<4000000;i+=1);
2021/6/3 update:
修复了垃圾收集器还是他妈的会重复收集的bug这次我设计了三个标记状态来保证垃圾是被正确收集了。
修复了垃圾收集器还是会重复收集的bug这次我设计了三个标记状态来保证垃圾是被正确收集了。
`callf`指令拆分为`callfv``callfh`。并且`callfv`将直接从`val_stack`获取传参,而不是先通过一个`vm_vec`把参数收集起来再传入,后者是非常低效的做法。
建议更多使用`callfv`而不是`callfh`,因为`callfh`只能从栈上获取参数并整合为`vm_hash`之后才能传给该指令进行处理,拖慢执行速度。
```javascript
var f=func(x,y){return x+y;}
f(1024,2048);
var f = func(x, y) { return x + y; }
f(1024, 2048);
```
```x86asm
@@ -472,12 +475,12 @@ func <0x2a3>:
在这个版本中我们给nasal加入了协程:
```javascript
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; }
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; }
};
```
@@ -630,3 +633,11 @@ in __`nasal_dbg.h:215`__: `auto canary=gc.stack+STACK_MAX_DEPTH-1;`
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`。

View File

@@ -44,6 +44,7 @@ In `std/example_module.nas`:
```nasal
var a = 1;
var _a = 1;
```
We analysed this file and generated the ast.
@@ -54,11 +55,39 @@ 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,10 +33,11 @@
</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/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>
@@ -79,13 +80,14 @@
在8.0版本中这个解释器需要跑超过2200秒来绘制这张图。
</p>
<p>
The figure below is the feigenbaum-figure generated by ppm script written in nasal.
The figure below is the feigenbaum-figure and burningship-figure generated by ppm script written in nasal.
</p>
<p>
下方是使用 nasal 的 ppm 生成脚本生成的 feigenbaum 图形。
下方是使用 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>
@@ -126,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>

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/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

19
doc/svg/nasal.svg Normal file
View File

@@ -0,0 +1,19 @@
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" fill="#9eb0d8"/>
<g transform="translate(6,0)">
<g fill="#1d2c4e">
<!-- left side -->
<path d="M 10 10 L 10 80 L 20 90 L 25 90 L 25 20 L 15 10 Z"/>
<path d="M 10 10 L 84 84 L 84 90 L 70 90 L 10 30 Z"/>
<!-- right-up corner -->
<path d="M 30 10 L 70 50 L 70 55 L 30 15 Z" />
<path d="M 30 10 L 43 10 L 70 37 L 70 55 Z" />
<path d="M 68 10 L 68 55 L 83 55 L 83 18 L 75 10 Z" />
<path d="M 70 50 L 70 55 L 65 55 L 55 45 L 55 35 Z"/>
</g>
<g fill="#97363a">
<!-- right-down corner -->
<path d="M 83 60 L 83 75 L 68 60 Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@@ -0,0 +1,19 @@
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<!-- <rect width="100" height="100" fill="#9eb0d8"/> -->
<g transform="translate(6,0)">
<g fill="#6e93e9">
<!-- left side -->
<path d="M 10 10 L 10 80 L 20 90 L 25 90 L 25 20 L 15 10 Z"/>
<path d="M 10 10 L 84 84 L 84 90 L 70 90 L 10 30 Z"/>
<!-- right-up corner -->
<path d="M 30 10 L 70 50 L 70 55 L 30 15 Z" />
<path d="M 30 10 L 43 10 L 70 37 L 70 55 Z" />
<path d="M 68 10 L 68 55 L 83 55 L 83 18 L 75 10 Z" />
<path d="M 70 50 L 70 55 L 65 55 L 55 45 L 55 35 Z"/>
</g>
<g fill="#97363a">
<!-- right-down corner -->
<path d="M 83 60 L 83 75 L 68 60 Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 764 B

770
doc/tutorial.md Normal file
View File

@@ -0,0 +1,770 @@
# <img src="./svg/nasal_transparent.svg" height="50px"/> __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 or vectors.
```javascript
1+2-(1+3)*(2+4)/(16-9);
"str1"~"str2";
[0]~[1]; # should be [0, 1]
```
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;
a = [0];
a ~= [1]; # should be [0, 1]
```
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, wrap the `__print` up 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 wrap built-in function up 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:
```javascript
use std.dylib;
var dlhandle = dylib.dlopen("libfib");
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");
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.

748
doc/tutorial_zh.md Normal file
View File

@@ -0,0 +1,748 @@
# <img src="./svg/nasal_transparent.svg" height="50px"/> __教程__
![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";
[0]~[1]; # should be [0, 1]
```
对于条件语句,可以使用`==` `!=` `<` `>` `<=` `>=`来比较数据。`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;
a = [0];
a ~= [1]; # should be [0, 1]
```
`??` 运算符用于检查左侧值是否为 `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代码来运行这个斐波那契函数了:
```javascript
use std.dylib;
var dlhandle = dylib.dlopen("libfib");
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");
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&);`
参数是自定义类型的类型名。

27
doc/windows-build.md Normal file
View File

@@ -0,0 +1,27 @@
# Build Nasal-Interpreter on Windows
## MSVC / Visual Studio
Need CMake and Visual Studio 2022. Remember to add MSBuild.exe to Path.
Valid on powershell:
```sh
mkdir cmake-windows-msvc
cd cmake-windows-msvc
cmake .. -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022"
MSBuild.exe nasal.sln /p:Configuration=Release /p:Platform=x64
```
## MingW-W64
Need CMake and MingW-W64. Remember to add MingW-W64 bin to Path.
Valid on powershell:
```sh
mkdir cmake-windows-mingw
cd cmake-windows-mingw
cmake .. -DCMAKE_BUILD_TYPE=Release -G "MinGW Makefiles" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
mingw32-make.exe -j6
```

180
makefile
View File

@@ -1,17 +1,19 @@
STD = c++17
OS = $(shell uname)
ifeq ($(OS), Darwin)
CXXFLAGS = -std=$(STD) -c -O3 -fno-exceptions -fPIC -mmacosx-version-min=10.15
else
CXXFLAGS = -std=$(STD) -c -O3 -fno-exceptions -fPIC
endif
CPPFLAGS = -I .
NASAL_HEADER=\
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/nasal_builtin.h\
src/natives/builtin.h\
src/nasal_codegen.h\
src/nasal_dbg.h\
src/nasal_err.h\
@@ -21,19 +23,26 @@ NASAL_HEADER=\
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/fg_props.h\
src/bits_lib.h\
src/io_lib.h\
src/math_lib.h\
src/dylib_lib.h\
src/unix_lib.h\
src/coroutine.h\
src/repl.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/natives/subprocess.h\
src/repl/repl.h\
src/util/fs.h\
src/util/util.h
NASAL_OBJECT=\
NASAL_OBJECT = \
build/nasal_err.o\
build/nasal_ast.o\
build/ast_visitor.o\
@@ -46,18 +55,24 @@ NASAL_OBJECT=\
build/nasal_opcode.o\
build/symbol_finder.o\
build/nasal_codegen.o\
build/nasal_misc.o\
build/nasal_gc.o\
build/nasal_builtin.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/subprocess.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
@@ -78,16 +93,33 @@ build:
build/main.o: $(NASAL_HEADER) src/main.cpp | build
$(CXX) $(CXXFLAGS) src/main.cpp -o build/main.o
build/nasal_misc.o: src/nasal.h src/nasal_misc.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_misc.cpp -o build/nasal_misc.o
build/cli.o: src/cli/cli.h src/cli/cli.cpp | build
$(CXX) $(CXXFLAGS) src/cli/cli.cpp -o build/cli.o
build/repl.o: $(NASAL_HEADER) src/repl.h src/repl.cpp | build
$(CXX) $(CXXFLAGS) src/repl.cpp -o build/repl.o
build/util.o: src/util/util.h src/util/util.cpp | build
$(CXX) $(CXXFLAGS) src/util/util.cpp -o build/util.o
build/nasal_err.o: src/nasal.h src/repl.h src/nasal_err.h src/nasal_err.cpp | build
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_gc.o: src/nasal.h src/nasal_gc.h src/nasal_gc.cpp | build
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: \
@@ -95,12 +127,16 @@ build/nasal_import.o: \
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.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
@@ -111,61 +147,97 @@ build/nasal_ast.o: \
src/nasal_ast.h src/nasal_ast.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_ast.cpp -o build/nasal_ast.o
build/nasal_builtin.o: \
build/builtin.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/nasal_builtin.h src/nasal_builtin.cpp | build
$(CXX) $(CXXFLAGS) src/nasal_builtin.cpp -o build/nasal_builtin.o
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/coroutine.h src/coroutine.cpp | build
$(CXX) $(CXXFLAGS) src/coroutine.cpp -o build/coroutine.o
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/bits_lib.h src/bits_lib.cpp | build
$(CXX) $(CXXFLAGS) src/bits_lib.cpp -o build/bits_lib.o
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/math_lib.h src/math_lib.cpp | build
$(CXX) $(CXXFLAGS) src/math_lib.cpp -o build/math_lib.o
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/io_lib.h src/io_lib.cpp | build
$(CXX) $(CXXFLAGS) src/io_lib.cpp -o build/io_lib.o
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/dylib_lib.h src/dylib_lib.cpp | build
$(CXX) $(CXXFLAGS) src/dylib_lib.cpp -o build/dylib_lib.o
src/util/util.h\
src/util/fs.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/unix_lib.h src/unix_lib.cpp | build
$(CXX) $(CXXFLAGS) src/unix_lib.cpp -o build/unix_lib.o
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/subprocess.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/natives/subprocess.h src/natives/subprocess.cpp | build
$(CXX) $(CXXFLAGS) src/natives/subprocess.cpp -o build/subprocess.o
build/fg_props.o: \
src/nasal.h\
src/nasal_type.h\
src/nasal_gc.h\
src/fg_props.h src/fg_props.cpp | build
$(CXX) $(CXXFLAGS) src/fg_props.cpp -o build/fg_props.o
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/nasal_builtin.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
@@ -174,6 +246,7 @@ build/nasal_parse.o: \
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
@@ -205,6 +278,7 @@ build/ast_dumper.o: \
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
@@ -221,7 +295,9 @@ clean:
@ rm $(NASAL_OBJECT)
.PHONY: test
test:nasal
test:
@ ./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
@@ -254,9 +330,13 @@ test:nasal
@ ./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 -t -d test/replace_test.nas
@ ./nasal -e test/scalar.nas hello world
@ ./nasal -e test/trait.nas
@ ./nasal -t test/subprocess_test.nas
@ ./nasal -t test/trait.nas
@ ./nasal -t -d test/turingmachine.nas
@ ./nasal -d test/wavecollapse.nas
@ ./nasal test/word_collector.nas test/md5compare.nas
@ ./nasal -t -d test/wavecollapse.nas
@ ./nasal -t -d test/wavecity.nas
@ ./nasal -t test/word_collector.nas test/md5compare.nas
@ ./nasal -t -d test/ycombinator.nas

View File

@@ -2,6 +2,8 @@
#include <iostream>
#include "../src/nasal.h"
#include "../src/nasal_type.h"
#include "../src/nasal_gc.h"
namespace nasal {
namespace fib_module {
@@ -18,19 +20,19 @@ var fib(var* args, usize size, gc* ngc) {
return nas_err("fib", "lack arguments");
}
var num = args[0];
return var::num(fibonaci(num.tonum()));
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");
return nas_err("quick_fib", "lack arguments");
}
double num = args[0].tonum();
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) {
for (double i = 1; i<num; ++i) {
res = a+b;
a = b;
b = res;
@@ -40,41 +42,74 @@ var quick_fib(var* args, usize size, gc* ngc) {
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<u32*>(ptr);
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_obj);
res.obj().set(ghost_for_test, ghost_for_test_destructor, new u32);
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.objchk(ghost_for_test)) {
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();
*((u32*)res.obj().ptr) = static_cast<u32>(num);
std::cout << "set_new_ghost: successfully set ghost = " << num << "\n";
res.ghost().get<ghost_obj>()->number = static_cast<u32>(num);
std::cout << "set_new_ghost: successfully set ghost.number = " << num << "\n";
res.ghost().get<ghost_obj>()->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.objchk(ghost_for_test)) {
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.obj() << " result = "
<< *((u32*)res.obj().ptr) << "\n";
std::cout << "print_new_ghost: " << res.ghost() << " number = "
<< res.ghost().get<ghost_obj>()->number
<< " test_string = "
<< res.ghost().get<ghost_obj>()->test_string
<< "\n";
return nil;
}
@@ -89,7 +124,7 @@ module_func_info func_tbl[] = {
}
extern "C" module_func_info* get() {
NASAL_EXPORT module_func_info* get() {
return fib_module::func_tbl;
}

View File

@@ -1,10 +1,16 @@
#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
@@ -28,7 +34,8 @@ public:
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
// 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;
@@ -104,7 +111,7 @@ module_func_info func_tbl[] = {
{nullptr, nullptr}
};
extern "C" module_func_info* get() {
NASAL_EXPORT module_func_info* get() {
return func_tbl;
}

View File

@@ -1,6 +1,7 @@
import.std.dylib;
use std.dylib;
use std.os;
var _dl = dylib.dlopen("libfib."~(os.platform()=="windows"?"dll":"so"));
var _dl = dylib.dlopen("libfib");
var _fib = _dl.fib;
@@ -18,37 +19,40 @@ 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 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,18 +1,19 @@
import.std.dylib;
use std.dylib;
use std.os;
var (
kbhit,
getch,
nonblock
) = func {
var lib = dylib.dlopen("libkey"~(os.platform()=="windows"? ".dll":".so"));
var lib = dylib.dlopen("libkey");
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);}
func() {return call(kb);},
func() {return call(gt);},
func() {return call(nb);}
];
}();

View File

@@ -1,6 +1,7 @@
import.std.dylib;
use std.dylib;
use std.os;
var _dl = dylib.dlopen("libmat."~(os.platform()=="windows"?"dll":"so"));
var _dl = dylib.dlopen("libmat");
var _vec2 = _dl.nas_vec2;

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");
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,117 +0,0 @@
import.std.dylib;
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,
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 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,17 +1,20 @@
.PHONY=clean all winall
.PHONY = clean all winall
dynamic_libs_so = libfib.so libkey.so libnasock.so libmat.so
dynamic_libs_dll = libfib.dll libkey.dll libnasock.dll libmat.dll
used_header = ../src/nasal.h ../src/nasal_gc.h
used_object = ../build/nasal_misc.o ../build/nasal_gc.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
OS = $(shell uname)
ifndef OS
OS = $(shell uname)
endif
ifeq ($(OS), Darwin)
CXXFLAGS = -std=$(STD) -c -O3 -fPIC -mmacosx-version-min=10.15
CXXFLAGS = -std=$(STD) -c -O3 -fPIC -mmacosx-version-min=10.15 -I ../src
else
CXXFLAGS = -std=$(STD) -c -O3 -fPIC
CXXFLAGS = -std=$(STD) -c -O3 -fPIC -I ../src
endif
all: $(dynamic_libs_so)
@@ -26,7 +29,7 @@ libfib.so: fib.cpp $(used_header) $(used_object)
@ rm 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) -std=$(STD) -c -O3 fib.cpp -fPIC -o fib.o -I ../src
@ $(CXX) -shared -o libfib.dll fib.o $(used_object) -static
@ del fib.o
@@ -37,7 +40,7 @@ libkey.so: keyboard.cpp $(used_header) $(used_object)
@ rm keyboard.o
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) -std=$(STD) -c -O3 keyboard.cpp -fPIC -o keyboard.o -static -I ../src
@ $(CXX) -shared -o libkey.dll keyboard.o $(used_object) -static
@ del keyboard.o
@@ -48,7 +51,7 @@ libnasock.so: nasocket.cpp $(used_header) $(used_object)
@ rm nasocket.o
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) -std=$(STD) -c -O3 nasocket.cpp -fPIC -I ../src -o nasocket.o -lwsock32 -static
@ $(CXX) -shared -o libnasock.dll nasocket.o $(used_object) -lwsock32 -static
@ del nasocket.o
@@ -59,7 +62,7 @@ libmat.so: matrix.cpp $(used_header) $(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) -std=$(STD) -c -O3 matrix.cpp -fPIC -I ../src -o matrix.o -static
@ $(CXX) -shared -o libmat.dll matrix.o $(used_object) -static
@ del matrix.o

View File

@@ -1,17 +1,19 @@
#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_vec);
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_vec);
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]);
@@ -19,71 +21,71 @@ var nas_vec3(var* args, usize size, gc* ngc) {
}
var nas_vec2_add(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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].type!=vm_vec)
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=2)
return nil;
var res = ngc->alloc(vm_vec);
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].type!=vm_vec)
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=2)
@@ -91,14 +93,14 @@ var nas_vec2_norm(var* args, usize size, gc* ngc) {
auto x = v0[0].num();
auto y = v0[1].num();
auto t = std::sqrt(x*x+y*y);
var res = ngc->alloc(vm_vec);
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].type!=vm_vec)
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=2)
@@ -109,7 +111,7 @@ var nas_vec2_len(var* args, usize size, gc* ngc) {
}
var nas_vec2_dot(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
@@ -119,13 +121,13 @@ var nas_vec2_dot(var* args, usize size, gc* ngc) {
}
var nas_vec3_add(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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()));
@@ -133,13 +135,13 @@ var nas_vec3_add(var* args, usize size, gc* ngc) {
}
var nas_vec3_sub(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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()));
@@ -147,13 +149,13 @@ var nas_vec3_sub(var* args, usize size, gc* ngc) {
}
var nas_vec3_mult(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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()));
@@ -161,13 +163,13 @@ var nas_vec3_mult(var* args, usize size, gc* ngc) {
}
var nas_vec3_div(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
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_vec);
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()));
@@ -175,12 +177,12 @@ var nas_vec3_div(var* args, usize size, gc* ngc) {
}
var nas_vec3_neg(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec)
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
return nil;
var res = ngc->alloc(vm_vec);
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()));
@@ -188,7 +190,7 @@ var nas_vec3_neg(var* args, usize size, gc* ngc) {
}
var nas_vec3_norm(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec)
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
@@ -197,7 +199,7 @@ var nas_vec3_norm(var* args, usize size, gc* ngc) {
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_vec);
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));
@@ -205,7 +207,7 @@ var nas_vec3_norm(var* args, usize size, gc* ngc) {
}
var nas_vec3_len(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec)
if (!args[0].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
if (v0.size()!=3)
@@ -217,13 +219,13 @@ var nas_vec3_len(var* args, usize size, gc* ngc) {
}
var nas_rotate_x(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec)
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_vec);
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)));
@@ -231,13 +233,13 @@ var nas_rotate_x(var* args, usize size, gc* ngc) {
}
var nas_rotate_y(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec)
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_vec);
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)));
@@ -245,13 +247,13 @@ var nas_rotate_y(var* args, usize size, gc* ngc) {
}
var nas_rotate_z(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec)
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_vec);
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()));
@@ -259,7 +261,7 @@ var nas_rotate_z(var* args, usize size, gc* ngc) {
}
var nas_vec3_dot(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_vec || args[1].type!=vm_vec)
if (!args[0].is_vec() || !args[1].is_vec())
return nil;
auto& v0 = args[0].vec().elems;
auto& v1 = args[1].vec().elems;
@@ -293,7 +295,7 @@ module_func_info func_tbl[] = {
{nullptr, nullptr}
};
extern "C" module_func_info* get() {
NASAL_EXPORT module_func_info* get() {
return func_tbl;
}

View File

@@ -1,10 +1,17 @@
#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")
@@ -19,8 +26,9 @@ public:
WSACleanup();
}
};
// use static object to do WSAStartup and WSACleanup
static WSAmanager win;
#else
#include <netdb.h>
#include <sys/socket.h>
@@ -31,14 +39,14 @@ static WSAmanager win;
namespace nasal {
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)
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].type!=vm_num)
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())));
@@ -48,19 +56,19 @@ var nas_closesocket(var* args, usize size, gc* ngc) {
}
var nas_shutdown(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
if (!args[0].is_num())
return nas_err("shutdown", "\"sd\" must be a number");
if (args[1].type!=vm_num)
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].type!=vm_num)
if (!args[0].is_num())
return nas_err("bind", "\"sd\" muse be a number");
if (args[1].type!=vm_str)
if (!args[1].is_str())
return nas_err("bind", "\"ip\" should be a string including an ip with correct format");
if (args[2].type!=vm_num)
if (!args[2].is_num())
return nas_err("bind", "\"port\" must be a number");
sockaddr_in server;
memset(&server, 0, sizeof(sockaddr_in));
@@ -69,25 +77,25 @@ var nas_bind(var* args, usize size, gc* ngc) {
server.sin_port = htons(args[2].num());
return var::num(static_cast<double>(bind(
args[0].num(),
(sockaddr*)&server,
reinterpret_cast<sockaddr*>(&server),
sizeof(server)
)));
}
var nas_listen(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
if (!args[0].is_num())
return nas_err("listen", "\"sd\" must be a number");
if (args[1].type!=vm_num)
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].type!=vm_num)
if (!args[0].is_num())
return nas_err("connect", "\"sd\" must be a number");
if (args[1].type!=vm_str)
if (!args[1].is_str())
return nas_err("connect", "\"hostname\" must be a string");
if (args[2].type!=vm_num)
if (!args[2].is_num())
return nas_err("connect", "\"port\" must be a number");
sockaddr_in addr;
memset(&addr, 0, sizeof(sockaddr_in));
@@ -97,22 +105,30 @@ var nas_connect(var* args, usize size, gc* ngc) {
memcpy(&addr.sin_addr, entry->h_addr, entry->h_length);
return var::num(static_cast<double>(connect(
args[0].num(),
(sockaddr*)&addr,
reinterpret_cast<sockaddr*>(&addr),
sizeof(sockaddr_in)
)));
}
var nas_accept(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
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(), (sockaddr*)&client, &socklen);
int client_sd = accept(
args[0].num(),
reinterpret_cast<sockaddr*>(&client),
&socklen
);
#else
int client_sd = accept(args[0].num(), (sockaddr*)&client, (socklen_t*)&socklen);
int client_sd = accept(
args[0].num(),
reinterpret_cast<sockaddr*>(&client),
reinterpret_cast<socklen_t*>(&socklen)
);
#endif
var res=ngc->temp = ngc->alloc(vm_hash);
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));
@@ -121,11 +137,11 @@ var nas_accept(var* args, usize size, gc* ngc) {
}
var nas_send(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
if (!args[0].is_num())
return nas_err("send", "\"sd\" must be a number");
if (args[1].type!=vm_str)
if (!args[1].is_str())
return nas_err("send", "\"buff\" must be a string");
if (args[2].type!=vm_num)
if (!args[2].is_num())
return nas_err("send", "\"flags\" muse be a number");
return var::num(static_cast<double>(send(
args[0].num(),
@@ -136,15 +152,15 @@ var nas_send(var* args, usize size, gc* ngc) {
}
var nas_sendto(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
if (!args[0].is_num())
return nas_err("sendto", "\"sd\" must be a number");
if (args[1].type!=vm_str)
if (!args[1].is_str())
return nas_err("sendto", "\"hostname\" must be a string");
if (args[2].type!=vm_num)
if (!args[2].is_num())
return nas_err("sendto", "\"port\" must be a number");
if (args[3].type!=vm_str)
if (!args[3].is_str())
return nas_err("sendto", "\"buff\" must be a string");
if (args[4].type!=vm_num)
if (!args[4].is_num())
return nas_err("sendto", "\"flags\" must be a number");
sockaddr_in addr;
memset(&addr, 0, sizeof(sockaddr_in));
@@ -157,24 +173,24 @@ var nas_sendto(var* args, usize size, gc* ngc) {
args[3].str().c_str(),
args[3].str().length(),
args[4].num(),
(sockaddr*)&addr,
reinterpret_cast<sockaddr*>(&addr),
sizeof(sockaddr_in)
)));
}
var nas_recv(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
if (!args[0].is_num())
return nas_err("recv", "\"sd\" must be a number");
if (args[1].type!=vm_num)
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].type!=vm_num)
if (!args[2].is_num())
return nas_err("recv", "\"flags\" muse be a number");
var res = ngc->temp = ngc->alloc(vm_hash);
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());
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);
@@ -184,17 +200,17 @@ var nas_recv(var* args, usize size, gc* ngc) {
}
var nas_recvfrom(var* args, usize size, gc* ngc) {
if (args[0].type!=vm_num)
if (!args[0].is_num())
return nas_err("recvfrom", "\"sd\" must be a number");
if (args[1].type!=vm_num)
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].type!=vm_num)
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_hash);
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
@@ -203,7 +219,7 @@ var nas_recvfrom(var* args, usize size, gc* ngc) {
buf,
args[1].num(),
args[2].num(),
(sockaddr*)&addr,
reinterpret_cast<sockaddr*>(&addr),
&socklen
);
#else
@@ -212,8 +228,8 @@ var nas_recvfrom(var* args, usize size, gc* ngc) {
buf,
args[1].num(),
args[2].num(),
(sockaddr*)&addr,
(socklen_t*)&socklen
reinterpret_cast<sockaddr*>(&addr),
reinterpret_cast<socklen_t*>(&socklen)
);
#endif
hash["size"] = var::num(static_cast<double>(recvsize));
@@ -221,6 +237,7 @@ var nas_recvfrom(var* args, usize size, gc* ngc) {
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;
}
@@ -245,7 +262,7 @@ module_func_info func_tbl[] = {
{nullptr, nullptr}
};
extern "C" module_func_info* get() {
NASAL_EXPORT module_func_info* get() {
return func_tbl;
}

View File

@@ -0,0 +1,17 @@
{
"name": "nasal-web-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.21.1",
"ffi-napi": "^4.0.3",
"yargs": "^17.7.2"
}
}

View File

@@ -0,0 +1,638 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nasal Interpreter Web Demo</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css" rel="stylesheet">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 20px;
padding: 30px;
background: linear-gradient(145deg, #2c3e50, #3498db);
border-radius: 12px;
color: white;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.logo {
margin-bottom: 2px;
}
.ascii-art {
font-family: 'Monaco', monospace;
color: #ecf0f1;
text-shadow: 0 0 10px rgba(255,255,255,0.3);
margin: 0;
line-height: 1.2;
user-select: none;
}
.header h1 {
font-size: 2.5em;
margin: 0 0 10px 0;
font-weight: 700;
background: linear-gradient(to right, #fff, #ecf0f1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.subtitle {
font-size: 1.2em;
color: #ecf0f1;
margin: 0 0 25px 0;
opacity: 0.9;
}
.credits {
display: flex;
justify-content: center;
gap: 30px;
flex-wrap: wrap;
}
.credit-item {
display: flex;
align-items: center;
gap: 8px;
}
.credit-label {
color: #bdc3c7;
font-size: 0.9em;
}
.credit-link {
text-decoration: none;
padding: 5px 10px;
border-radius: 20px;
background: rgba(255,255,255,0.1);
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 5px;
}
.credit-link:hover {
background: rgba(255,255,255,0.2);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.highlight {
color: #fff;
font-weight: 500;
}
.author {
color: #bdc3c7;
font-size: 0.9em;
}
@media (max-width: 600px) {
.header {
padding: 20px;
}
.header h1 {
font-size: 2em;
}
.credits {
flex-direction: column;
align-items: center;
gap: 15px;
}
}
.editor-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.code-section, .output-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.CodeMirror {
height: 400px;
border: 1px solid #ddd;
border-radius: 4px;
}
.output-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}
.status-success {
background-color: #2ecc71;
}
.status-error {
background-color: #e74c3c;
}
#output {
height: 400px;
background: #282c34;
color: #abb2bf;
padding: 10px;
border-radius: 4px;
border: none;
overflow-y: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
line-height: 1.5;
}
.message {
margin: 4px 0;
padding: 8px;
border-radius: 4px;
background: #2c313a;
}
.error-message {
border-left: 3px solid #e06c75;
}
.success-message {
border-left: 3px solid #98c379;
}
.timestamp {
color: #5c6370;
font-size: 0.9em;
margin-right: 8px;
}
.terminal-output {
margin: 8px 0 0 0;
padding: 8px;
background: #21252b;
border-radius: 3px;
white-space: pre-wrap;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
line-height: 1.5;
}
.terminal-output:empty {
display: none;
}
.message + .message {
margin-top: 8px;
}
.status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
}
.status-success {
background-color: #98c379;
}
.status-error {
background-color: #e06c75;
}
.output-header {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 0 10px;
}
.output-header h2 {
margin: 0;
color: #abb2bf;
}
.controls {
text-align: center;
margin: 20px 0;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
margin-right: 10px;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
}
.examples {
margin-top: 20px;
}
.example-btn {
background-color: #27ae60;
margin: 0 5px;
}
.example-btn:hover {
background-color: #219a52;
}
.terminal-output {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
margin: 0;
padding: 8px;
background: #2c3e50;
color: #ecf0f1;
border-radius: 4px;
}
.timestamp {
font-size: 0.9em;
color: #7f8c8d;
margin-bottom: 4px;
}
.terminal-red-bold {
color: #ff5555;
font-weight: bold;
}
.terminal-cyan-bold {
color: #8be9fd;
font-weight: bold;
}
.terminal-bold {
font-weight: bold;
}
.terminal-caret {
color: #ff5555;
font-weight: bold;
}
.error-message {
background: #2c3e50;
border-left: 4px solid #e74c3c;
padding: 10px;
margin: 5px 0;
color: #ecf0f1;
}
.success-message {
background: #2c3e50;
border-left: 4px solid #2ecc71;
padding: 10px;
margin: 5px 0;
color: #ecf0f1;
}
#output {
background: #34495e;
color: #ecf0f1;
padding: 15px;
border-radius: 4px;
border: none;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
}
.error-message pre, .success-message pre {
margin: 0;
}
/* Error message styling */
.error-type {
color: #e06c75;
font-weight: bold;
}
.error-desc {
color: #abb2bf;
font-weight: bold;
}
.error-arrow {
color: #56b6c2;
font-weight: bold;
}
.error-file {
color: #e06c75;
font-weight: bold;
}
.error-loc {
color: #abb2bf;
}
.error-line-number {
color: #56b6c2;
user-select: none;
}
.error-code {
color: #abb2bf;
margin-left: 8px;
}
.error-pointer-space {
color: #abb2bf;
white-space: pre;
}
.error-pointer {
color: #e06c75;
font-weight: bold;
}
.terminal-output {
margin: 8px 0 0 0;
padding: 8px;
background: #21252b;
border-radius: 3px;
white-space: pre-wrap;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
line-height: 1.5;
}
.timing-checkbox {
display: inline-flex;
align-items: center;
margin-left: 10px;
color: #abb2bf;
}
.timing-checkbox input {
margin-right: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">
<pre class="ascii-art">
__ _
/\ \ \__ _ ___ __ _| |
/ \/ / _` / __|/ _` | |
/ /\ / (_| \__ \ (_| | |
\_\ \/ \__,_|___/\__,_|_|
</pre>
</div>
<h1>Nasal Interpreter Web Demo</h1>
<p class="subtitle">Write and execute Nasal code directly in your browser</p>
<div class="credits">
<div class="credit-item">
<span class="credit-label">Powered by</span>
<a href="https://www.fgprc.org.cn/nasal_interpreter.html" class="credit-link">
<span class="highlight">Nasal Interpreter</span>
<span class="author">by ValKmjolnir</span>
</a>
</div>
<div class="credit-item">
<span class="credit-label">Web App by</span>
<a href="https://sidi762.github.io" class="credit-link">
<span class="highlight">LIANG Sidi</span>
</a>
</div>
</div>
</div>
<div class="editor-container">
<div class="code-section">
<h2>Code Editor</h2>
<textarea id="code">var x = 1 + 2;
println(x);</textarea>
</div>
<div class="output-section">
<div class="output-header">
<h2>Output</h2>
<div id="status"></div>
</div>
<div id="output"></div>
</div>
</div>
<div class="controls">
<button id="runBtn" onclick="runCode()">Run</button>
<button onclick="clearOutput()">Clear Output</button>
<label class="timing-checkbox">
<input type="checkbox" id="showTime"> Show execution time
</label>
</div>
<div class="examples">
<h3>Example Programs:</h3>
<button class="example-btn" onclick="loadExample('basic')">Basic Math</button>
<button class="example-btn" onclick="loadExample('loops')">Loops</button>
<button class="example-btn" onclick="loadExample('functions')">Functions</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/javascript/javascript.min.js"></script>
<script>
// Initialize CodeMirror
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
mode: "javascript", // Using JavaScript mode as it's close enough to Nasal
theme: "monokai",
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 4,
tabSize: 4,
lineWrapping: true
});
// Example programs
const examples = {
basic: `# Basic math operations
var a = 10;
var b = 5;
println("Addition: ", a + b);
println("Subtraction: ", a - b);
println("Multiplication: ", a * b);
println("Division: ", a / b);`,
loops: `# Loop example
var sum = 0;
for (var i = 1; i <= 5; i += 1) {
sum += i;
println("Current sum: ", sum);
}
println("Final sum: ", sum);`,
functions: `# Function example
var factorial = func(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
for (var i = 0; i <= 5; i += 1) {
println("Factorial of ", i, " is ", factorial(i));
}`
};
function loadExample(type) {
editor.setValue(examples[type]);
}
function formatTerminalOutput(text) {
// Remove the output/error message header if present
text = text.replace(/^\[(.*?)\] (Output|Error):\s*\n/, '');
// Convert ANSI color codes to CSS classes
const colorMap = {
'\u001b[91;1m': '<span class="terminal-red-bold">', // bright red bold
'\u001b[36;1m': '<span class="terminal-cyan-bold">', // bright cyan bold
'\u001b[0m': '</span>', // reset
'\u001b[1m': '<span class="terminal-bold">' // bold
};
// Replace ANSI codes with HTML spans
Object.entries(colorMap).forEach(([code, html]) => {
text = text.replace(new RegExp(escapeRegExp(code), 'g'), html);
});
// Preserve whitespace and arrow formatting
text = text.replace(/\^/g, '<span class="terminal-caret">^</span>');
return text;
}
// Utility function to escape special characters in strings for RegExp
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function formatErrorMessage(text) {
// Replace ANSI escape codes with CSS classes
return text
// Remove any existing formatting first
.replace(/\u001b\[\d+(?:;\d+)*m/g, '')
// Format the error line
.replace(/^parse: (.+)$/m, '<span class="error-type">parse:</span> <span class="error-desc">$1</span>')
// Format the file location
.replace(/^\s*--> (.+?)(\d+):(\d+)$/m, '<span class="error-arrow">--></span> <span class="error-file">$1</span><span class="error-loc">$2:$3</span>')
// Format the code line
.replace(/^(\d+ \|)(.*)$/m, '<span class="error-line-number">$1</span><span class="error-code">$2</span>')
// Format the error pointer
.replace(/^(\s*\|)(\s*)(\^+)$/m, '<span class="error-line-number">$1</span><span class="error-pointer-space">$2</span><span class="error-pointer">$3</span>');
}
async function runCode() {
const runBtn = document.getElementById('runBtn');
const output = document.getElementById('output');
const status = document.getElementById('status');
const showTime = document.getElementById('showTime').checked;
try {
runBtn.disabled = true;
const code = editor.getValue();
const response = await fetch('/eval', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
code,
showTime
})
});
const data = await response.json();
const timestamp = new Date().toLocaleTimeString();
if (data.error) {
status.innerHTML = '<span class="status-indicator status-error"></span>';
output.innerHTML += `<div class="message error-message">
<span class="timestamp">[${timestamp}]</span>
<pre class="terminal-output">${formatErrorMessage(data.error)}</pre>
</div>`;
} else {
status.innerHTML = '<span class="status-indicator status-success"></span>';
output.innerHTML += `<div class="message success-message">
<span class="timestamp">[${timestamp}]</span>
<pre class="terminal-output">${data.result.trim()}</pre>
</div>`;
}
output.scrollTop = output.scrollHeight;
} catch (err) {
status.innerHTML = '<span class="status-indicator status-error"></span>';
output.innerHTML += `<div class="message error-message">
<span class="timestamp">[${new Date().toLocaleTimeString()}]</span>
<pre class="terminal-output">Server Error: ${err.message}</pre>
</div>`;
} finally {
runBtn.disabled = false;
}
}
function clearOutput() {
document.getElementById('output').innerHTML = '';
document.getElementById('status').innerHTML = '';
}
// Utility function to escape HTML to prevent XSS
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
</script>
</body>
</html>

View File

@@ -0,0 +1,240 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nasal Interpreter Web REPL</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css" rel="stylesheet">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #2c3e50;
color: #ecf0f1;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.repl-container {
background: #34495e;
padding: 20px;
border-radius: 8px;
height: 600px;
display: flex;
flex-direction: column;
}
.repl-output {
flex-grow: 1;
background: #21252b;
color: #abb2bf;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
overflow-y: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
line-height: 1.5;
white-space: pre-wrap;
}
.repl-input-container {
display: flex;
align-items: flex-start;
}
.repl-prompt {
color: #3498db;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
padding: 5px;
user-select: none;
}
.repl-input {
flex-grow: 1;
background: #21252b;
border: none;
color: #abb2bf;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
padding: 5px;
outline: none;
resize: none;
min-height: 24px;
overflow-y: hidden;
}
.controls {
text-align: center;
margin-top: 20px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
margin-right: 10px;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
}
.examples {
margin-top: 20px;
text-align: center;
}
.example-btn {
background-color: #27ae60;
margin: 0 5px;
}
.example-btn:hover {
background-color: #219a52;
}
.output-line {
margin: 2px 0;
min-height: 1.2em;
}
.input-line {
color: #3498db;
white-space: pre;
}
.error-line {
color: #e74c3c;
white-space: pre;
}
.result-line {
color: #2ecc71;
white-space: pre;
margin-left: 4px; /* Slight indent for results */
}
.system-message {
color: #7f8c8d;
font-style: italic;
}
.help-text {
color: #95a5a6;
white-space: pre;
font-family: monospace;
}
.error-type {
color: #ff5f5f;
font-weight: bold;
}
.error-desc {
color: #abb2bf;
font-weight: bold;
}
.error-arrow, .error-line-number {
color: #56b6c2;
font-weight: bold;
}
.error-file {
color: #ff5f5f;
font-weight: bold;
}
.error-code {
color: #abb2bf;
}
.error-pointer-space {
white-space: pre;
}
.error-pointer {
color: #ff5f5f;
font-weight: bold;
}
.error-red-bold {
color: #e06c75;
font-weight: bold;
}
.error-cyan-bold {
color: #56b6c2;
font-weight: bold;
}
.error-bold {
font-weight: bold;
}
@media (max-width: 768px) {
.repl-container {
height: 400px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Nasal Interpreter Web REPL</h1>
<p>Interactive Read-Eval-Print Loop for Nasal</p>
<p>Powered by ValKmjolnir's <a href="https://www.fgprc.org.cn/nasal_interpreter.html">Nasal Interpreter</a>, Web App by <a href="https://sidi762.github.io">LIANG Sidi</a></p>
</div>
<div class="repl-container">
<div id="repl-output" class="repl-output">
<div class="output-line">Welcome to Nasal Web REPL Demo!</div>
</div>
<div class="repl-input-container">
<span class="repl-prompt">>>></span>
<textarea id="repl-input" class="repl-input" rows="1"
placeholder="Enter Nasal code here..."></textarea>
</div>
</div>
<div class="controls">
<button onclick="clearREPL()">Clear REPL</button>
</div>
<div class="examples">
<h3>Example Commands:</h3>
<button class="example-btn" onclick="insertExample('basic')">Basic Math</button>
<button class="example-btn" onclick="insertExample('loops')">Loops</button>
<button class="example-btn" onclick="insertExample('functions')">Functions</button>
</div>
</div>
<script src="repl.js"></script>
</body>
</html>

View File

@@ -0,0 +1,249 @@
let replSessionId = null;
let multilineInput = [];
let historyIndex = -1;
let commandHistory = [];
let multilineBuffer = [];
let isMultilineMode = false;
// Initialize REPL
async function initRepl() {
try {
const response = await fetch('/repl/init', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const data = await response.json();
if (data.sessionId) {
replSessionId = data.sessionId;
console.log('REPL session initialized:', replSessionId);
// Display version info
appendOutput('Nasal REPL interpreter version ' + data.version);
appendOutput('Type your code below and press Enter to execute.');
appendOutput('Use Shift+Enter for multiline input.\n');
showPrompt();
} else {
throw new Error('Failed to initialize REPL session');
}
} catch (err) {
appendOutput(`Error: ${err.message}`, 'error-line');
}
}
// Format error messages to match command-line REPL
function formatError(error) {
// Split the error message into lines
const lines = error.split('\n');
return lines.map(line => {
// Add appropriate indentation for the error pointer
if (line.includes('-->')) {
return ' ' + line;
} else if (line.includes('^')) {
return ' ' + line;
}
return line;
}).join('\n');
}
// Handle input
const input = document.getElementById('repl-input');
input.addEventListener('keydown', async (e) => {
if (e.key === 'Enter') {
if (e.shiftKey) {
// Shift+Enter: add newline
const pos = input.selectionStart;
const value = input.value;
input.value = value.substring(0, pos) + '\n' + value.substring(pos);
input.selectionStart = input.selectionEnd = pos + 1;
input.style.height = 'auto';
input.style.height = input.scrollHeight + 'px';
e.preventDefault();
return;
}
e.preventDefault();
const code = input.value.trim();
// Skip empty lines but still show prompt
if (!code) {
showPrompt(isMultilineMode ? '... ' : '>>> ');
return;
}
try {
// First check if input is complete
const checkResponse = await fetch('/repl/eval', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: replSessionId,
line: code,
check: true,
buffer: multilineBuffer // Send existing buffer
})
});
const checkData = await checkResponse.json();
if (checkData.needsMore) {
// Add to multiline buffer and show continuation prompt
multilineBuffer.push(code);
isMultilineMode = true;
// Display the input with continuation prompt
appendOutput(code, 'input-line', multilineBuffer.length === 1 ? '>>> ' : '... ');
input.value = '';
showPrompt('... ');
return;
}
// If we were in multiline mode, add the final line
if (isMultilineMode) {
multilineBuffer.push(code);
}
// Get the complete code to evaluate
const fullCode = isMultilineMode ?
multilineBuffer.join('\n') : code;
// Display the input
appendOutput(code, 'input-line', isMultilineMode ? '... ' : '>>> ');
// Evaluate the code
const response = await fetch('/repl/eval', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId: replSessionId,
line: fullCode
})
});
const data = await response.json();
if (data.error) {
appendOutput(formatError(data.error.trim()), 'error-line');
} else if (data.result) {
handleResult(data.result);
}
// Reset multiline state
multilineBuffer = [];
isMultilineMode = false;
input.value = '';
showPrompt('>>> ');
} catch (err) {
appendOutput(`Error: ${err.message}`, 'error-line');
multilineBuffer = [];
isMultilineMode = false;
input.value = '';
showPrompt('>>> ');
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
if (historyIndex > 0) {
historyIndex--;
input.value = commandHistory[historyIndex];
}
} else if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyIndex < commandHistory.length - 1) {
historyIndex++;
input.value = commandHistory[historyIndex];
} else {
historyIndex = commandHistory.length;
input.value = '';
}
}
});
// Auto-resize input
input.addEventListener('input', () => {
input.style.height = 'auto';
input.style.height = input.scrollHeight + 'px';
});
// Show prompt and scroll to bottom
function showPrompt(prompt = '>>> ') {
const promptSpan = document.querySelector('.repl-prompt');
if (promptSpan) {
promptSpan.textContent = prompt;
}
}
// Append output to REPL
function appendOutput(text, className = '', prefix = '') {
const output = document.getElementById('repl-output');
const line = document.createElement('div');
line.className = `output-line ${className}`;
line.innerHTML = prefix + formatErrorMessage(text);
output.appendChild(line);
output.scrollTop = output.scrollHeight;
}
// Clear REPL
function clearREPL() {
const output = document.getElementById('repl-output');
output.innerHTML = '';
appendOutput('Screen cleared', 'system-message');
showPrompt();
}
// Example snippets
const examples = {
basic: `var x = 1011 + 1013;
println("x = ", x);`,
loops: `var sum = 0;
for (var i = 1; i <= 5; i += 1) {
sum += i;
}
println("Sum:", sum);`,
functions: `var factorial = func(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
};
factorial(5);`
};
// Insert example into input
function insertExample(type) {
input.value = examples[type];
input.style.height = 'auto';
input.style.height = input.scrollHeight + 'px';
input.focus();
}
// Initialize REPL on page load
window.addEventListener('load', initRepl);
// Add these utility functions
function formatErrorMessage(text) {
// Replace ANSI escape codes with CSS classes
return text
// Remove any existing formatting first
.replace(/\u001b\[\d+(?:;\d+)*m/g, '')
// Format the error line
.replace(/^parse: (.+)$/m, '<span class="error-type">parse:</span> <span class="error-desc">$1</span>')
// Format the file location
.replace(/^\s*--> (.+?)(\d+):(\d+)$/m, '<span class="error-arrow">--></span> <span class="error-file">$1</span><span class="error-loc">$2:$3</span>')
// Format the code line
.replace(/^(\d+ \|)(.*)$/m, '<span class="error-line-number">$1</span><span class="error-code">$2</span>')
// Format the error pointer
.replace(/^(\s*\|)(\s*)(\^+)$/m, '<span class="error-line-number">$1</span><span class="error-pointer-space">$2</span><span class="error-pointer">$3</span>');
}
function handleResult(result) {
const lines = result.split('\n');
lines.forEach(line => {
if (line.trim()) {
appendOutput(line.trim(), 'result-line');
}
});
}

96
nasal-web-app/server.js Normal file
View File

@@ -0,0 +1,96 @@
const express = require('express');
const path = require('path');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const koffi = require('koffi');
require('expose-gc');
// Parse command line arguments
const argv = yargs(hideBin(process.argv))
.usage('Usage: $0 [options]')
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging'
})
.option('port', {
alias: 'p',
type: 'number',
description: 'Port to run the server on',
default: 3000
})
.option('host', {
type: 'string',
description: 'Host to run the server on',
default: 'localhost'
})
.help()
.alias('help', 'h')
.version()
.argv;
const app = express();
app.use(express.json());
app.use(express.static('public'));
let nasalLib;
try {
// First load the library
const lib = koffi.load(path.join(__dirname, '../module/libnasal-web.dylib'));
// Then declare the functions explicitly
nasalLib = {
nasal_init: lib.func('nasal_init', 'void*', []),
nasal_cleanup: lib.func('nasal_cleanup', 'void', ['void*']),
nasal_eval: lib.func('nasal_eval', 'const char*', ['void*', 'const char*', 'int']),
nasal_get_error: lib.func('nasal_get_error', 'const char*', ['void*'])
};
} catch (err) {
console.error('Failed to load nasal library:', err);
process.exit(1);
}
app.post('/eval', (req, res) => {
const { code, showTime = false } = req.body;
if (!code) {
return res.status(400).json({ error: 'No code provided' });
}
if (argv.verbose) {
console.log('Received code evaluation request:', code);
console.log('Show time:', showTime);
}
const ctx = nasalLib.nasal_init();
try {
const result = nasalLib.nasal_eval(ctx, code, showTime ? 1 : 0);
const error = nasalLib.nasal_get_error(ctx);
if (error && error !== 'null') {
if (argv.verbose) console.log('Nasal error:', error);
res.json({ error: error });
} else if (result && result.trim() !== '') {
if (argv.verbose) console.log('Nasal output:', result);
res.json({ result: result });
} else {
if (argv.verbose) console.log('No output or error returned');
res.json({ error: 'No output or error returned' });
}
} catch (err) {
if (argv.verbose) console.error('Server error:', err);
res.status(500).json({ error: err.message });
} finally {
if (argv.verbose) console.log('Cleaning up Nasal context');
nasalLib.nasal_cleanup(ctx);
global.gc()
}
});
const PORT = argv.port || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`Visit http://localhost:${PORT} to use the Nasal interpreter`);
if (argv.verbose) console.log('Verbose logging enabled');
});

View File

@@ -0,0 +1,188 @@
const express = require('express');
const path = require('path');
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const koffi = require('koffi');
// Parse command line arguments
const argv = yargs(hideBin(process.argv))
.usage('Usage: $0 [options]')
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging'
})
.option('port', {
alias: 'p',
type: 'number',
description: 'Port to run the server on',
default: 3001
})
.option('host', {
type: 'string',
description: 'Host to run the server on',
default: 'localhost'
})
.help()
.alias('help', 'h')
.version()
.argv;
const app = express();
app.use(express.json());
app.use(express.static('public'));
// Load Nasal REPL library functions
let nasalLib;
try {
const lib = koffi.load(path.join(__dirname, '../module/libnasal-web.dylib'));
nasalLib = {
nasal_repl_init: lib.func('nasal_repl_init', 'void*', []),
nasal_repl_cleanup: lib.func('nasal_repl_cleanup', 'void', ['void*']),
nasal_repl_eval: lib.func('nasal_repl_eval', 'const char*', ['void*', 'const char*']),
nasal_repl_is_complete: lib.func('nasal_repl_is_complete', 'int', ['void*', 'const char*']),
nasal_repl_get_version: lib.func('nasal_repl_get_version', 'const char*', [])
};
if (argv.verbose) {
console.log('REPL Library loaded successfully');
}
} catch (err) {
console.error('Failed to load REPL library:', err);
process.exit(1);
}
// Store active REPL sessions
const replSessions = new Map();
// Clean up inactive sessions periodically (30 minutes timeout)
const SESSION_TIMEOUT = 30 * 60 * 1000;
setInterval(() => {
const now = Date.now();
for (const [sessionId, session] of replSessions.entries()) {
if (now - session.lastAccess > SESSION_TIMEOUT) {
if (argv.verbose) {
console.log(`Cleaning up inactive session: ${sessionId}`);
}
nasalLib.nasal_repl_cleanup(session.context);
replSessions.delete(sessionId);
}
}
}, 60000); // Check every minute
app.post('/repl/init', (req, res) => {
try {
const ctx = nasalLib.nasal_repl_init();
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const version = nasalLib.nasal_repl_get_version();
replSessions.set(sessionId, {
context: ctx,
lastAccess: Date.now()
});
if (argv.verbose) {
console.log(`New REPL session initialized: ${sessionId}`);
}
res.json({
sessionId,
version
});
} catch (err) {
if (argv.verbose) {
console.error('Failed to initialize REPL session:', err);
}
res.status(500).json({ error: 'Failed to initialize REPL session' });
}
});
app.post('/repl/eval', (req, res) => {
const { sessionId, line, check, buffer } = req.body;
if (!sessionId || !replSessions.has(sessionId)) {
return res.status(400).json({ error: 'Invalid or expired session' });
}
if (!line) {
return res.status(400).json({ error: 'No code provided' });
}
try {
const session = replSessions.get(sessionId);
session.lastAccess = Date.now();
if (check) {
const codeToCheck = buffer ? [...buffer, line].join('\n') : line;
const isComplete = nasalLib.nasal_repl_is_complete(session.context, codeToCheck);
if (isComplete === 1) {
return res.json({ needsMore: true });
} else if (isComplete === -1) {
return res.json({ error: 'Invalid input' });
}
}
const result = nasalLib.nasal_repl_eval(session.context, line);
if (argv.verbose) {
console.log(`REPL evaluation for session ${sessionId}:`, { line, result });
}
res.json({ result });
} catch (err) {
if (argv.verbose) {
console.error(`REPL evaluation error for session ${sessionId}:`, err);
}
res.status(500).json({ error: err.message });
}
});
app.post('/repl/cleanup', (req, res) => {
const { sessionId } = req.body;
if (!sessionId || !replSessions.has(sessionId)) {
return res.status(400).json({ error: 'Invalid session' });
}
try {
const session = replSessions.get(sessionId);
nasalLib.nasal_repl_cleanup(session.context);
replSessions.delete(sessionId);
if (argv.verbose) {
console.log(`REPL session cleaned up: ${sessionId}`);
}
res.json({ message: 'Session cleaned up successfully' });
} catch (err) {
if (argv.verbose) {
console.error(`Failed to cleanup session ${sessionId}:`, err);
}
res.status(500).json({ error: err.message });
}
});
// Handle cleanup on server shutdown
process.on('SIGINT', () => {
console.log('\nCleaning up REPL sessions before exit...');
for (const [sessionId, session] of replSessions.entries()) {
try {
nasalLib.nasal_repl_cleanup(session.context);
if (argv.verbose) {
console.log(`Cleaned up session: ${sessionId}`);
}
} catch (err) {
console.error(`Error cleaning up session ${sessionId}:`, err);
}
}
process.exit(0);
});
const PORT = argv.port || 3001;
app.listen(PORT, () => {
console.log(`REPL Server running on http://localhost:${PORT}`);
if (argv.verbose) console.log('Verbose logging enabled');
});

1
nasal-web-app/std Symbolic link
View File

@@ -0,0 +1 @@
../std

View File

@@ -1,55 +1,70 @@
#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->get_location());
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->get_location());
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->get_location());
std::cout << format_location(node);
return true;
}
bool ast_dumper::visit_string_literal(string_literal* node) {
dump_indent();
std::cout << "string \"" << rawstr(node->get_content()) << "\"";
std::cout << format_location(node->get_location());
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->get_location());
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->get_location());
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->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_elements()) {
for (auto i : node->get_elements()) {
if (i==node->get_elements().back()) {
set_last();
}
@@ -62,9 +77,9 @@ bool ast_dumper::visit_vector_expr(vector_expr* node) {
bool ast_dumper::visit_hash_expr(hash_expr* node) {
dump_indent();
std::cout << "hash";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_members()) {
for (auto i : node->get_members()) {
if (i==node->get_members().back()) {
set_last();
}
@@ -77,7 +92,7 @@ bool ast_dumper::visit_hash_expr(hash_expr* node) {
bool ast_dumper::visit_hash_pair(hash_pair* node) {
dump_indent();
std::cout << "pair " << node->get_name();
std::cout << format_location(node->get_location());
std::cout << format_location(node);
if (node->get_value()) {
push_indent();
set_last();
@@ -90,9 +105,9 @@ bool ast_dumper::visit_hash_pair(hash_pair* node) {
bool ast_dumper::visit_function(function* node) {
dump_indent();
std::cout << "function";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_parameter_list()) {
for (auto i : node->get_parameter_list()) {
i->accept(this);
}
set_last();
@@ -104,9 +119,9 @@ bool ast_dumper::visit_function(function* node) {
bool ast_dumper::visit_code_block(code_block* node) {
dump_indent();
std::cout << "block";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_expressions()) {
for (auto i : node->get_expressions()) {
if (i==node->get_expressions().back()) {
set_last();
}
@@ -118,8 +133,14 @@ bool ast_dumper::visit_code_block(code_block* node) {
bool ast_dumper::visit_parameter(parameter* node) {
dump_indent();
std::cout << "parameter " << node->get_parameter_name();
std::cout << format_location(node->get_location());
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();
@@ -132,7 +153,7 @@ bool ast_dumper::visit_parameter(parameter* node) {
bool ast_dumper::visit_ternary_operator(ternary_operator* node) {
dump_indent();
std::cout << "ternary_operator";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
node->get_condition()->accept(this);
node->get_left()->accept(this);
@@ -152,26 +173,27 @@ bool ast_dumper::visit_binary_operator(binary_operator* node) {
return true;
}
dump_indent();
std::cout << "binary_operator ";
std::cout << "binary_operator \"";
switch(node->get_operator_type()) {
case binary_operator::binary_type::add: std::cout << "+"; break;
case binary_operator::binary_type::sub: std::cout << "-"; break;
case binary_operator::binary_type::mult: std::cout << "*"; break;
case binary_operator::binary_type::div: std::cout << "/"; break;
case binary_operator::binary_type::concat: std::cout << "~"; break;
case binary_operator::binary_type::bitwise_and: std::cout << "&"; break;
case binary_operator::binary_type::bitwise_or: std::cout << "|"; break;
case binary_operator::binary_type::bitwise_xor: std::cout << "^"; break;
case binary_operator::binary_type::cmpeq: std::cout << "=="; break;
case binary_operator::binary_type::cmpneq: std::cout << "!="; break;
case binary_operator::binary_type::grt: std::cout << ">"; break;
case binary_operator::binary_type::geq: std::cout << ">="; break;
case binary_operator::binary_type::less: std::cout << "<"; break;
case binary_operator::binary_type::leq: std::cout << "<="; break;
case binary_operator::binary_type::condition_and: std::cout << "and"; break;
case binary_operator::binary_type::condition_or: std::cout << "or"; break;
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->get_location());
std::cout << "\"" << format_location(node);
push_indent();
node->get_left()->accept(this);
set_last();
@@ -186,13 +208,13 @@ bool ast_dumper::visit_unary_operator(unary_operator* node) {
return true;
}
dump_indent();
std::cout << "unary_operator ";
std::cout << "unary_operator \"";
switch(node->get_operator_type()) {
case unary_operator::unary_type::negative: std::cout << "-"; break;
case unary_operator::unary_type::logical_not: std::cout << "!"; break;
case unary_operator::unary_type::bitwise_not: std::cout << "~"; break;
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->get_location());
std::cout << "\"" << format_location(node);
push_indent();
set_last();
node->get_value()->accept(this);
@@ -203,13 +225,13 @@ bool ast_dumper::visit_unary_operator(unary_operator* node) {
bool ast_dumper::visit_call_expr(call_expr* node) {
dump_indent();
std::cout << "call_expr";
std::cout << format_location(node->get_location());
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()) {
for (auto i : node->get_calls()) {
if (i==node->get_calls().back()) {
set_last();
}
@@ -222,16 +244,23 @@ bool ast_dumper::visit_call_expr(call_expr* node) {
bool ast_dumper::visit_call_hash(call_hash* node) {
dump_indent();
std::cout << "call_hash " << node->get_field();
std::cout << format_location(node->get_location());
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->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_slices()) {
for (auto i : node->get_slices()) {
if (i==node->get_slices().back()) {
set_last();
}
@@ -244,9 +273,9 @@ bool ast_dumper::visit_call_vector(call_vector* node) {
bool ast_dumper::visit_call_function(call_function* node) {
dump_indent();
std::cout << "call_function";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_argument()) {
for (auto i : node->get_argument()) {
if (i==node->get_argument().back()) {
set_last();
}
@@ -259,7 +288,7 @@ bool ast_dumper::visit_call_function(call_function* node) {
bool ast_dumper::visit_slice_vector(slice_vector* node) {
dump_indent();
std::cout << "slice";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
if (!node->get_end()) {
set_last();
@@ -276,7 +305,7 @@ bool ast_dumper::visit_slice_vector(slice_vector* node) {
bool ast_dumper::visit_definition_expr(definition_expr* node) {
dump_indent();
std::cout << "definition";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
if (node->get_variable_name()) {
node->get_variable_name()->accept(this);
@@ -297,17 +326,17 @@ bool ast_dumper::visit_assignment_expr(assignment_expr* node) {
dump_indent();
std::cout << "assignment ";
switch(node->get_assignment_type()) {
case assignment_expr::assign_type::add_equal: std::cout << "+="; break;
case assignment_expr::assign_type::sub_equal: std::cout << "-="; break;
case assignment_expr::assign_type::mult_equal: std::cout << "*="; break;
case assignment_expr::assign_type::div_equal: std::cout << "/="; break;
case assignment_expr::assign_type::concat_equal: std::cout << "~="; break;
case assignment_expr::assign_type::equal: std::cout << "="; break;
case assignment_expr::assign_type::bitwise_and_equal: std::cout << "&="; break;
case assignment_expr::assign_type::bitwise_or_equal: std::cout << "|="; break;
case assignment_expr::assign_type::bitwise_xor_equal: std::cout << "^="; break;
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->get_location());
std::cout << format_location(node);
push_indent();
node->get_left()->accept(this);
set_last();
@@ -319,9 +348,9 @@ bool ast_dumper::visit_assignment_expr(assignment_expr* node) {
bool ast_dumper::visit_multi_identifier(multi_identifier* node) {
dump_indent();
std::cout << "multiple_identifier";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_variables()) {
for (auto i : node->get_variables()) {
if (i==node->get_variables().back()) {
set_last();
}
@@ -334,9 +363,9 @@ bool ast_dumper::visit_multi_identifier(multi_identifier* node) {
bool ast_dumper::visit_tuple_expr(tuple_expr* node) {
dump_indent();
std::cout << "tuple";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
for(auto i : node->get_elements()) {
for (auto i : node->get_elements()) {
if (i==node->get_elements().back()) {
set_last();
}
@@ -349,7 +378,7 @@ bool ast_dumper::visit_tuple_expr(tuple_expr* node) {
bool ast_dumper::visit_multi_assign(multi_assign* node) {
dump_indent();
std::cout << "multiple_assignment";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
node->get_tuple()->accept(this);
set_last();
@@ -361,7 +390,7 @@ bool ast_dumper::visit_multi_assign(multi_assign* node) {
bool ast_dumper::visit_while_expr(while_expr* node) {
dump_indent();
std::cout << "while";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
node->get_condition()->accept(this);
set_last();
@@ -373,7 +402,7 @@ bool ast_dumper::visit_while_expr(while_expr* node) {
bool ast_dumper::visit_for_expr(for_expr* node) {
dump_indent();
std::cout << "for";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
node->get_initial()->accept(this);
node->get_condition()->accept(this);
@@ -386,8 +415,12 @@ bool ast_dumper::visit_for_expr(for_expr* node) {
bool ast_dumper::visit_iter_expr(iter_expr* node) {
dump_indent();
std::cout << "iterator";
std::cout << format_location(node->get_location());
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()) {
@@ -401,12 +434,12 @@ bool ast_dumper::visit_iter_expr(iter_expr* node) {
bool ast_dumper::visit_forei_expr(forei_expr* node) {
dump_indent();
if (node->get_loop_type()==forei_expr::forei_loop_type::foreach) {
if (node->get_loop_type()==forei_expr::kind::foreach) {
std::cout << "foreach";
} else {
std::cout << "forindex";
}
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
node->get_iterator()->accept(this);
node->get_value()->accept(this);
@@ -419,14 +452,14 @@ bool ast_dumper::visit_forei_expr(forei_expr* node) {
bool ast_dumper::visit_condition_expr(condition_expr* node) {
dump_indent();
std::cout << "condition";
std::cout << format_location(node->get_location());
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()) {
for (auto i : node->get_elsif_stataments()) {
if (i==node->get_elsif_stataments().back() &&
!node->get_else_statement()) {
set_last();
@@ -444,7 +477,7 @@ bool ast_dumper::visit_condition_expr(condition_expr* node) {
bool ast_dumper::visit_if_expr(if_expr* node) {
dump_indent();
std::cout << "if";
std::cout << format_location(node->get_location());
std::cout << format_location(node);
push_indent();
if (node->get_condition()) {
node->get_condition()->accept(this);
@@ -458,21 +491,21 @@ bool ast_dumper::visit_if_expr(if_expr* node) {
bool ast_dumper::visit_continue_expr(continue_expr* node) {
dump_indent();
std::cout << "continue";
std::cout << format_location(node->get_location());
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->get_location());
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->get_location());
std::cout << format_location(node);
if (node->get_value()) {
push_indent();
set_last();

View File

@@ -1,85 +1,101 @@
#pragma once
#include "ast_visitor.h"
#include <iostream>
#include <cstring>
#include <sstream>
namespace nasal {
class ast_dumper:public ast_visitor {
private:
std::vector<std::string> indent;
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(const span& location) {
std::stringstream ss;
ss << " -> ";
ss << location.file << ":";
ss << location.begin_line << ":" << location.begin_column + 1;
ss << "\n";
return ss.str();
}
public:
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_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) {
root->accept(this);
}
};
}
#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();
}
};
}

427
src/ast_format.cpp Normal file
View File

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

148
src/ast_format.h Normal file
View File

@@ -0,0 +1,148 @@
#pragma once
#include "ast_visitor.h"
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstring>
#include <sstream>
#include <vector>
namespace nasal {
class ast_format: public ast_visitor {
private:
std::ofstream out;
std::vector<std::string> indent;
private:
void push_indent() {
indent.push_back(" ");
}
void pop_indent() {
if (indent.size()) {
indent.pop_back();
}
}
void dump_indent() {
for (const auto& i : indent) {
out << i;
}
}
void dump_formating_node_info(expr* n, const char* name) {
std::cout << " formating " << name << " @ 0x";
std::cout << std::hex << n << std::dec << "\n";
}
bool need_dump_semi(expr* n) {
switch (n->get_type()) {
case expr_type::ast_use:
case expr_type::ast_null:
case expr_type::ast_nil:
case expr_type::ast_num:
case expr_type::ast_str:
case expr_type::ast_bool:
case expr_type::ast_vec:
case expr_type::ast_hash:
case expr_type::ast_call:
case expr_type::ast_multi_assign:
case expr_type::ast_unary:
case expr_type::ast_binary:
case expr_type::ast_ternary: return true;
case expr_type::ast_def: {
auto dn = reinterpret_cast<definition_expr*>(n);
if (dn->get_value() &&
dn->get_value()->get_type() == expr_type::ast_func) {
return false;
}
return true;
}
case expr_type::ast_assign: {
auto dn = reinterpret_cast<assignment_expr*>(n);
if (dn->get_right() &&
dn->get_right()->get_type() == expr_type::ast_func) {
return false;
}
return true;
}
case expr_type::ast_ret: {
auto dn = reinterpret_cast<return_expr*>(n);
if (dn->get_value() &&
dn->get_value()->get_type() == expr_type::ast_func) {
return false;
}
return true;
}
default: break;
}
return false;
}
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:
ast_format(const std::string output_file): out(output_file) {
if (out.fail()) {
throw std::runtime_error("can't open file: " + output_file);
}
}
void do_format(code_block* root) {
std::cout << "nasal-format is not stable right now, ";
std::cout << "take care of source code!\n";
dump_formating_node_info(root, "program root");
bool is_use_statement = true;
for (auto i : root->get_expressions()) {
if (is_use_statement && i->get_type() != expr_type::ast_use) {
is_use_statement = false;
out << "\n";
}
i->accept(this);
if (need_dump_semi(i)) {
out << ";\n";
} else {
out << "\n";
}
}
}
};
}

View File

@@ -7,6 +7,13 @@ bool ast_visitor::visit_expr(expr* node) {
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;
@@ -37,14 +44,14 @@ bool ast_visitor::visit_bool_literal(bool_literal* node) {
}
bool ast_visitor::visit_vector_expr(vector_expr* node) {
for(auto i : node->get_elements()) {
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()) {
for (auto i : node->get_members()) {
i->accept(this);
}
return true;
@@ -58,7 +65,7 @@ bool ast_visitor::visit_hash_pair(hash_pair* node) {
}
bool ast_visitor::visit_function(function* node) {
for(auto i : node->get_parameter_list()) {
for (auto i : node->get_parameter_list()) {
i->accept(this);
}
node->get_code_block()->accept(this);
@@ -66,7 +73,7 @@ bool ast_visitor::visit_function(function* node) {
}
bool ast_visitor::visit_code_block(code_block* node) {
for(auto i : node->get_expressions()) {
for (auto i : node->get_expressions()) {
i->accept(this);
}
return true;
@@ -99,7 +106,7 @@ bool ast_visitor::visit_unary_operator(unary_operator* node) {
bool ast_visitor::visit_call_expr(call_expr* node) {
node->get_first()->accept(this);
for(auto i : node->get_calls()) {
for (auto i : node->get_calls()) {
i->accept(this);
}
return true;
@@ -109,15 +116,19 @@ 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()) {
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()) {
for (auto i : node->get_argument()) {
i->accept(this);
}
return true;
@@ -152,14 +163,14 @@ bool ast_visitor::visit_assignment_expr(assignment_expr* node) {
}
bool ast_visitor::visit_multi_identifier(multi_identifier* node) {
for(auto i : node->get_variables()) {
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()) {
for (auto i : node->get_elements()) {
i->accept(this);
}
return true;
@@ -203,7 +214,7 @@ bool ast_visitor::visit_forei_expr(forei_expr* node) {
bool ast_visitor::visit_condition_expr(condition_expr* node) {
node->get_if_statement()->accept(this);
for(auto i : node->get_elsif_stataments()) {
for (auto i : node->get_elsif_stataments()) {
i->accept(this);
}
if (node->get_else_statement()) {

View File

@@ -7,6 +7,7 @@ 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*);
@@ -25,6 +26,7 @@ public:
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*);

View File

@@ -1,153 +0,0 @@
#include "bits_lib.h"
namespace nasal {
var builtin_u32xor(var* local, gc& ngc) {
return var::num(static_cast<f64>(
static_cast<u32>(local[1].num()) ^
static_cast<u32>(local[2].num())
));
}
var builtin_u32and(var* local, gc& ngc) {
return var::num(static_cast<f64>(
static_cast<u32>(local[1].num()) &
static_cast<u32>(local[2].num())
));
}
var builtin_u32or(var* local, gc& ngc) {
return var::num(static_cast<f64>(
static_cast<u32>(local[1].num()) |
static_cast<u32>(local[2].num())
));
}
var builtin_u32nand(var* local, gc& ngc) {
return var::num(static_cast<f64>(~(
static_cast<u32>(local[1].num()) &
static_cast<u32>(local[2].num())
)));
}
var builtin_u32not(var* local, gc& ngc) {
return var::num(static_cast<f64>(~static_cast<u32>(local[1].num())));
}
var builtin_fld(var* local, gc& ngc) {
// bits.fld(s,0,3);
// if s stores 10100010(162)
// will get 101(5)
var str = local[1];
var startbit = local[2];
var length = local[3];
if (str.type!=vm_str || str.val.gcobj->unmut) {
return nas_err("fld", "\"str\" must be mutable string");
}
if (startbit.type!=vm_num || length.type!=vm_num) {
return nas_err("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("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(var* local, 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)
var str = local[1];
var startbit = local[2];
var length = local[3];
if (str.type!=vm_str || str.val.gcobj->unmut) {
return nas_err("sfld", "\"str\" must be mutable string");
}
if (startbit.type!=vm_num || length.type!=vm_num) {
return nas_err("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("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(var* local, gc& ngc) {
// bits.setfld(s,0,8,69);
// set 01000101(69) to string will get this:
// 10100010(162)
// so s[0]=162
var str = local[1];
var startbit = local[2];
var length = local[3];
var value = local[4];
if (str.type!=vm_str || str.val.gcobj->unmut) {
return nas_err("setfld", "\"str\" must be mutable string");
}
if (startbit.type!=vm_num || length.type!=vm_num || value.type!=vm_num) {
return nas_err("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("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(var* local, gc& ngc) {
var length = local[1];
if (length.type!=vm_num || length.num()<=0) {
return nas_err("buf", "\"len\" must be number greater than 0");
}
var str = ngc.alloc(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}
};
}

View File

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

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

@@ -0,0 +1,139 @@
#include "nasal.h"
#include "cli/cli.h"
#include "util/util.h"
#include "nasal_parse.h"
#include <iostream>
#include <thread>
#include <cstdlib>
#include <ctime>
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;
}
std::ostream& nasal_format_help(std::ostream& out) {
out
<< "\n"
<< " ,--#-,\n"
<< "<3 / \\____\\ <3\n"
<< " |_|__A_|\n"
<< "\nnasal-format <option>\n"
<< "option:\n"
<< " -h, --help | get help.\n"
<< " -v, --version | get version.\n"
<< "\nnasal-format <file>\n"
<< "file:\n"
<< " <filename> | file to be formatted.\n"
<< "\n";
return out;
}
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;
}
std::ostream& nasal_format_version(std::ostream& out) {
out << "nasal-format version " << __nasver__ << "-beta";
out << " " << nasal::util::get_platform();
out << " " << nasal::util::get_arch();
out << " (" << __DATE__ << " " << __TIME__ << ")\n";
return out;
}
}

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

@@ -0,0 +1,78 @@
#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&);
std::ostream& nasal_format_help(std::ostream&);
std::ostream& logo(std::ostream&);
std::ostream& version(std::ostream&);
std::ostream& nasal_format_version(std::ostream&);
}

View File

@@ -1,120 +0,0 @@
#include "coroutine.h"
namespace nasal {
var builtin_cocreate(var* local, 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
// +-------------+
var func = local[1];
if (func.type!=vm_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");
}
var co = ngc.alloc(vm_co);
nas_co& cort = co.co();
cort.ctx.pc = func.func().entry-1;
cort.ctx.top[0] = nil;
cort.ctx.localr = cort.ctx.top+1;
cort.ctx.top = cort.ctx.localr+func.func().lsize;
cort.ctx.localr[0] = func.func().local[0];
cort.ctx.top[0] = nil; // old upvalr
cort.ctx.top++;
cort.ctx.top[0] = var::addr((var*)nullptr); // old localr
cort.ctx.top++;
cort.ctx.top[0] = var::ret(0); // old pc, set to zero to make op_ret recognizing this as coroutine function
cort.ctx.funcr = func; // make sure the coroutine function can use correct upvalues
cort.status = nas_co::status::suspended;
return co;
}
var builtin_coresume(var* local, gc& ngc) {
if (ngc.cort) {
return nas_err("coroutine::resume", "cannot start another coroutine when one is running");
}
var co = local[1];
// return nil if is not a coroutine object
if (co.type!=vm_co) {
return nil;
}
// cannot resume a dead coroutine
if (co.co().status==nas_co::status::dead) {
return nil;
}
// change to coroutine context
ngc.ctxchg(co.co());
// fetch coroutine's stack top and return
// so the coroutine's stack top in fact is not changed
if (ngc.rctx->top[0].type==vm_ret) {
// when first calling this coroutine, the stack top must be vm_ret
return ngc.rctx->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 local[2];
}
var builtin_coyield(var* local, gc& ngc) {
if (!ngc.cort) {
return nas_err("coroutine::yield", "no coroutine is running");
}
// this will set to main stack top
ngc.ctxreserve();
// 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 local[1];
}
var builtin_costatus(var* local, gc& ngc) {
var co = local[1];
if (co.type!=vm_co) {
return ngc.newstr("error");
}
switch(co.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(var* local, 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}
};
}

View File

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

View File

@@ -1,117 +0,0 @@
#include "dylib_lib.h"
namespace nasal {
const auto dylib_type_name = "dylib";
const auto func_addr_type_name = "faddr";
void dylib_destructor(void* ptr) {
#ifdef _WIN32
FreeLibrary(static_cast<HMODULE>(ptr));
#else
dlclose(ptr);
#endif
}
void func_addr_destructor(void* ptr) {}
var builtin_dlopen(var* local, gc& ngc) {
var dlname = local[1];
if (dlname.type!=vm_str) {
return nas_err("dlopen", "\"libname\" must be string");
}
#ifdef _WIN32
wchar_t* str = new wchar_t[dlname.str().size()+1];
if (!str) {
return nas_err("dlopen", "malloc failed");
}
memset(str, 0, sizeof(wchar_t)*dlname.str().size()+1);
mbstowcs(str, dlname.str().c_str(),dlname.str().size()+1);
void* ptr = LoadLibraryA(dlname.str().c_str());
delete []str;
#else
void* ptr = dlopen(dlname.str().c_str(), RTLD_LOCAL|RTLD_LAZY);
#endif
if (!ptr) {
return nas_err("dlopen", "cannot open dynamic lib <"+dlname.str()+">");
}
var ret = ngc.temp = ngc.alloc(vm_hash);
var lib = ngc.alloc(vm_obj);
lib.obj().set(dylib_type_name, dylib_destructor, ptr);
ret.hash().elems["lib"] = lib;
#ifdef _WIN32
void* func = (void*)GetProcAddress(
static_cast<HMODULE>(lib.obj().ptr),
"get"
);
#else
void* func = dlsym(lib.obj().ptr, "get");
#endif
if (!func) {
return nas_err("dlopen", "cannot find <get> function");
}
// get function pointer by name
module_func_info* tbl = reinterpret_cast<get_func_ptr>(func)();
if (!tbl) {
return nas_err("dlopen", "failed to get module functions");
}
for(u32 i = 0; tbl[i].name; ++i) {
void* p = (void*)tbl[i].fd;
var tmp = ngc.alloc(vm_obj);
tmp.obj().set(func_addr_type_name, func_addr_destructor, p);
ret.hash().elems[tbl[i].name] = tmp;
}
ngc.temp = nil;
return ret;
}
var builtin_dlclose(var* local, gc& ngc) {
var libptr = local[1];
if (!libptr.objchk(dylib_type_name)) {
return nas_err("dlclose", "\"lib\" is not a valid dynamic lib");
}
libptr.obj().clear();
return nil;
}
var builtin_dlcallv(var* local, gc& ngc) {
var fp = local[1];
var args = local[2];
if (!fp.objchk(func_addr_type_name)) {
return nas_err("dlcall", "\"ptr\" is not a valid function pointer");
}
auto& vec = args.vec().elems;
return reinterpret_cast<module_func>(fp.obj().ptr)(
vec.data(),
vec.size(),
&ngc
);
}
var builtin_dlcall(var* local, gc& ngc) {
var fp = local[1];
if (!fp.objchk(func_addr_type_name)) {
return nas_err("dlcall", "\"ptr\" is not a valid function pointer");
}
var* local_frame_start = local+2;
usize local_frame_size = ngc.rctx->top-local_frame_start;
// arguments' stored place begins at local +2
return reinterpret_cast<module_func>(fp.obj().ptr)(
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}
};
}

View File

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

53
src/format.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "nasal.h"
#include "nasal_lexer.h"
#include "nasal_ast.h"
#include "nasal_parse.h"
#include "util/util.h"
#include "cli/cli.h"
#include "ast_format.h"
#include <iostream>
#include <thread>
[[noreturn]]
void err() {
std::cerr << "invalid argument(s), use <nasal-format -h> to get help.\n";
std::exit(1);
}
void execute(const nasal::cli::cli_config& config) {
nasal::lexer lex;
nasal::parse parse;
// 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();
nasal::ast_format("nasal-format-out.nas").do_format(parse.tree());
}
int main(i32 argc, const char* argv[]) {
// output version info
if (argc <= 1) {
err();
} else if (argc > 2) {
err();
}
// 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 (config.has(nasal::cli::option::cli_help)) {
std::clog << nasal::cli::nasal_format_help;
} else if (config.has(nasal::cli::option::cli_version)) {
std::clog << nasal::cli::nasal_format_version;
} else if (config.input_file_path.size()) {
execute(config);
} else {
err();
}
return 0;
}

View File

@@ -1,213 +0,0 @@
#include "io_lib.h"
namespace nasal {
const auto file_type_name = "file";
void filehandle_destructor(void* ptr) {
if (static_cast<FILE*>(ptr)==stdin) {
return;
}
fclose(static_cast<FILE*>(ptr));
}
var builtin_readfile(var* local, gc& ngc) {
var val = local[1];
if (val.type!=vm_str) {
return nas_err("io::readfile", "\"filename\" must be string");
}
std::ifstream in(val.str(), std::ios::binary);
std::stringstream rd;
if (!in.fail()) {
rd << in.rdbuf();
}
return ngc.newstr(rd.str());
}
var builtin_fout(var* local, gc& ngc) {
var val = local[1];
var str = local[2];
if (val.type!=vm_str) {
return nas_err("io::fout", "\"filename\" must be string");
}
std::ofstream out(val.str());
if (out.fail()) {
return nas_err("io::fout", "cannot open <"+val.str()+">");
}
out << str;
return nil;
}
var builtin_exists(var* local, gc& ngc) {
if (local[1].type!=vm_str) {
return zero;
}
return access(local[1].str().c_str(), F_OK)!=-1?one:zero;
}
var builtin_open(var* local, gc& ngc) {
var name = local[1];
var mode = local[2];
if (name.type!=vm_str) {
return nas_err("open", "\"filename\" must be string");
}
if (mode.type!=vm_str) {
return nas_err("open", "\"mode\" must be string");
}
FILE* res = fopen(name.str().c_str(), mode.str().c_str());
if (!res) {
return nas_err("open", "failed to open file <"+name.str()+">");
}
var ret = ngc.alloc(vm_obj);
ret.obj().set(file_type_name, filehandle_destructor, res);
return ret;
}
var builtin_close(var* local, gc& ngc) {
var fd = local[1];
if (!fd.objchk(file_type_name)) {
return nas_err("close", "not a valid filehandle");
}
fd.obj().clear();
return nil;
}
var builtin_read(var* local, gc& ngc) {
var fd = local[1];
var buf = local[2];
var len = local[3];
if (!fd.objchk(file_type_name)) {
return nas_err("read", "not a valid filehandle");
}
if (buf.type!=vm_str || buf.val.gcobj->unmut) {
return nas_err("read", "\"buf\" must be mutable string");
}
if (len.type!=vm_num) {
return nas_err("read", "\"len\" must be number");
}
if (len.num()<=0 || len.num()>=(1<<30)) {
return nas_err("read", "\"len\" less than 1 or too large");
}
char* buff = new char[(usize)len.num()+1];
if (!buff) {
return nas_err("read", "malloc failed");
}
f64 res = fread(buff, 1, len.num(), static_cast<FILE*>(fd.obj().ptr));
buf.str() = buff;
buf.val.gcobj->unmut = true;
delete []buff;
return var::num(res);
}
var builtin_write(var* local, gc& ngc) {
var fd = local[1];
var str = local[2];
if (!fd.objchk(file_type_name)) {
return nas_err("write", "not a valid filehandle");
}
if (str.type!=vm_str) {
return nas_err("write", "\"str\" must be string");
}
return var::num(static_cast<f64>(fwrite(
str.str().c_str(),
1,
str.str().length(),
static_cast<FILE*>(fd.obj().ptr)
)));
}
var builtin_seek(var* local, gc& ngc) {
var fd = local[1];
var pos = local[2];
var whence = local[3];
if (!fd.objchk(file_type_name)) {
return nas_err("seek", "not a valid filehandle");
}
return var::num(static_cast<f64>(fseek(
static_cast<FILE*>(fd.obj().ptr),
pos.num(),
whence.num()
)));
}
var builtin_tell(var* local, gc& ngc) {
var fd = local[1];
if (!fd.objchk(file_type_name)) {
return nas_err("tell", "not a valid filehandle");
}
return var::num(static_cast<f64>(ftell(static_cast<FILE*>(fd.obj().ptr))));
}
var builtin_readln(var* local, gc& ngc) {
var fd = local[1];
if (!fd.objchk(file_type_name)) {
return nas_err("readln", "not a valid filehandle");
}
var str = ngc.alloc(vm_str);
char c;
while((c = fgetc(static_cast<FILE*>(fd.obj().ptr)))!=EOF) {
if (c=='\r') {
continue;
}
if (c=='\n') {
return str;
}
str.str() += c;
}
if (str.str().length()) {
return str;
}
return nil;
}
var builtin_stat(var* local, gc& ngc) {
var name = local[1];
if (name.type!=vm_str) {
return nas_err("stat", "\"filename\" must be string");
}
struct stat buf;
if (stat(name.str().c_str(),&buf)<0) {
return nas_err("stat", "failed to open file <"+name.str()+">");
}
var ret = ngc.alloc(vm_vec);
ret.vec().elems = {
var::num(static_cast<f64>(buf.st_dev)),
var::num(static_cast<f64>(buf.st_ino)),
var::num(static_cast<f64>(buf.st_mode)),
var::num(static_cast<f64>(buf.st_nlink)),
var::num(static_cast<f64>(buf.st_uid)),
var::num(static_cast<f64>(buf.st_gid)),
var::num(static_cast<f64>(buf.st_rdev)),
var::num(static_cast<f64>(buf.st_size)),
var::num(static_cast<f64>(buf.st_atime)),
var::num(static_cast<f64>(buf.st_mtime)),
var::num(static_cast<f64>(buf.st_ctime))
};
return ret;
}
var builtin_eof(var* local, gc& ngc) {
var fd = local[1];
if (!fd.objchk(file_type_name)) {
return nas_err("readln", "not a valid filehandle");
}
return var::num(static_cast<f64>(feof(static_cast<FILE*>(fd.obj().ptr))));
}
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},
{nullptr, nullptr}
};
}

View File

@@ -1,32 +0,0 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#include "nasal_builtin.h"
#include <sys/stat.h>
#ifdef _MSC_VER
#define F_OK 0 // fuck msc
#endif
namespace nasal {
void filehandle_destructor(void*);
var builtin_readfile(var*, gc&);
var builtin_fout(var*, gc&);
var builtin_exists(var*, gc&);
var builtin_open(var*, gc&);
var builtin_close(var*, gc&);
var builtin_read(var*, gc&);
var builtin_write(var*, gc&);
var builtin_seek(var*, gc&);
var builtin_tell(var*, gc&);
var builtin_readln(var*, gc&);
var builtin_stat(var*, gc&);
var builtin_eof(var*, gc&);
extern nasal_builtin_table io_lib_native[];
}

View File

@@ -1,4 +1,6 @@
#include "nasal.h"
#include "nasal_type.h"
#include "nasal_gc.h"
#include "nasal_err.h"
#include "nasal_lexer.h"
#include "nasal_ast.h"
@@ -11,104 +13,21 @@
#include "nasal_codegen.h"
#include "nasal_vm.h"
#include "nasal_dbg.h"
#include "repl.h"
#include <unordered_map>
#include <thread>
#include "util/util.h"
#include "repl/repl.h"
#include "cli/cli.h"
#include <cstdlib>
const u32 VM_RAW_AST = 1;
const u32 VM_AST = 1<<1;
const u32 VM_CODE = 1<<2;
const u32 VM_TIME = 1<<3;
const u32 VM_EXEC = 1<<4;
const u32 VM_DETAIL = 1<<5;
const u32 VM_DEBUG = 1<<6;
const u32 VM_SYMINFO = 1<<7;
const u32 VM_PROFILE = 1<<8;
const u32 VM_PROF_ALL = 1<<9;
std::ostream& help(std::ostream& out) {
out
<< "\n"
<< " ,--#-,\n"
<< "<3 / \\____\\ <3\n"
<< " |_|__A_|\n"
#ifdef _WIN32
<< "use command <chcp 65001> to use unicode.\n"
#endif
<< "\nnasal <option>\n"
<< "option:\n"
<< " -h, --help | get help.\n"
<< " -v, --version | get version.\n"
<< " -r, --repl | use repl interpreter(experimental).\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"
<< " -dbg, --debug | debug mode.\n"
<< " --prof | show profiling result, available in debug mode.\n"
<< " --prof-all | show profiling result of all files,"
<< "available under debug mode.\n"
<< "file:\n"
<< " <filename> | execute file.\n"
<< "argv:\n"
<< " <args> | cmd arguments used in program.\n"
<< "\n";
return out;
}
std::ostream& logo(std::ostream& out) {
out
<< "\n"
<< " __ _\n"
<< " /\\ \\ \\__ _ ___ __ _| |\n"
<< " / \\/ / _` / __|/ _` | |\n"
<< " / /\\ / (_| \\__ \\ (_| | |\n"
<< " \\_\\ \\/ \\__,_|___/\\__,_|_|\n"
<< "ver : " << __nasver << " (" << __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"
<< "input <nasal -h> to get help .\n\n";
return out;
}
std::ostream& version(std::ostream& out) {
std::srand(std::time(nullptr));
f64 num = 0;
for(u32 i = 0; i<5; ++i) {
num = (num+rand())*(1.0/(RAND_MAX+1.0));
}
if (num<0.01) {
nasal::parse::easter_egg();
}
out << "nasal interpreter version " << __nasver;
out << " (" << __DATE__ << " " << __TIME__ << ")\n";
return out;
}
[[noreturn]]
void err() {
std::cerr
<< "invalid argument(s).\n"
<< "use <nasal -h> to get help.\n";
std::cerr << "invalid argument(s), use <nasal -h> to get help.\n";
std::exit(1);
}
void execute(
const std::string& file,
const std::vector<std::string>& argv,
const u32 cmd) {
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;
@@ -118,73 +37,102 @@ void execute(
nasal::codegen gen;
// lexer scans file to get tokens
lex.scan(file).chkerr();
lex.scan(config.input_file_path).chkerr();
// parser gets lexer's token list to compile
parse.compile(lex).chkerr();
if (cmd&VM_RAW_AST) {
auto dumper = std::unique_ptr<nasal::ast_dumper>(new nasal::ast_dumper);
dumper->dump(parse.tree());
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, file, cmd&VM_DETAIL).chkerr();
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::unique_ptr<nasal::optimizer>(new nasal::optimizer);
auto opt = std::make_unique<nasal::optimizer>();
opt->do_optimization(parse.tree());
if (cmd&VM_AST) {
auto dumper = std::unique_ptr<nasal::ast_dumper>(new nasal::ast_dumper);
dumper->dump(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).chkerr();
if (cmd&VM_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 (cmd&VM_SYMINFO) {
if (config.has(option::cli_show_symbol)) {
gen.symbol_dump(std::cout);
}
// run
auto start = clk::now();
if (cmd&VM_DEBUG) {
auto debugger = std::unique_ptr<nasal::dbg>(new nasal::dbg);
debugger->run(gen, ld, argv, cmd&VM_PROFILE, cmd&VM_PROF_ALL);
} else if (cmd&VM_TIME || cmd&VM_EXEC) {
auto runtime = std::unique_ptr<nasal::vm>(new nasal::vm);
runtime->set_detail_report_info(cmd&VM_DETAIL);
runtime->run(gen, ld, argv);
const auto start = clk::now();
double gc_time_ms = 0.0;
double gc_total_memory = 0.0;
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)
);
gc_time_ms = debugger->get_gc_time_ms();
gc_total_memory = debugger->get_total_memory();
} 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);
gc_time_ms = runtime->get_gc_time_ms();
gc_total_memory = runtime->get_total_memory();
}
// get running time
auto end = clk::now();
if (cmd&VM_TIME) {
const auto end = clk::now();
if (config.has(option::cli_show_execute_time)) {
double execute_time_sec = static_cast<f64>((end - start).count())/den;
double gc_time_sec = gc_time_ms / 1000.0;
std::clog << "process exited after ";
std::clog << (end-start).count()*1.0/den << "s.\n\n";
std::clog << execute_time_sec << "s, gc time: ";
std::clog << gc_time_sec << "s (";
std::clog << gc_time_sec / execute_time_sec * 100.0 << "%), ";
std::clog << "memory usage: " << gc_total_memory << "MB\n\n";
}
}
i32 main(i32 argc, const char* argv[]) {
// output version info
if (argc<=1) {
std::clog << logo;
if (argc <= 1) {
std::clog << nasal::cli::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) {
std::string s(argv[1]);
if (s=="-h" || s=="--help") {
std::clog << help;
} else if (s=="-v" || s=="--version") {
std::clog << version;
} else if (s=="-r" || s=="--repl") {
auto repl = std::unique_ptr<nasal::repl::repl>(new nasal::repl::repl);
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 << nasal::cli::version;
} else if (config.has(nasal::cli::option::cli_repl_mode)) {
auto repl = std::make_unique<nasal::repl::repl>();
repl->execute();
} else if (s[0]!='-') {
execute(s, {}, VM_EXEC);
} else if (config.input_file_path.size()) {
execute(config);
} else {
err();
}
@@ -192,40 +140,6 @@ i32 main(i32 argc, const char* argv[]) {
}
// execute with arguments
const std::unordered_map<std::string, u32> cmdlst = {
{"--raw-ast", VM_RAW_AST},
{"--ast", VM_AST},
{"-a", VM_AST},
{"--code", VM_CODE},
{"-c", VM_CODE},
{"--symbol", VM_SYMINFO},
{"-s", VM_SYMINFO},
{"--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},
{"--debug", VM_DEBUG},
{"-dbg", VM_DEBUG},
{"--prof", VM_PROFILE},
{"--prof-all", VM_PROF_ALL}
};
u32 cmd = 0;
std::string filename = "";
std::vector<std::string> vm_argv;
for(i32 i = 1; i<argc; ++i) {
if (cmdlst.count(argv[i])) {
cmd |= cmdlst.at(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);
execute(config);
return 0;
}

View File

@@ -1,77 +0,0 @@
#include "math_lib.h"
namespace nasal {
var builtin_pow(var* local, gc& ngc) {
var x = local[1];
var y = local[2];
if (x.type!=vm_num || y.type!=vm_num) {
return var::num(std::nan(""));
}
return var::num(std::pow(x.num(), y.num()));
}
var builtin_sin(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? sin(val.num()):std::nan(""));
}
var builtin_cos(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? cos(val.num()):std::nan(""));
}
var builtin_tan(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? tan(val.num()):std::nan(""));
}
var builtin_exp(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? exp(val.num()):std::nan(""));
}
var builtin_lg(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? log(val.num())/log(10.0):std::nan(""));
}
var builtin_ln(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? log(val.num()):std::nan(""));
}
var builtin_sqrt(var* local, gc& ngc) {
var val = local[1];
return var::num(val.type==vm_num? sqrt(val.num()):std::nan(""));
}
var builtin_atan2(var* local, gc& ngc) {
var x = local[1];
var y = local[2];
if (x.type!=vm_num || y.type!=vm_num) {
return var::num(std::nan(""));
}
return var::num(atan2(y.num(), x.num()));
}
var builtin_isnan(var* local, gc& ngc) {
var x = local[1];
return (x.type==vm_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}
};
}

View File

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

View File

@@ -1,15 +1,11 @@
#pragma once
#ifndef __nasver
#define __nasver "11.0"
#ifndef __nasver__
#define __nasver__ "11.3.3"
#endif
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <fstream>
#include <cstring>
#include <cmath>
#include <vector>
// abbreviation of some useful basic type
using i32 = std::int32_t;
@@ -21,41 +17,13 @@ using u64 = std::uint64_t;
using usize = std::size_t;
using f64 = double;
namespace nasal {
// virtual machine stack depth, both global depth and value stack depth
const u32 VM_STACK_DEPTH = UINT16_MAX + 1;
bool is_windows();
bool is_linux();
bool is_macos();
bool is_x86();
bool is_amd64();
bool is_x86_64();
bool is_arm();
bool is_aarch64();
bool is_ia64();
bool is_powerpc();
bool is_superh();
// virtual machine stack depth
// both global depth and value stack depth
const u32 STACK_DEPTH = 4096;
f64 hex2f(const char*);
f64 oct2f(const char*);
// 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.
f64 dec2f(const char*);
f64 str2num(const char*);
i32 utf8_hdchk(const char);
std::string chrhex(const char);
std::string rawstr(const std::string&, const usize maxlen=0);
}
#include "nasal_gc.h"
// avoid error loading function bug in MSVC version nasal.exe
#ifdef _MSC_VER
// and fuck MSVC again
#define NASAL_EXPORT extern "C" __declspec(dllexport)
#else
#define NASAL_EXPORT extern "C" __attribute__((visibility("default")))
#endif

View File

@@ -7,6 +7,16 @@ 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);
}
@@ -36,7 +46,7 @@ void bool_literal::accept(ast_visitor* visitor) {
}
vector_expr::~vector_expr() {
for(auto i : elements) {
for (auto i : elements) {
delete i;
}
}
@@ -46,7 +56,7 @@ void vector_expr::accept(ast_visitor* visitor) {
}
hash_expr::~hash_expr() {
for(auto i : members) {
for (auto i : members) {
delete i;
}
}
@@ -66,7 +76,7 @@ void hash_pair::accept(ast_visitor* visitor) {
}
function::~function() {
for(auto i : parameter_list) {
for (auto i : parameter_list) {
delete i;
}
if (block) {
@@ -79,7 +89,7 @@ void function::accept(ast_visitor* visitor) {
}
code_block::~code_block() {
for(auto i : expressions) {
for (auto i : expressions) {
delete i;
}
}
@@ -147,10 +157,10 @@ void unary_operator::accept(ast_visitor* visitor) {
}
call_expr::~call_expr() {
if(first) {
if (first) {
delete first;
}
for(auto i : calls) {
for (auto i : calls) {
delete i;
}
}
@@ -163,8 +173,12 @@ 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) {
for (auto i : calls) {
delete i;
}
}
@@ -174,7 +188,7 @@ void call_vector::accept(ast_visitor* visitor) {
}
call_function::~call_function() {
for(auto i : args) {
for (auto i : args) {
delete i;
}
}
@@ -229,7 +243,7 @@ void assignment_expr::accept(ast_visitor* visitor) {
}
multi_identifier::~multi_identifier() {
for(auto i : variables) {
for (auto i : variables) {
delete i;
}
}
@@ -239,7 +253,7 @@ void multi_identifier::accept(ast_visitor* visitor) {
}
tuple_expr::~tuple_expr() {
for(auto i : elements) {
for (auto i : elements) {
delete i;
}
}
@@ -326,7 +340,7 @@ condition_expr::~condition_expr() {
if (if_stmt) {
delete if_stmt;
}
for(auto i : elsif_stmt) {
for (auto i : elsif_stmt) {
delete i;
}
if (else_stmt) {

View File

@@ -8,8 +8,9 @@
namespace nasal {
enum class expr_type:u32 {
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
@@ -22,6 +23,7 @@ enum class expr_type:u32 {
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]
@@ -46,6 +48,7 @@ enum class expr_type:u32 {
};
class ast_visitor;
class identifier;
class hash_pair;
class parameter;
class slice_vector;
@@ -63,13 +66,13 @@ public:
expr(const span& location, expr_type node_type):
nd_loc(location), nd_type(node_type) {}
virtual ~expr() = default;
void set_begin(u32 line, u32 column) {
void set_begin(u64 line, u64 column) {
nd_loc.begin_line = line;
nd_loc.begin_column = column;
}
const span& get_location() const {return nd_loc;}
const u32 get_line() const {return nd_loc.begin_line;}
expr_type get_type() const {return nd_type;}
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;
@@ -77,7 +80,20 @@ public:
virtual void accept(ast_visitor*);
};
class call:public expr {
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) {}
@@ -85,7 +101,7 @@ public:
virtual void accept(ast_visitor*);
};
class null_expr:public expr {
class null_expr: public expr {
public:
null_expr(const span& location):
expr(location, expr_type::ast_null) {}
@@ -93,7 +109,7 @@ public:
void accept(ast_visitor*) override;
};
class nil_expr:public expr {
class nil_expr: public expr {
public:
nil_expr(const span& location):
expr(location, expr_type::ast_nil) {}
@@ -101,19 +117,21 @@ public:
void accept(ast_visitor*) override;
};
class number_literal:public expr {
class number_literal: public expr {
private:
f64 number;
std::string raw_text;
public:
number_literal(const span& location, const f64 num):
expr(location, expr_type::ast_num), number(num) {}
number_literal(const span& location, const f64 num, const std::string& raw):
expr(location, expr_type::ast_num), number(num), raw_text(raw) {}
~number_literal() override = default;
f64 get_number() const {return number;}
f64 get_number() const { return number; }
const std::string& get_raw_text() const { return raw_text; }
void accept(ast_visitor*) override;
};
class string_literal:public expr {
class string_literal: public expr {
private:
std::string content;
@@ -121,11 +139,11 @@ 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;}
const std::string& get_content() const {return content;}
void accept(ast_visitor*) override;
};
class identifier:public expr {
class identifier: public expr {
private:
std::string name;
@@ -137,7 +155,7 @@ public:
void accept(ast_visitor*) override;
};
class bool_literal:public expr {
class bool_literal: public expr {
private:
bool flag;
@@ -149,7 +167,7 @@ public:
void accept(ast_visitor*) override;
};
class vector_expr:public expr {
class vector_expr: public expr {
private:
std::vector<expr*> elements;
@@ -162,7 +180,7 @@ public:
void accept(ast_visitor*) override;
};
class hash_expr:public expr {
class hash_expr: public expr {
private:
std::vector<hash_pair*> members;
@@ -175,7 +193,7 @@ public:
void accept(ast_visitor*) override;
};
class hash_pair:public expr {
class hash_pair: public expr {
private:
std::string name;
expr* value;
@@ -192,7 +210,7 @@ public:
void accept(ast_visitor*) override;
};
class function:public expr {
class function: public expr {
private:
std::vector<parameter*> parameter_list;
code_block* block;
@@ -209,7 +227,7 @@ public:
void accept(ast_visitor*) override;
};
class code_block:public expr {
class code_block: public expr {
private:
std::vector<expr*> expressions;
@@ -222,16 +240,16 @@ public:
void accept(ast_visitor*) override;
};
class parameter:public expr {
class parameter: public expr {
public:
enum class param_type {
enum class kind {
normal_parameter,
default_parameter,
dynamic_parameter
};
private:
param_type type;
kind type;
std::string name;
expr* default_value;
@@ -240,16 +258,16 @@ public:
expr(location, expr_type::ast_param),
name(""), default_value(nullptr) {}
~parameter() override;
void set_parameter_type(param_type pt) {type = pt;}
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;}
param_type get_parameter_type() {return type;}
const std::string& get_parameter_name() const {return name;}
expr* get_default_value() {return default_value;}
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 {
class ternary_operator: public expr {
private:
expr* condition;
expr* left;
@@ -269,9 +287,9 @@ public:
void accept(ast_visitor*) override;
};
class binary_operator:public expr {
class binary_operator: public expr {
public:
enum class binary_type {
enum class kind {
add,
sub,
mult,
@@ -287,11 +305,12 @@ public:
bitwise_xor,
bitwise_and,
condition_and,
condition_or
condition_or,
null_chain
};
private:
binary_type type;
kind type;
expr* left;
expr* right;
number_literal* optimized_const_number;
@@ -304,29 +323,29 @@ public:
optimized_const_number(nullptr),
optimized_const_string(nullptr) {}
~binary_operator() override;
void set_operator_type(binary_type operator_type) {type = operator_type;}
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;}
binary_type get_operator_type() const {return type;}
expr* get_left() {return left;}
expr* get_right() {return right;}
number_literal* get_optimized_number() {return optimized_const_number;}
string_literal* get_optimized_string() {return optimized_const_string;}
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 {
class unary_operator: public expr {
public:
enum class unary_type {
enum class kind {
negative,
logical_not,
bitwise_not
};
private:
unary_type type;
kind type;
expr* value;
number_literal* optimized_number;
@@ -335,16 +354,16 @@ public:
expr(location, expr_type::ast_unary),
value(nullptr), optimized_number(nullptr) {}
~unary_operator() override;
void set_operator_type(unary_type operator_type) {type = operator_type;}
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;}
unary_type get_operator_type() const {return type;}
expr* get_value() {return value;}
number_literal* get_optimized_number() {return optimized_number;}
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 {
class call_expr: public expr {
private:
expr* first;
std::vector<call*> calls;
@@ -361,7 +380,7 @@ public:
void accept(ast_visitor*) override;
};
class call_hash:public call {
class call_hash: public call {
private:
std::string field;
@@ -374,7 +393,20 @@ public:
void accept(ast_visitor*) override;
};
class call_vector:public call {
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;
@@ -387,7 +419,7 @@ public:
void accept(ast_visitor*) override;
};
class call_function:public call {
class call_function: public call {
private:
std::vector<expr*> args;
@@ -400,7 +432,7 @@ public:
void accept(ast_visitor*) override;
};
class slice_vector:public expr {
class slice_vector: public expr {
private:
expr* begin;
expr* end;
@@ -417,7 +449,7 @@ public:
void accept(ast_visitor*) override;
};
class definition_expr:public expr {
class definition_expr: public expr {
private:
identifier* variable_name;
multi_identifier* variables;
@@ -441,9 +473,9 @@ public:
void accept(ast_visitor*) override;
};
class assignment_expr:public expr {
class assignment_expr: public expr {
public:
enum class assign_type {
enum class kind {
equal,
add_equal,
sub_equal,
@@ -456,7 +488,7 @@ public:
};
private:
assign_type type;
kind type;
expr* left;
expr* right;
@@ -465,16 +497,16 @@ public:
expr(location, expr_type::ast_assign),
left(nullptr), right(nullptr) {}
~assignment_expr() override;
void set_assignment_type(assign_type operator_type) {type = operator_type;}
void set_assignment_type(kind operator_type) {type = operator_type;}
void set_left(expr* node) {left = node;}
void set_right(expr* node) {right = node;}
assign_type get_assignment_type() const {return type;}
expr* get_left() {return left;}
expr* get_right() {return right;}
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 {
class multi_identifier: public expr {
private:
std::vector<identifier*> variables;
@@ -487,7 +519,7 @@ public:
void accept(ast_visitor*) override;
};
class tuple_expr:public expr {
class tuple_expr: public expr {
private:
std::vector<expr*> elements;
@@ -500,7 +532,7 @@ public:
void accept(ast_visitor*) override;
};
class multi_assign:public expr {
class multi_assign: public expr {
private:
tuple_expr* tuple;
expr* value;
@@ -517,7 +549,7 @@ public:
void accept(ast_visitor*) override;
};
class while_expr:public expr {
class while_expr: public expr {
private:
expr* condition;
code_block* block;
@@ -534,7 +566,7 @@ public:
void accept(ast_visitor*) override;
};
class for_expr:public expr {
class for_expr: public expr {
private:
expr* initializing;
expr* condition;
@@ -558,32 +590,36 @@ public:
void accept(ast_visitor*) override;
};
class iter_expr:public expr {
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 {
class forei_expr: public expr {
public:
enum class forei_loop_type {
enum class kind {
foreach,
forindex
};
private:
forei_loop_type type;
kind type;
iter_expr* iterator;
expr* vector_node;
code_block* block;
@@ -591,21 +627,21 @@ private:
public:
forei_expr(const span& location):
expr(location, expr_type::ast_forei),
type(forei_loop_type::foreach), iterator(nullptr),
type(kind::foreach), iterator(nullptr),
vector_node(nullptr), block(nullptr) {}
~forei_expr() override;
void set_loop_type(forei_loop_type ft) {type = ft;}
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;}
forei_loop_type get_loop_type() const {return type;}
iter_expr* get_iterator() {return iterator;}
expr* get_value() {return vector_node;}
code_block* get_code_block() {return block;}
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 {
class condition_expr: public expr {
private:
if_expr* if_stmt;
std::vector<if_expr*> elsif_stmt;
@@ -625,7 +661,7 @@ public:
void accept(ast_visitor*) override;
};
class if_expr:public expr {
class if_expr: public expr {
private:
expr* condition;
code_block* block;
@@ -642,7 +678,7 @@ public:
void accept(ast_visitor*) override;
};
class continue_expr:public expr {
class continue_expr: public expr {
public:
continue_expr(const span& location):
expr(location, expr_type::ast_continue) {}
@@ -650,7 +686,7 @@ public:
void accept(ast_visitor*) override;
};
class break_expr:public expr {
class break_expr: public expr {
public:
break_expr(const span& location):
expr(location, expr_type::ast_break) {}
@@ -658,7 +694,7 @@ public:
void accept(ast_visitor*) override;
};
class return_expr:public expr {
class return_expr: public expr {
private:
expr* value;

View File

@@ -1,669 +0,0 @@
#include "nasal_builtin.h"
#include <chrono>
namespace nasal {
var builtin_print(var* local, gc& ngc) {
for(auto& i : local[1].vec().elems) {
std::cout << i;
}
std::cout << std::flush;
return nil;
}
var builtin_println(var* local, gc& ngc) {
for(auto& i : local[1].vec().elems) {
std::cout << i;
}
std::cout << std::endl;
return nil;
}
var builtin_exit(var* local, gc& ngc) {
std::exit(local[1].num());
return nil;
}
var builtin_abort(var* local, gc& ngc) {
std::abort();
return nil;
}
var builtin_append(var* local, gc& ngc) {
var vec = local[1];
var elem = local[2];
if (vec.type!=vm_vec) {
return nas_err("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(var* local, gc& ngc) {
var vec = local[1];
var size = local[2];
if (vec.type!=vm_vec) {
return nas_err("setsize", "\"vec\" must be vector");
}
if (size.type!=vm_num || size.num()<0) {
return nil;
}
vec.vec().elems.resize(static_cast<i64>(size.num()), nil);
return nil;
}
var builtin_system(var* local, gc& ngc) {
var str = local[1];
if (str.type!=vm_str) {
return var::num(-1);
}
return var::num(static_cast<f64>(system(str.str().c_str())));
}
var builtin_input(var* local, gc& ngc) {
var end = local[1];
var ret = ngc.alloc(vm_str);
if (end.type!=vm_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(var* local, gc& ngc) {
var delimeter = local[1];
var str = local[2];
if (delimeter.type!=vm_str) {
return nas_err("split", "\"separator\" must be string");
}
if (str.type!=vm_str) {
return nas_err("split", "\"str\" must be string");
}
const auto& deli = delimeter.str();
const auto& s = str.str();
// avoid being sweeped
var res = ngc.temp = ngc.alloc(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(var* local, gc& ngc) {
var val = local[1];
if (val.type!=vm_num && val.type!=vm_nil) {
return nas_err("rand", "\"seed\" must be nil or number");
}
if (val.type==vm_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(var* local, gc& ngc) {
var val = local[1];
std::stringstream ss;
ss << "0";
if (val.type>vm_num) {
ss << "x" << std::hex;
ss << reinterpret_cast<u64>(val.val.gcobj) << std::dec;
}
return ngc.newstr(ss.str());
}
var builtin_int(var* local, gc& ngc) {
var val = local[1];
if (val.type!=vm_num && val.type!=vm_str) {
return nil;
}
return var::num(static_cast<f64>(static_cast<i32>(val.tonum())));
}
var builtin_floor(var* local, gc& ngc) {
var val = local[1];
return var::num(std::floor(val.num()));
}
var builtin_num(var* local, gc& ngc) {
var val = local[1];
if (val.type==vm_num) {
return val;
}
if (val.type!=vm_str) {
return nil;
}
f64 res = val.tonum();
if (std::isnan(res)) {
return nil;
}
return var::num(res);
}
var builtin_pop(var* local, gc& ngc) {
var val = local[1];
if (val.type!=vm_vec) {
return nas_err("pop", "\"vec\" must be vector");
}
auto& vec = val.vec().elems;
if (vec.size()) {
var tmp = vec.back();
vec.pop_back();
return tmp;
}
return nil;
}
var builtin_str(var* local, gc& ngc) {
return ngc.newstr(local[1].tostr());
}
var builtin_size(var* local, gc& ngc) {
var val = local[1];
f64 num = 0;
switch(val.type) {
case vm_num: num = val.num(); break;
case vm_str: num = val.str().length(); break;
case vm_vec: num = val.vec().size(); break;
case vm_hash: num = val.hash().size(); break;
case vm_map: num = val.map().mapper.size(); break;
}
return var::num(num);
}
var builtin_time(var* local, gc& ngc) {
var val = local[1];
if (val.type!=vm_num) {
return nas_err("time", "\"begin\" must be number");
}
time_t begin = (time_t)val.num();
return var::num(static_cast<f64>(time(&begin)));
}
var builtin_contains(var* local, gc& ngc) {
var hash = local[1];
var key = local[2];
if (hash.type!=vm_hash || key.type!=vm_str) {
return zero;
}
return hash.hash().elems.count(key.str())? one:zero;
}
var builtin_delete(var* local, gc& ngc) {
var hash = local[1];
var key = local[2];
if (hash.type!=vm_hash) {
return nas_err("delete", "\"hash\" must be hash");
}
if (key.type!=vm_str) {
return nil;
}
if (hash.hash().elems.count(key.str())) {
hash.hash().elems.erase(key.str());
}
return nil;
}
var builtin_keys(var* local, gc& ngc) {
var hash = local[1];
if (hash.type!=vm_hash && hash.type!=vm_map) {
return nas_err("keys", "\"hash\" must be hash");
}
// avoid being sweeped
var 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;
}
var builtin_die(var* local, gc& ngc) {
return nas_err("error", local[1].tostr());
}
var builtin_find(var* local, gc& ngc) {
var needle = local[1];
var haystack = local[2];
usize pos = haystack.tostr().find(needle.tostr());
if (pos==std::string::npos) {
return var::num(-1.0);
}
return var::num(static_cast<f64>(pos));
}
var builtin_type(var* local, gc& ngc) {
switch(local[1].type) {
case vm_none: return ngc.newstr("undefined");
case vm_nil: return ngc.newstr("nil");
case vm_num: return ngc.newstr("num");
case vm_str: return ngc.newstr("str");
case vm_vec: return ngc.newstr("vec");
case vm_hash: return ngc.newstr("hash");
case vm_func: return ngc.newstr("func");
case vm_obj: return ngc.newstr("obj");
case vm_co: return ngc.newstr("coroutine");
case vm_map: return ngc.newstr("namespace");
}
return nil;
}
var builtin_substr(var* local, gc& ngc) {
var str = local[1];
var beg = local[2];
var len = local[3];
if (str.type!=vm_str) {
return nas_err("substr", "\"str\" must be string");
}
if (beg.type!=vm_num || beg.num()<0) {
return nas_err("substr", "\"begin\" should be number >= 0");
}
if (len.type!=vm_num || len.num()<0) {
return nas_err("substr", "\"length\" should be number >= 0");
}
usize begin = (usize)beg.num();
usize length = (usize)len.num();
if (begin>=str.str().length()) {
return nas_err("susbtr", "begin index out of range: "+std::to_string(begin));
}
return ngc.newstr(str.str().substr(begin,length));
}
var builtin_streq(var* local, gc& ngc) {
var a = local[1];
var b = local[2];
return var::num(static_cast<f64>(
(a.type!=vm_str || b.type!=vm_str)? 0:(a.str()==b.str())
));
}
var builtin_left(var* local, gc& ngc) {
var str = local[1];
var len = local[2];
if (str.type!=vm_str) {
return nas_err("left", "\"string\" must be string");
}
if (len.type!=vm_num) {
return nas_err("left", "\"length\" must be number");
}
if (len.num()<0) {
return ngc.newstr("");
}
return ngc.newstr(str.str().substr(0, len.num()));
}
var builtin_right(var* local, gc& ngc) {
var str = local[1];
var len = local[2];
if (str.type!=vm_str) {
return nas_err("right", "\"string\" must be string");
}
if (len.type!=vm_num) {
return nas_err("right", "\"length\" must be number");
}
i32 length = static_cast<i32>(len.num());
i32 srclen = 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(var* local, gc& ngc) {
var a = local[1];
var b = local[2];
if (a.type!=vm_str || b.type!=vm_str) {
return nas_err("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(var* local, gc& ngc) {
const char* extend[] = {
""," ","","ƒ","","","","",
"ˆ","","Š","","Œ"," ","Ž"," ",
" ","","","","","","","",
"˜","","š","","œ"," ","ž","Ÿ",
" ","¡","¢","£","¤","¥","¦","§",
"¨","©","ª","«","¬"," ","®","¯",
"°","±","²","³","´","µ","","·",
"¸","¹","º","»","¼","½","¾","¿",
"À","Á","Â","Ã","Ä","Å","Æ","Ç",
"È","É","Ê","Ë","Ì","Í","Î","Ï",
"Ð","Ñ","Ò","Ó","Ô","Õ","Ö","×",
"Ø","Ù","Ú","Û","Ü","Ý","Þ","ß",
"à","á","â","ã","ä","å","æ","ç",
"è","é","ê","ë","ì","í","î","ï",
"ð","ñ","ò","ó","ô","õ","ö","÷",
"ø","ù","ú","û","ü","ý","þ","ÿ"
};
i32 num = local[1].num();
if (0<=num && num<128) {
return ngc.newstr((char)num);
} else if (128<=num && num<256) {
return ngc.newstr(extend[num-128]);
}
return ngc.newstr(" ");
}
var builtin_char(var* local, gc& ngc) {
return ngc.newstr((unsigned char)local[1].num());
}
var builtin_values(var* local, gc& ngc) {
var hash = local[1];
if (hash.type!=vm_hash) {
return nas_err("values", "\"hash\" must be hash");
}
var vec = ngc.alloc(vm_vec);
auto& v = vec.vec().elems;
for(auto& i : hash.hash().elems) {
v.push_back(i.second);
}
return vec;
}
var builtin_sleep(var* local, gc& ngc) {
var val = local[1];
if (val.type!=vm_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(var* local, gc& ngc) {
if (is_windows()) {
return ngc.newstr("windows");
} else if (is_linux()) {
return ngc.newstr("linux");
} else if (is_macos()) {
return ngc.newstr("macOS");
}
return ngc.newstr("unknown");
}
var builtin_arch(var* local, gc& ngc) {
if (is_x86()) {
return ngc.newstr("x86");
} else if (is_x86_64()) {
return ngc.newstr("x86-64");
} else if (is_amd64()) {
return ngc.newstr("amd64");
} else if (is_arm()) {
return ngc.newstr("arm");
} else if (is_aarch64()) {
return ngc.newstr("aarch64");
} else if (is_ia64()) {
return ngc.newstr("ia64");
} else if (is_powerpc()) {
return ngc.newstr("powerpc");
} else if (is_superh()) {
return ngc.newstr("superh");
}
return ngc.newstr("unknown");
}
// 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(var* local, gc& ngc) {
var str = local[1];
if (str.type!=vm_str) {
return nas_err("md5", "\"str\" must be string");
}
return ngc.newstr(md5(str.str()));
}
var builtin_millisec(var* local, gc& ngc) {
f64 res = std::chrono::duration_cast<std::chrono::milliseconds>
(std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
return var::num(res);
}
var builtin_gcextend(var* local, gc& ngc) {
var type = local[1];
if (type.type!=vm_str) {
return nil;
}
auto& s = type.str();
if (s=="str") {
ngc.extend(vm_str);
} else if (s=="vec") {
ngc.extend(vm_vec);
} else if (s=="hash") {
ngc.extend(vm_hash);
} else if (s=="func") {
ngc.extend(vm_func);
} else if (s=="upval") {
ngc.extend(vm_upval);
} else if (s=="obj") {
ngc.extend(vm_obj);
} else if (s=="co") {
ngc.extend(vm_co);
}
return nil;
}
var builtin_gcinfo(var* local, gc& ngc) {
auto den = std::chrono::high_resolution_clock::duration::period::den;
var res = ngc.alloc(vm_hash);
double total = 0;
for(u32 i = 0; i<gc_type_size; ++i) {
total += ngc.gcnt[i];
}
// using ms
auto& map = res.hash().elems;
map["total"] = var::num(ngc.worktime*1.0/den*1000);
map["average"] = var::num(ngc.worktime*1.0/den*1000/total);
map["max_gc"] = var::num(ngc.max_time*1.0/den*1000);
map["max_mark"] = var::num(ngc.max_mark_time*1.0/den*1000);
map["max_sweep"] = var::num(ngc.max_sweep_time*1.0/den*1000);
return res;
}
var builtin_logtime(var* local, 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(var* local, gc& ngc) {
var arg = local[1];
if (arg.type!=vm_obj) {
return nas_err("ghosttype", "this is not a ghost object.");
}
const auto& name = arg.obj().get_ghost_name();
if (!name.length()) {
return var::num(reinterpret_cast<u64>(arg.obj().ptr));
}
return ngc.newstr(name);
}
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},
{"__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},
{"__millisec", builtin_millisec},
{"__gcextd", builtin_gcextend},
{"__gcinfo", builtin_gcinfo},
{"__logtime", builtin_logtime},
{"__ghosttype", builtin_ghosttype},
{nullptr, nullptr}
};
}

View File

@@ -1,81 +0,0 @@
#pragma once
#include "nasal.h"
#include "nasal_gc.h"
#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_print(var*, gc&);
var builtin_println(var*, gc&);
var builtin_exit(var*, gc&);
var builtin_abort(var*, gc&);
var builtin_append(var*, gc&);
var builtin_setsize(var*, gc&);
var builtin_system(var*, gc&);
var builtin_input(var*, gc&);
var builtin_split(var*, gc&);
var builtin_rand(var*, gc&);
var builtin_id(var*, gc&);
var builtin_int(var*, gc&);
var builtin_floor(var*, gc&);
var builtin_num(var*, gc&);
var builtin_pop(var*, gc&);
var builtin_str(var*, gc&);
var builtin_size(var*, gc&);
var builtin_time(var*, gc&);
var builtin_contains(var*, gc&);
var builtin_delete(var*, gc&);
var builtin_keys(var*, gc&);
var builtin_die(var*, gc&);
var builtin_find(var*, gc&);
var builtin_type(var*, gc&);
var builtin_substr(var*, gc&);
var builtin_streq(var*, gc&);
var builtin_left(var*, gc&);
var builtin_right(var*, gc&);
var builtin_cmp(var*, gc&);
var builtin_chr(var*, gc&);
var builtin_char(var*, gc&);
var builtin_values(var*, gc&);
var builtin_sleep(var*, gc&);
var builtin_platform(var*, gc&);
var builtin_arch(var*, gc&);
// md5 related functions
std::string tohex(u32);
std::string md5(const std::string&);
var builtin_md5(var*, gc&);
var builtin_millisec(var*, gc&);
var builtin_gcextend(var*, gc&);
var builtin_gcinfo(var*, gc&);
var builtin_logtime(var*, gc&);
var builtin_ghosttype(var*, 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)(var*, gc&);
};
extern nasal_builtin_table builtin[];
}

File diff suppressed because it is too large Load Diff

View File

@@ -8,14 +8,17 @@
#include "nasal_parse.h"
#include "nasal_import.h"
#include "nasal_builtin.h"
#include "coroutine.h"
#include "bits_lib.h"
#include "math_lib.h"
#include "fg_props.h"
#include "io_lib.h"
#include "dylib_lib.h"
#include "unix_lib.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 "natives/subprocess.h"
#include <iomanip>
#include <list>
@@ -35,7 +38,31 @@ private:
error err;
// repl output flag, will generate op_repl to output stack top value if true
bool need_repl_output;
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", "__terminal_size",
// io
"__fout", "__open", "__write", "__stat"
// bits
"__fld", "__sfld", "__setfld",
"__buf",
// fg
"__logprint",
// dylib
"__dlopen", "__dlclose", "__dlcallv", "__dlcall",
// unix
"__chdir", "__environ", "__getcwd", "__getenv",
// subprocess
"__subprocess_create",
"__subprocess_active",
"__subprocess_terminate"
};
// file mapper for file -> index
std::unordered_map<std::string, usize> file_map;
@@ -45,8 +72,8 @@ private:
std::vector<u32> in_foreach_loop_level;
// constant numbers and strings
std::unordered_map<f64, u32> const_number_map;
std::unordered_map<std::string, u32> const_string_map;
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;
@@ -60,52 +87,56 @@ private:
std::vector<opcode> code;
// used to store jmp operands index, to fill the jump address back
std::list<std::vector<i32>> continue_ptr;
std::list<std::vector<i32>> break_ptr;
std::list<std::vector<u64>> continue_ptr;
std::list<std::vector<u64>> break_ptr;
// symbol table
// global : max STACK_DEPTH-1 values
std::unordered_map<std::string, i32> global;
std::unordered_map<std::string, std::unordered_set<std::string>> experimental_namespace;
// 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 STACK_DEPTH value
std::list<std::unordered_map<std::string, i32>> local;
// 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 die(const std::string& info, expr* node) {
err.err("code", node->get_location(), info);
}
void regist_num(const f64);
void regist_str(const std::string&);
void regist_number(const f64);
void regist_string(const std::string&);
void find_symbol(code_block*);
void add_symbol(const std::string&);
i32 local_symbol_find(const std::string&);
i32 global_symbol_find(const std::string&);
i32 upvalue_symbol_find(const std::string&);
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, u32, const span&);
void emit(u8, u64, const span&);
void num_gen(number_literal*);
void str_gen(string_literal*);
void number_gen(number_literal*);
void string_gen(string_literal*);
void bool_gen(bool_literal*);
void vec_gen(vector_expr*);
void vector_gen(vector_expr*);
void hash_gen(hash_expr*);
void func_gen(function*);
void call_gen(call_expr*);
void call_id(identifier*);
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_id(identifier*);
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 def_gen(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&);
@@ -113,7 +144,7 @@ private:
void multi_assign_gen(multi_assign*);
void cond_gen(condition_expr*);
void loop_gen(expr*);
void load_continue_break(i32, i32);
void load_continue_break(u64, u64);
void while_gen(while_expr*);
void for_gen(for_expr*);
void forei_gen(forei_expr*);
@@ -122,6 +153,7 @@ private:
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*);
@@ -129,18 +161,15 @@ private:
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;}
const auto& get_experimental_namespace() const {
return experimental_namespace;
}
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);
const error& compile(parse&, linker&, bool, bool);
void print(std::ostream&);
void symbol_dump(std::ostream&) const;
};

View File

@@ -2,19 +2,19 @@
namespace nasal {
void debug_prof_data::init_counter() {
for(usize i = 0; i<debug_prof_data::operand_size; ++i) {
void operand_line_counter::init_counter() {
for (usize i = 0; i<operand_line_counter::operand_size; ++i) {
operand_counter[i] = 0;
}
}
void debug_prof_data::load_file_line_counter(
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 = {};
flstream fs;
for(usize i =0; i<file_list.size(); ++i) {
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({});
@@ -22,16 +22,16 @@ void debug_prof_data::load_file_line_counter(
}
}
void debug_prof_data::init(const std::vector<std::string>& file_list) {
void operand_line_counter::init(const std::vector<std::string>& file_list) {
init_counter();
load_file_line_counter(file_list);
}
void debug_prof_data::dump_counter() const {
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<debug_prof_data::operand_size; ++i) {
for (usize i = 0; i<operand_line_counter::operand_size; ++i) {
total += operand_counter[i];
opcall.push_back({i, operand_counter[i]});
}
@@ -41,30 +41,31 @@ void debug_prof_data::dump_counter() const {
}
);
std::clog << "\noperands call info (<1% ignored)\n";
for(const auto& i : opcall) {
for (const auto& i : opcall) {
u64 rate = i.second*100/total;
if (!rate) {
break;
}
std::clog << " " << opname[i.first] << " : ";
std::clog << i.second << " (" << rate << "%)\n";
std::clog << " ";
std::clog << operand_name_table.at(static_cast<opcode_type>(i.first));
std::clog << " : " << i.second << " (" << rate << "%)\n";
}
std::clog << " total : " << total << '\n';
}
void debug_prof_data::dump_code_line_counter(std::ostream& os) const {
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) {
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) {
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) {
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]));
@@ -73,9 +74,9 @@ void debug_prof_data::dump_code_line_counter(std::ostream& os) const {
}
}
void debug_prof_data::dump_this_file_line_counter(std::ostream& os) const {
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]) {
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();
@@ -83,7 +84,7 @@ void debug_prof_data::dump_this_file_line_counter(std::ostream& os) const {
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) {
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]));
@@ -94,7 +95,7 @@ void debug_prof_data::dump_this_file_line_counter(std::ostream& os) const {
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) {
while (pos!=std::string::npos) {
if (pos>last) {
res.push_back(cmd.substr(last, pos-last));
}
@@ -108,21 +109,21 @@ std::vector<std::string> dbg::parse(const std::string& cmd) {
}
u16 dbg::file_index(const std::string& filename) const {
for(u16 i = 0; i<fsize; ++i) {
for (u16 i = 0; i<file_list_size; ++i) {
if (filename==files[i]) {
return i;
}
}
return 65535;
return UINT16_MAX;
}
void dbg::err() {
void dbg::err() const {
std::cerr
<< "incorrect command\n"
<< "input \'h\' to get help\n";
}
void dbg::help() {
void dbg::help() const {
std::clog
<< "<option>\n"
<< " h, help | get help\n"
@@ -133,7 +134,7 @@ void dbg::help() {
<< " 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"
<< " a, all | show global, local and upvalue\n"
<< " n, next | execute next bytecode\n"
<< " q, exit | exit debugger\n"
<< "<option> <filename> <line>\n"
@@ -141,34 +142,44 @@ void dbg::help() {
}
void dbg::list_file() const {
for(usize i = 0; i<fsize; ++i) {
for (usize i = 0; i<file_list_size; ++i) {
std::clog << "[" << i << "] " << files[i] << "\n";
}
}
void dbg::step_info() {
u32 line = bytecode[ctx.pc].line==0? 0:bytecode[ctx.pc].line-1;
u32 begin = (line>>3)==0? 0:((line>>3)<<3);
u32 end = (1+(line>>3))<<3;
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 << clear_screen << set_cursor;
std::clog << "\nsource code:\n";
for(u32 i = begin; i<end && i<src.size(); ++i) {
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(cnum, cstr, native.data(), files);
std::clog << "next bytecode:\n";
for(u32 i = begin; i<end && bytecode[i].op!=op_exit; ++i) {
codestream::set(
const_number,
const_string,
global_symbol_name,
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";
}
stackinfo(10);
stack_info(16);
}
void dbg::interact() {
@@ -178,44 +189,49 @@ void dbg::interact() {
}
// do not need interact while doing profiling
if (do_profiling) {
if (do_operand_count) {
return;
}
if ((bytecode[ctx.pc].fidx!=bk_fidx ||
bytecode[ctx.pc].line!=bk_line) && // break point
!next) {// next step
// 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) {
while (true) {
std::clog << ">> ";
std::getline(std::cin, cmd);
auto res=parse(cmd);
auto res = parse(cmd);
if (res.size()==0) {
step_info();
// enter key without input using cmd_next by default
next = true;
return;
} else if (res.size()==1) {
switch(get_cmd_type(res[0])) {
case dbg_cmd::cmd_help: help(); break;
case dbg_cmd::cmd_backtrace: traceback(); break;
case dbg_cmd::cmd_continue: return;
case dbg_cmd::cmd_list_file: list_file(); break;
case dbg_cmd::cmd_global: gstate(); break;
case dbg_cmd::cmd_local: lstate(); break;
case dbg_cmd::cmd_upval: ustate(); break;
case dbg_cmd::cmd_register: reginfo(); break;
case dbg_cmd::cmd_show_all: detail(); break;
case dbg_cmd::cmd_next: next = true; return;
case dbg_cmd::cmd_exit: std::exit(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])==dbg_cmd::cmd_break_point) {
bk_fidx = file_index(res[1]);
if (bk_fidx==65535) {
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;
}
@@ -223,7 +239,7 @@ void dbg::interact() {
if (tmp<=0) {
std::clog << "incorrect line number `" << res[2] << "`\n";
} else {
bk_line = tmp;
break_line = tmp;
}
} else {
err();
@@ -231,36 +247,42 @@ void dbg::interact() {
}
}
void dbg::run(
const codegen& gen,
const linker& linker,
const std::vector<std::string>& argv,
bool profile,
bool show_all_prof_result) {
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_profiling = profile || show_all_prof_result;
do_operand_count = profile || show_all_prof_result;
const auto& file_list = linker.get_file_list();
fsize = file_list.size();
init(gen.strs(), gen.nums(), gen.natives(),
gen.codes(), gen.globals(),
file_list, argv);
data.init(file_list);
file_list_size = file_list.size();
std::vector<u32> code;
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<u32> code_line;
for(auto& i : gen.codes()) {
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]]) {
while (operand_function[code[ctx.pc]]) {
interact();
data.add_operand_counter(code[ctx.pc]);
data.add_code_line_counter(code_file_index[ctx.pc], code_line[ctx.pc]);
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");
@@ -268,13 +290,13 @@ void dbg::run(
++ctx.pc;
}
data.dump_counter();
if (do_profiling) {
counter.dump_operand_count();
if (do_operand_count) {
show_all_prof_result?
data.dump_code_line_counter(std::clog):
data.dump_this_file_line_counter(std::clog);
counter.dump_all_code_line_counter(std::clog):
counter.dump_this_file_line_counter(std::clog);
}
ngc.info();
ngc.status.dump_info();
ngc.clear();
imm.clear();
return;

View File

@@ -11,9 +11,11 @@
namespace nasal {
class debug_prof_data {
// 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;
static const usize operand_size = opcode_type::op_ret + 1;
u64 operand_counter[operand_size];
std::vector<std::string> file_name_list;
std::vector<std::vector<u64>> file_line_counter;
@@ -25,8 +27,8 @@ private:
public:
void init(const std::vector<std::string>&);
void dump_counter() const;
void dump_code_line_counter(std::ostream&) const;
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;
@@ -37,58 +39,9 @@ public:
}
};
class dbg:public vm {
class dbg: public vm {
private:
typedef void (dbg::*nasal_vm_func)();
const nasal_vm_func operand_function[op_ret + 1] = {
nullptr, &dbg::o_repl,
&dbg::o_intl, &dbg::o_loadg,
&dbg::o_loadl, &dbg::o_loadu,
&dbg::o_pnum, &dbg::o_pnil,
&dbg::o_pstr, &dbg::o_newv,
&dbg::o_newh, &dbg::o_newf,
&dbg::o_happ, &dbg::o_para,
&dbg::o_deft, &dbg::o_dyn,
&dbg::o_lnot, &dbg::o_usub,
&dbg::o_bnot, &dbg::o_btor,
&dbg::o_btxor, &dbg::o_btand,
&dbg::o_add, &dbg::o_sub,
&dbg::o_mul, &dbg::o_div,
&dbg::o_lnk, &dbg::o_addc,
&dbg::o_subc, &dbg::o_mulc,
&dbg::o_divc, &dbg::o_lnkc,
&dbg::o_addeq, &dbg::o_subeq,
&dbg::o_muleq, &dbg::o_diveq,
&dbg::o_lnkeq, &dbg::o_bandeq,
&dbg::o_boreq, &dbg::o_bxoreq,
&dbg::o_addeqc, &dbg::o_subeqc,
&dbg::o_muleqc, &dbg::o_diveqc,
&dbg::o_lnkeqc, &dbg::o_addecp,
&dbg::o_subecp, &dbg::o_mulecp,
&dbg::o_divecp, &dbg::o_lnkecp,
&dbg::o_meq, &dbg::o_eq,
&dbg::o_neq, &dbg::o_less,
&dbg::o_leq, &dbg::o_grt,
&dbg::o_geq, &dbg::o_lessc,
&dbg::o_leqc, &dbg::o_grtc,
&dbg::o_geqc, &dbg::o_pop,
&dbg::o_jmp, &dbg::o_jt,
&dbg::o_jf, &dbg::o_cnt,
&dbg::o_findex, &dbg::o_feach,
&dbg::o_callg, &dbg::o_calll,
&dbg::o_upval, &dbg::o_callv,
&dbg::o_callvi, &dbg::o_callh,
&dbg::o_callfv, &dbg::o_callfh,
&dbg::o_callb, &dbg::o_slcbeg,
&dbg::o_slcend, &dbg::o_slc,
&dbg::o_slc2, &dbg::o_mcallg,
&dbg::o_mcalll, &dbg::o_mupval,
&dbg::o_mcallv, &dbg::o_mcallh,
&dbg::o_ret
};
private:
enum class dbg_cmd {
enum class cmd_kind {
cmd_error,
cmd_help,
cmd_backtrace,
@@ -105,69 +58,67 @@ private:
};
private:
const std::unordered_map<std::string, dbg_cmd> command_table = {
{"h", dbg_cmd::cmd_help},
{"help", dbg_cmd::cmd_help},
{"bt", dbg_cmd::cmd_backtrace},
{"backtrace", dbg_cmd::cmd_backtrace},
{"c", dbg_cmd::cmd_continue},
{"continue", dbg_cmd::cmd_continue},
{"f", dbg_cmd::cmd_list_file},
{"file", dbg_cmd::cmd_list_file},
{"g", dbg_cmd::cmd_global},
{"global", dbg_cmd::cmd_global},
{"l", dbg_cmd::cmd_local},
{"local", dbg_cmd::cmd_local},
{"u", dbg_cmd::cmd_upval},
{"upval", dbg_cmd::cmd_upval},
{"r", dbg_cmd::cmd_register},
{"register", dbg_cmd::cmd_register},
{"a", dbg_cmd::cmd_show_all},
{"all", dbg_cmd::cmd_show_all},
{"n", dbg_cmd::cmd_next},
{"next", dbg_cmd::cmd_next},
{"bk", dbg_cmd::cmd_break_point},
{"break", dbg_cmd::cmd_break_point},
{"q", dbg_cmd::cmd_exit},
{"exit", dbg_cmd::cmd_exit}
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}
};
dbg_cmd get_cmd_type(const std::string& cmd) const {
return command_table.count(cmd)?
command_table.at(cmd):dbg_cmd::cmd_error;
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 fsize;
u16 bk_fidx;
u32 bk_line;
usize file_list_size;
u16 break_file_index;
u64 break_line;
error src;
private:
debug_prof_data data;
bool do_profiling;
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();
void help();
void err() const;
void help() const;
void list_file() const;
void step_info();
void interact();
public:
dbg():
next(false), fsize(0),
bk_fidx(0), bk_line(0),
do_profiling(false) {}
void run(
const codegen&,
const linker&,
const std::vector<std::string>&,
bool,
bool
);
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);
};
}

View File

@@ -1,5 +1,5 @@
#include "nasal_err.h"
#include "repl.h"
#include "repl/repl.h"
namespace nasal {
@@ -10,13 +10,56 @@ struct for_reset {
for_reset() {
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &scr);
}
static for_reset* singleton() {
static for_reset windows_set;
return &windows_set;
}
};
static for_reset windows_system_set;
#endif
std::ostream& clear_screen(std::ostream& s) {
#ifdef _WIN32
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE) {
return s;
}
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
return s;
}
auto rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
auto cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
DWORD dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
COORD coord = { 0, 0 };
DWORD dwCharsWritten;
FillConsoleOutputCharacter(hConsole, ' ', dwConSize, coord, &dwCharsWritten);
// set raw attribute
FillConsoleOutputAttribute(
hConsole,
csbi.wAttributes,
dwConSize,
coord,
&dwCharsWritten
);
// set cursor position
SetConsoleCursorPosition(hConsole, coord);
#else
s << "\033c";
#endif
return s;
}
std::ostream& set_cursor(std::ostream& s) {
#ifdef _WIN32
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), {0, 0});
#else
s << "\033[0;0H";
#endif
return s;
}
std::ostream& back_white(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xf0);
@@ -64,15 +107,17 @@ std::ostream& white(std::ostream& s) {
std::ostream& reset(std::ostream& s) {
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
for_reset::singleton()->scr.wAttributes);
SetConsoleTextAttribute(
GetStdHandle(STD_OUTPUT_HANDLE),
windows_system_set.scr.wAttributes
);
#else
s << "\033[0m";
#endif
return s;
}
void flstream::load(const std::string& f) {
void filestream::load(const std::string& f) {
if (file==f) { // don't need to load a loaded file
return;
}
@@ -105,7 +150,7 @@ void flstream::load(const std::string& f) {
std::exit(1);
}
while(!in.eof()) {
while (!in.eof()) {
std::string line;
std::getline(in, line);
res.push_back(line);
@@ -121,22 +166,23 @@ 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) {
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" << cyan << " --> "
<< red << loc.file << ":" << loc.begin_line << ":" << loc.begin_column+1
<< reset << "\n";
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(u32 line = loc.begin_line; line<=loc.end_line; ++line) {
for (u64 line = loc.begin_line; line<=loc.end_line; ++line) {
// skip line 0
if (!line) {
continue;
@@ -165,25 +211,25 @@ void error::err(
// output underline
std::cerr << cyan << iden << " | " << reset;
if (loc.begin_line==loc.end_line) {
for(u32 i = 0; i<loc.begin_column; ++i) {
for (u64 i = 0; i<loc.begin_column; ++i) {
std::cerr << char(" \t"[code[i]=='\t']);
}
for(u32 i = loc.begin_column; i<loc.end_column; ++i) {
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(u32 i = 0; i<loc.begin_column; ++i) {
for (u64 i = 0; i<loc.begin_column; ++i) {
std::cerr << char(" \t"[code[i]=='\t']);
}
for(u32 i = loc.begin_column; i<code.size(); ++i) {
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(u32 i = 0; i<code.size(); ++i) {
for (u64 i = 0; i<code.size(); ++i) {
std::cerr << red << (code[i]=='\t'? "^^^^":"^");
}
} else {
for(u32 i = 0; i<loc.end_column; ++i) {
for (u64 i = 0; i<loc.end_column; ++i) {
std::cerr << red << (code[i]=='\t'? "^^^^":"^");
}
}

View File

@@ -11,13 +11,19 @@
namespace nasal {
struct span {
u32 begin_line;
u32 begin_column;
u32 end_line;
u32 end_column;
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& clear_screen(std::ostream&);
std::ostream& set_cursor(std::ostream&);
std::ostream& back_white(std::ostream&);
std::ostream& red(std::ostream&);
std::ostream& cyan(std::ostream&);
@@ -25,13 +31,13 @@ std::ostream& orange(std::ostream&);
std::ostream& white(std::ostream&);
std::ostream& reset(std::ostream&);
class flstream {
class filestream {
protected:
std::string file;
std::vector<std::string> res;
public:
flstream():file("") {}
filestream(): file("") {}
void load(const std::string&);
const std::string& operator[](usize n) const {return res[n];}
const auto& name() const {return file;}
@@ -39,23 +45,23 @@ public:
usize size() const {return res.size();}
};
class error:public flstream {
class error: public filestream {
private:
u32 cnt; // counter for errors
std::string identation(usize len) {
return std::string(len,' ');
return std::string(len, ' ');
}
std::string leftpad(u32 num, usize len) {
std::string leftpad(u64 num, usize len) {
auto tmp = std::to_string(num);
while(tmp.length()<len) {
tmp=" "+tmp;
while (tmp.length()<len) {
tmp = " "+tmp;
}
return tmp;
}
public:
error():cnt(0) {}
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&);
@@ -65,7 +71,7 @@ public:
std::exit(1);
}
}
u32 geterr() const {return cnt;}
auto geterr() const { return cnt; }
};
}

View File

@@ -1,371 +1,38 @@
#include "nasal_gc.h"
#include "nasal_gc.h"
namespace nasal {
var nas_vec::get_val(const i32 n) {
i32 size = elems.size();
if (n<-size || n>=size) {
return var::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 std::string& key) {
if (elems.count(key)) {
return elems.at(key);
} else if (!elems.count("parents")) {
return var::none();
}
var ret = var::none();
var val = elems.at("parents");
if (val.type!=vm_vec) {
return ret;
}
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 ret;
}
var* nas_hash::get_mem(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.type!=vm_vec) {
return addr;
}
for(auto& i : val.vec().elems) {
if (i.type==vm_hash) {
addr = i.hash().get_mem(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;
}
void nas_func::clear() {
dpara = -1;
local.clear();
upval.clear();
keys.clear();
}
void nas_ghost::set(
const std::string& ghost_type_name,
destructor destructor_pointer,
void* ghost_pointer) {
type_name = ghost_type_name;
dtor_ptr = destructor_pointer;
ptr = ghost_pointer;
}
void nas_ghost::clear() {
// do nothing if pointer is null
if (!ptr) {
return;
}
// do clear pointer if destructor function pointer is null
if (!dtor_ptr) {
type_name = "";
ptr = nullptr;
return;
}
// do destruction
dtor_ptr(ptr);
type_name = "";
ptr = nullptr;
dtor_ptr = 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.ptr) << std::dec << ">";
return out;
}
void nas_co::clear() {
if (!ctx.stack) {
return;
}
for(u32 i = 0; i<STACK_DEPTH; ++i) {
ctx.stack[i] = var::nil();
}
ctx.pc = 0;
ctx.localr = nullptr;
ctx.memr = nullptr;
ctx.canary = ctx.stack+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_val(const std::string& key) {
if (mapper.count(key)) {
return *mapper.at(key);
}
return var::none();
}
var* nas_map::get_mem(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(u8 val_type) {
mark = gc_status::collected;
type = val_type;
unmut = 0;
switch(val_type) {
case vm_str: ptr.str = new std::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_ghost; break;
case vm_co: ptr.co = new nas_co; break;
case vm_map: ptr.map = new nas_map; 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;
case vm_map: delete ptr.map; 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;
case vm_map: ptr.map->clear(); break;
}
}
f64 var::tonum() {
return type!=vm_str? val.num:str2num(str().c_str());
}
std::string var::tostr() {
if (type==vm_str) {
return str();
} else if (type==vm_num) {
std::string 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;
}
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 << ref.obj(); break;
case vm_co: out << ref.co(); break;
case vm_map: out << ref.map(); break;
}
return out;
}
bool var::objchk(const std::string& name) {
return type==vm_obj && obj().type_name==name && obj().ptr;
}
var var::none() {
return {vm_none, static_cast<u32>(0)};
}
var var::nil() {
return {vm_nil, static_cast<u32>(0)};
}
var var::ret(u32 pc) {
return {vm_ret, pc};
}
var var::cnt(i64 n) {
return {vm_cnt, n};
}
var var::num(f64 n) {
return {vm_num, n};
}
var var::gcobj(nas_val* p) {
return {p->type, p};
}
var var::addr(var* p) {
return {vm_addr, p};
}
var* var::addr() {
return val.addr;
}
u32 var::ret() {
return val.ret;
}
i64& var::cnt() {
return val.cnt;
}
f64 var::num() {
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::obj() {
return *val.gcobj->ptr.obj;
}
nas_co& var::co() {
return *val.gcobj->ptr.co;
}
nas_map& var::map() {
return *val.gcobj->ptr.map;
}
void gc::do_mark_sweep() {
using clk = std::chrono::high_resolution_clock;
auto begin = clk::now();
count_mark_time();
count_sweep_time();
}
void gc::count_mark_time() {
if (in_incremental_sweep_stage) {
return;
}
status.stamp();
mark();
auto mark_end = clk::now();
status.elapsed_mark_time();
in_incremental_sweep_stage = true;
current_sweep_index = memory.size() - 1;
}
void gc::count_sweep_time() {
status.stamp();
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;
status.elapsed_sweep_time();
}
void gc::mark() {
std::vector<var> bfs;
mark_context_root(bfs);
if (memory.size()>8192 && bfs.size()>4) {
usize size = bfs.size();
// concurrent mark
if (memory.size() > UINT16_MAX * 16 && bfs.size() > 16) {
auto 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);
@@ -376,11 +43,12 @@ void gc::mark() {
t3.join();
return;
}
while(!bfs.empty()) {
// normal mark
while (!bfs.empty()) {
var value = bfs.back();
bfs.pop_back();
if (value.type<=vm_num ||
if (value.type<=vm_type::vm_num ||
value.val.gcobj->mark!=nas_val::gc_status::uncollected) {
continue;
}
@@ -390,18 +58,18 @@ void gc::mark() {
void gc::concurrent_mark(std::vector<var>& vec, usize begin, usize end) {
std::vector<var> bfs;
for(auto i = begin; i<end; ++i) {
for (auto i = begin; i<end; ++i) {
var value = vec[i];
if (value.type<=vm_num ||
if (value.type<=vm_type::vm_num ||
value.val.gcobj->mark!=nas_val::gc_status::uncollected) {
continue;
}
mark_var(bfs, value);
}
while(!bfs.empty()) {
while (!bfs.empty()) {
var value = bfs.back();
bfs.pop_back();
if (value.type<=vm_num ||
if (value.type<=vm_type::vm_num ||
value.val.gcobj->mark!=nas_val::gc_status::uncollected) {
continue;
}
@@ -411,20 +79,20 @@ void gc::concurrent_mark(std::vector<var>& vec, usize begin, usize end) {
void gc::mark_context_root(std::vector<var>& bfs_queue) {
// scan global
for(usize i = 0; i<main_context_global_size; ++i) {
for (usize i = 0; i < main_context_global_size; ++i) {
auto& val = main_context_global[i];
if (val.type>vm_num) {
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 = rctx->stack; i<=rctx->top; ++i) {
if (i->type>vm_num) {
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(rctx->funcr);
bfs_queue.push_back(rctx->upvalr);
bfs_queue.push_back(running_context->funcr);
bfs_queue.push_back(running_context->upvalr);
bfs_queue.push_back(temp);
if (!cort) {
@@ -432,98 +100,118 @@ void gc::mark_context_root(std::vector<var>& bfs_queue) {
}
// coroutine is running, so scan main process stack from mctx
for(var* i = mctx.stack; i<=mctx.top; ++i) {
if (i->type>vm_num) {
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(mctx.funcr);
bfs_queue.push_back(mctx.upvalr);
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_vec: mark_vec(bfs_queue, value.vec()); break;
case vm_hash: mark_hash(bfs_queue, value.hash()); break;
case vm_func: mark_func(bfs_queue, value.func()); break;
case vm_upval: mark_upval(bfs_queue, value.upval()); break;
case vm_co: mark_co(bfs_queue, value.co()); break;
case vm_map: mark_map(bfs_queue, value.map()); break;
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_num) {
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_num) {
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_num) {
for (auto& i : function.local) {
if (i.type > vm_type::vm_num) {
bfs_queue.push_back(i);
}
}
for(auto& i : function.upval) {
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_num) {
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_num) {
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_num) {
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 threshold is too small, too many allocated objects will be marked as "found"
// objects with "found" will be marked to "uncollected" in the next gc cycle
// this will cause memory wasting.
const i64 threshold = 4096;
for (i64 it = 0; it < threshold; ++it) {
if (current_sweep_index - it < 0) {
break;
}
auto i = memory[current_sweep_index - it];
if (i->mark==nas_val::gc_status::uncollected) {
i->clear();
unused[i->type-vm_str].push_back(i);
unused[static_cast<u32>(i->type)-static_cast<u32>(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;
}
}
current_sweep_index -= threshold;
if (current_sweep_index < 0) {
in_incremental_sweep_stage = false;
current_sweep_index = 0;
}
}
void gc::extend(u8 type) {
const u8 index = type-vm_str;
size[index] += incr[index];
void gc::extend(const vm_type type) {
const u32 index = static_cast<u32>(type)-static_cast<u32>(vm_type::vm_str);
status.object_size[index] += incr[index];
for(u32 i = 0; i<incr[index]; ++i) {
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);
@@ -531,202 +219,143 @@ void gc::extend(u8 type) {
memory.push_back(tmp);
unused[index].push_back(tmp);
}
switch(type) {
case vm_type::vm_str:
total_object_count += incr[index] * sizeof(std::string); break;
case vm_type::vm_vec:
total_object_count += incr[index] * sizeof(nas_vec); break;
case vm_type::vm_hash:
total_object_count += incr[index] * sizeof(nas_hash); break;
case vm_type::vm_func:
total_object_count += incr[index] * sizeof(nas_func); break;
case vm_type::vm_upval:
total_object_count += incr[index] * sizeof(nas_upval); break;
case vm_type::vm_ghost:
total_object_count += incr[index] * sizeof(nas_ghost); break;
case vm_type::vm_co:
total_object_count += incr[index] * sizeof(nas_co); break;
case vm_type::vm_map:
total_object_count += incr[index] * sizeof(nas_map); break;
default: break;
}
incr[index] = incr[index]+incr[index]/2;
// if incr[index] = 1, this will always be 1
incr[index] = incr[index] + incr[index];
}
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;
}
void gc::init(const std::vector<std::string>& constant_strings,
const std::vector<std::string>& argv) {
// initialize gc status recorder
status.init();
// coroutine pointer set to nullptr
cort = nullptr;
// init constant strings
strs.resize(constant_strings.size());
for(u32 i = 0; i<strs.size(); ++i) {
for (u64 i = 0; i < strs.size(); ++i) {
// incremental initialization, avoid memory leak in repl mode
if (strs[i].type==vm_str && strs[i].str()==constant_strings[i]) {
if (strs[i].is_str() && strs[i].str()==constant_strings[i]) {
continue;
}
strs[i] = var::gcobj(new nas_val(vm_str));
strs[i].val.gcobj->unmut = 1;
strs[i] = var::gcobj(new nas_val(vm_type::vm_str));
strs[i].val.gcobj->immutable = 1;
strs[i].str() = constant_strings[i];
total_object_count += strs[i].str().size();
total_object_count += sizeof(std::string);
}
// record arguments
env_argv.resize(argv.size());
for(usize i = 0; i<argv.size(); ++i) {
for (u64 i = 0; i < argv.size(); ++i) {
// incremental initialization, avoid memory leak in repl mode
if (env_argv[i].type==vm_str && env_argv[i].str()==argv[i]) {
if (env_argv[i].is_str() && env_argv[i].str() == argv[i]) {
continue;
}
env_argv[i] = var::gcobj(new nas_val(vm_str));
env_argv[i].val.gcobj->unmut = 1;
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];
total_object_count += env_argv[i].str().size();
total_object_count += sizeof(std::string);
}
}
void gc::clear() {
for(auto i : memory) {
for (auto i : memory) {
delete i;
}
memory.clear();
for(u8 i = 0; i<gc_type_size; ++i) {
for (u32 i = 0; i<GC_TYPE_SIZE; ++i) {
unused[i].clear();
}
for(auto& i : strs) {
for (auto& i : strs) {
delete i.val.gcobj;
}
strs.clear();
env_argv.clear();
}
void gc::info() const {
using std::left;
using std::setw;
using std::setfill;
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",
"object",
"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 += "-";
}
auto last_line = indent_string + "+" +
indent_string + "-" + indent_string + "-" + indent_string;
indent_string = indent_string + "+" +
indent_string + "+" + indent_string + "+" + indent_string;
std::clog << "\n" << indent_string << "\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" << indent_string << "\n";
double total = 0;
for(u8 i = 0; i<gc_type_size; ++i) {
if (!gcnt[i] && !acnt[i] && !size[i]) {
continue;
}
total += 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 << indent_string << "\n";
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" << indent_string << "\n";
std::clog << " " << left << setw(indent) << setfill(' ') << "gc time";
std::clog << " | " << worktime*1.0/den*1000 << " ms\n";
std::clog << " " << left << setw(indent) << setfill(' ') << "avg time";
std::clog << " | " << worktime*1.0/den*1000/total << " ms\n";
std::clog << " " << left << setw(indent) << setfill(' ') << "max gc";
std::clog << " | " << max_time*1.0/den*1000 << " ms\n";
std::clog << " " << left << setw(indent) << setfill(' ') << "max mark";
std::clog << " | " << max_mark_time*1.0/den*1000 << " ms\n";
std::clog << " " << left << setw(indent) << setfill(' ') << "max sweep";
std::clog << " | " << max_sweep_time*1.0/den*1000 << " ms\n";
std::clog << last_line << "\n";
}
var gc::alloc(u8 type) {
const u8 index = type-vm_str;
++acnt[index];
if (unused[index].empty()) {
++gcnt[index];
var gc::alloc(const vm_type type) {
const u32 index = static_cast<u32>(type)-static_cast<u32>(vm_type::vm_str);
++status.alloc_count[index];
// if still in incremental sweep stage? do it
// if not in incremental sweep stage, run a new gc cycle
if (in_incremental_sweep_stage) {
do_mark_sweep();
} else if (unused[index].empty()) {
++status.gc_cycle_trigger_count[index];
do_mark_sweep();
}
// if in incremental sweep stage, but the unused list is empty,
// do it until the unused list has something
while (unused[index].empty() && in_incremental_sweep_stage) {
do_mark_sweep();
}
// after all gc stages, still get empty list, extend
if (unused[index].empty()) {
extend(type);
}
var ret = var::gcobj(unused[index].back());
ret.val.gcobj->mark = nas_val::gc_status::uncollected;
ret.val.gcobj->clear();
// if incremental sweep stage, mark it as found
// but be aware that it may be collected in next gc cycle
ret.val.gcobj->mark = in_incremental_sweep_stage
? nas_val::gc_status::found
: nas_val::gc_status::uncollected;
unused[index].pop_back();
return ret;
}
void gc::ctxchg(nas_co& co) {
void gc::context_change(nas_co* co) {
// store running state to main context
mctx = *rctx;
main_context = *running_context;
// restore coroutine context state
*rctx = co.ctx;
*running_context = co->ctx;
// set coroutine pointer
cort = &co;
cort = co;
// set coroutine state to running
cort->status = nas_co::status::running;
}
void gc::ctxreserve() {
// pc=0 means this coroutine is finished
cort->status = rctx->pc?
nas_co::status::suspended:
nas_co::status::dead;
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 = *rctx;
cort->ctx = *running_context;
// restore main context state
*rctx = mctx;
*running_context = main_context;
// set coroutine pointer to nullptr
cort = nullptr;
}
var nas_err(const std::string& error_function_name, const std::string& info) {
std::cerr << "[vm] " << error_function_name << ": " << info << "\n";
return var::none();
}
}

View File

@@ -7,327 +7,72 @@
#pragma warning (disable:4102)
#endif
#ifndef _MSC_VER
#include <unistd.h>
#include <dirent.h>
#else
#include <io.h>
#include <direct.h>
#endif
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <iomanip>
#include <vector>
#include <unordered_map>
#include <chrono>
#include <algorithm>
#include <thread>
#include <cstring>
#include <sstream>
#include <iostream>
#include "nasal.h"
#include "nasal_type.h"
#include "util/gc_stat.h"
namespace nasal {
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,
vm_map // for globals and namespaces
};
struct free_list {
std::vector<nas_val*> elem[GC_TYPE_SIZE];
const u32 gc_type_size = vm_map-vm_str+1;
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
struct nas_val; // nas_val includes gc-managed types
struct var {
public:
u8 type = vm_none;
union {
u32 ret;
i64 cnt;
f64 num;
var* addr;
nas_val* gcobj;
} val;
private:
var(u8 t, u32 pc) {type = t; val.ret = pc;}
var(u8 t, i64 ct) {type = t; val.cnt = ct;}
var(u8 t, f64 n) {type = t; val.num = n;}
var(u8 t, var* p) {type = t; val.addr = p;}
var(u8 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 tonum();
std::string tostr();
bool objchk(const std::string&);
// create new var object
static var none();
static var nil();
static var ret(u32);
static var cnt(i64);
static var num(f64);
static var gcobj(nas_val*);
static var addr(var*);
// get content
var* addr();
u32 ret();
i64& cnt();
f64 num();
std::string& str();
nas_vec& vec();
nas_hash& hash();
nas_func& func();
nas_upval& upval();
nas_ghost& obj();
nas_co& co();
nas_map& map();
};
struct nas_vec {
std::vector<var> elems;
// mark if this is printed, avoid stackoverflow
bool printed;
nas_vec():printed(false) {}
usize size() const {return elems.size();}
var get_val(const i32);
var* get_mem(const i32);
};
struct nas_hash {
std::unordered_map<std::string, var> elems;
// mark if this is printed, avoid stackoverflow
bool printed;
nas_hash(): printed(false) {}
usize size() const {return elems.size();}
var get_val(const std::string&);
var* get_mem(const std::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 {
public:
/* on stack, use these variables */
bool onstk;
u32 size;
var* stk;
/* not on stack, use this */
std::vector<var> elems;
public:
nas_upval(): onstk(true), size(0), stk(nullptr) {}
var& operator[](usize n) {
return onstk? stk[n]:elems[n];
}
void clear() {
onstk = true;
elems.clear();
size = 0;
auto& operator[](i64 index) {
return elem[index];
}
};
struct nas_ghost {
private:
using destructor=void (*)(void*);
public:
std::string type_name;
destructor dtor_ptr;
void* ptr;
public:
nas_ghost(): type_name(""), dtor_ptr(nullptr), ptr(nullptr) {}
~nas_ghost() {clear();}
void set(const std::string&, destructor, void*);
void clear();
public:
const std::string& get_ghost_name() const {
return type_name;
}
};
struct context {
u32 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[STACK_DEPTH];
clear();
}
~nas_co() {
delete[] ctx.stack;
}
void clear();
};
struct nas_map {
bool printed = false;
std::unordered_map<std::string, var*> mapper;
nas_map() {}
void clear() {
mapper.clear();
}
var get_val(const std::string&);
var* get_mem(const std::string&);
};
struct nas_val {
enum class gc_status:u8 {
uncollected = 0,
collected,
found
};
gc_status mark;
u8 type; // value type
u8 unmut; // used to mark if a string is unmutable
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(u8);
~nas_val();
void clear();
};
std::ostream& operator<<(std::ostream&, nas_vec&);
std::ostream& operator<<(std::ostream&, nas_hash&);
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();
struct gc {
/* main context temporary storage */
context mctx;
context main_context;
/* global storage */
var* main_context_global = nullptr;
usize main_context_global_size = 0;
/* runtime context */
context* rctx = nullptr;
nas_co* cort = nullptr; // running coroutine
context* running_context = nullptr;
nas_co* cort = nullptr; // running coroutine
/* temporary space used in native/module functions */
/* 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
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
free_list unused; // gc free list
/* heap increase size */
u32 incr[gc_type_size] = {
128, // vm_str
128, // vm_vec
64, // vm_hash
128, // vm_func
u64 incr[GC_TYPE_SIZE] = {
256, // vm_str
256, // vm_vec
256, // vm_hash
256, // vm_func
256, // vm_upval
16, // vm_obj
16, // vm_co
2, // vm_map
4, // vm_obj
4, // vm_co
1, // vm_map
};
// total object count
u64 total_object_count = 0;
/* 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;
gc_stat status;
bool in_incremental_sweep_stage = false;
i64 current_sweep_index = 0;
void set(context* _ctx, var* _global, usize _size) {
rctx = _ctx;
running_context = _ctx;
main_context_global = _global;
main_context_global_size = _size;
}
@@ -335,6 +80,8 @@ struct gc {
private:
/* gc functions */
void do_mark_sweep();
void count_mark_time();
void count_sweep_time();
void mark();
void concurrent_mark(std::vector<var>&, usize, usize);
void mark_context_root(std::vector<var>&);
@@ -343,46 +90,53 @@ private:
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(u8);
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 u8);
void ctxchg(nas_co&);
void ctxreserve();
var alloc(const vm_type);
void context_change(nas_co*);
void context_reserve();
public:
f64 get_gc_time_ms() const {
return status.gc_time_ms();
}
// not very accurate
f64 get_total_memory() const {
return total_object_count * 3.5 / 1024.0 / 1024.0;
}
public:
var newstr(char c) {
var s = alloc(vm_str);
var s = alloc(vm_type::vm_str);
s.str() = c;
return s;
}
var newstr(const char* buff) {
var s=alloc(vm_str);
s.str() = 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_str);
var s = alloc(vm_type::vm_str);
s.str() = buff;
return s;
}
};
// use to print error log and return error value
var nas_err(const std::string&, const std::string&);
// module function type
typedef var (*module_func)(var*, usize, gc*);
// module function stores in tables with this type, end with {nullptr,nullptr}
// module function stores in tables with this type, end with {nullptr, nullptr}
struct module_func_info {
const char* name;
module_func fd;

View File

@@ -1,364 +1,428 @@
#include "nasal_import.h"
#include "symbol_finder.h"
namespace nasal {
linker::linker():
show_path(false), lib_loaded(false),
this_file(""), lib_path("") {
char sep = is_windows()? ';':':';
std::string PATH = getenv("PATH");
usize last = 0, pos = PATH.find(sep, 0);
while(pos!=std::string::npos) {
std::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));
}
}
std::string linker::get_path(call_expr* node) {
if (node->get_calls()[0]->get_type()==expr_type::ast_callf) {
auto tmp = (call_function*)node->get_calls()[0];
return ((string_literal*)tmp->get_argument()[0])->get_content();
}
auto fpath = std::string(".");
for(auto i : node->get_calls()) {
fpath += (is_windows()? "\\":"/") + ((call_hash*)i)->get_field();
}
return fpath + ".nas";
}
std::string linker::find_file(
const std::string& filename, const span& location) {
// first add file name itself into the file path
std::vector<std::string> fpath = {filename};
// generate search path from environ path
for(const auto& p : envpath) {
fpath.push_back(p + (is_windows()? "\\":"/") + filename);
}
// search file
for(const auto& i : fpath) {
if (access(i.c_str(), F_OK)!=-1) {
return i;
}
}
// we will find lib.nas in nasal std directory
if (filename=="lib.nas") {
return is_windows()?
find_file("std\\lib.nas", location):
find_file("std/lib.nas", location);
}
if (!show_path) {
err.err("link",
"in <" + location.file + ">: " +
"cannot find file <" + filename + ">, " +
"use <-d> to get detail search path");
return "";
}
std::string paths = "";
for(const auto& i : fpath) {
paths += " -> " + i + "\n";
}
err.err("link",
"in <" + location.file + ">: " +
"cannot find file <" + filename + "> in these paths:\n" + paths);
return "";
}
bool linker::import_check(expr* node) {
/*
call
|_id:import
|_callh:std
|_callh:file
*/
if (node->get_type()!=expr_type::ast_call) {
return false;
}
auto tmp = (call_expr*)node;
if (tmp->get_first()->get_type()!=expr_type::ast_id) {
return false;
}
if (((identifier*)tmp->get_first())->get_name()!="import") {
return false;
}
if (!tmp->get_calls().size()) {
return false;
}
// import.xxx.xxx;
if (tmp->get_calls()[0]->get_type()==expr_type::ast_callh) {
for(auto i : tmp->get_calls()) {
if (i->get_type()!=expr_type::ast_callh) {
return false;
}
}
return true;
}
// import("xxx");
if (tmp->get_calls().size()!=1) {
return false;
}
/*
call
|_id:import
|_call_func
|_string:'filename'
*/
if (tmp->get_calls()[0]->get_type()!=expr_type::ast_callf) {
return false;
}
auto func_call = (call_function*)tmp->get_calls()[0];
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::exist(const std::string& file) {
// avoid importing the same file
for(const auto& fname : files) {
if (file==fname) {
return true;
}
}
files.push_back(file);
return false;
}
u16 linker::find(const std::string& file) {
for(usize i = 0; i<files.size(); ++i) {
if (files[i]==file) {
return static_cast<u16>(i);
}
}
std::cerr << "unreachable: using this method incorrectly\n";
std::exit(-1);
return UINT16_MAX;
}
bool linker::check_self_import(const std::string& file) {
for(const auto& i : module_load_stack) {
if (file==i) {
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::link(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(call_expr* node) {
lexer lex;
parse par;
// get filename
auto filename = get_path(node);
// clear this node
for(auto i : node->get_calls()) {
delete i;
}
node->get_calls().clear();
auto location = node->get_first()->get_location();
delete node->get_first();
node->set_first(new nil_expr(location));
// this will make node to call_expr(nil),
// will not be optimized when generating bytecodes
// avoid infinite loading loop
filename = find_file(filename, node->get_location());
if (!filename.length()) {
return new code_block({0, 0, 0, 0, filename});
}
if (check_self_import(filename)) {
err.err("link", "self-referenced module <" + filename + ">:\n" +
" reference path: " + generate_self_import_path(filename));
return new code_block({0, 0, 0, 0, filename});
}
exist(filename);
module_load_stack.push_back(filename);
// start importing...
if (lex.scan(filename).geterr()) {
err.err("link", "error occurred when analysing <" + filename + ">");
}
if (par.compile(lex).geterr()) {
err.err("link", "error occurred when analysing <" + filename + ">");
}
auto tmp = par.swap(nullptr);
// check if tmp has 'import'
auto res = load(tmp, find(filename));
module_load_stack.pop_back();
return res;
}
code_block* linker::import_nasal_lib() {
lexer lex;
parse par;
auto filename = find_file("lib.nas", {0, 0, 0, 0, files[0]});
if (!filename.length()) {
return new code_block({0, 0, 0, 0, filename});
}
lib_path = filename;
// avoid infinite loading library
if (exist(filename)) {
return new code_block({0, 0, 0, 0, filename});
}
// start importing...
if (lex.scan(filename).geterr()) {
err.err("link", "error occurred when analysing library <" + filename + ">");
}
if (par.compile(lex).geterr()) {
err.err("link", "error occurred when analysing library <" + filename + ">");
}
auto tmp = par.swap(nullptr);
// check if tmp has 'import'
return load(tmp, find(filename));
}
std::string linker::generate_module_name(const std::string& filename) {
auto error_name = "error_generated@[" + filename + "]";
auto pos = filename.find_last_of(".nas");
if (pos==std::string::npos) {
return error_name;
}
pos -= 4;
auto split_pos = filename.find_last_of("/");
if (split_pos==std::string::npos) {
split_pos = filename.find_last_of("\\");
}
auto res = split_pos==std::string::npos?
filename.substr(0, pos + 1):
filename.substr(split_pos + 1, pos - split_pos);
if (!res.length()) {
err.warn("link", "get empty module name from <" + filename + ">, " +
"will not be easily accessed.");
}
if (res.length() && '0' <= res[0] && res[0] <= '9') {
err.warn("link", "get module <" + res + "> from <" + filename + ">, " +
"will not be easily accessed.");
}
if (res.length() && res.find(".")!=std::string::npos) {
err.warn("link", "get module <" + res + "> from <" + filename + ">, " +
"will not be easily accessed.");
}
return res;
}
return_expr* linker::generate_module_return(code_block* block) {
auto sf = new symbol_finder;
auto res = new return_expr(block->get_location());
auto value = new hash_expr(block->get_location());
res->set_value(value);
for(const auto& i : sf->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);
}
delete sf;
return res;
}
definition_expr* linker::generate_module_definition(code_block* block) {
auto def = new definition_expr(block->get_location());
def->set_identifier(new identifier(
block->get_location(),
generate_module_name(block->get_location().file)
));
auto call = new call_expr(block->get_location());
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;
}
code_block* linker::load(code_block* program_root, u16 fileindex) {
auto tree = new code_block({0, 0, 0, 0, files[fileindex]});
// load library, this ast will be linked with root directly
// so no extra namespace is generated
if (!lib_loaded) {
auto nasal_lib_code_block = import_nasal_lib();
// insert nasal lib code to the back of tree
link(tree, nasal_lib_code_block);
delete nasal_lib_code_block;
lib_loaded = true;
}
// load imported modules
for(auto& import_ast_node : program_root->get_expressions()) {
if (!import_check(import_ast_node)) {
break;
}
auto module_code_block = import_regular_file((call_expr*)import_ast_node);
// after importing the regular file as module, delete this node
const auto loc = import_ast_node->get_location();
delete import_ast_node;
// and replace the node with null_expr node
import_ast_node = new null_expr(loc);
// 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
tree->add_expression(generate_module_definition(module_code_block));
}
// insert program root to the back of tree
link(tree, program_root);
return tree;
}
const error& linker::link(
parse& parse, const std::string& self, bool spath = false) {
show_path = spath;
// initializing file map
this_file = self;
files = {self};
module_load_stack = {self};
// scan root and import files
// then generate a new ast and return to import_ast
// the main file's index is 0
auto new_tree_root = load(parse.tree(), 0);
auto old_tree_root = parse.swap(new_tree_root);
delete old_tree_root;
return err;
}
}
#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 env_get_path = getenv("PATH");
if (!env_get_path) {
err.warn("link", "cannot get env \"PATH\".");
envpath = {};
return;
}
const auto seperator = util::is_windows()? ';':':';
const auto PATH = std::string(env_get_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)) {
// do not export symbol begins with '_'
if (i.name.length() && i.name[0]=='_') {
continue;
}
auto pair = new hash_pair(block->get_location());
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;
}
}

View File

@@ -1,60 +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
#ifdef _MSC_VER
#define F_OK 0
#endif
#include "nasal.h"
#include "nasal_ast.h"
#include "nasal_lexer.h"
#include "nasal_parse.h"
#include "symbol_finder.h"
#include <vector>
namespace nasal {
class linker {
private:
bool show_path;
bool lib_loaded;
std::string this_file;
std::string lib_path;
error err;
std::vector<std::string> files;
std::vector<std::string> module_load_stack;
std::vector<std::string> envpath;
private:
bool import_check(expr*);
bool exist(const std::string&);
u16 find(const std::string&);
bool check_self_import(const std::string&);
std::string generate_self_import_path(const std::string&);
void link(code_block*, code_block*);
std::string get_path(call_expr*);
std::string find_file(const std::string&, const span&);
code_block* import_regular_file(call_expr*);
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*);
code_block* load(code_block*, u16);
public:
linker();
const error& link(parse&, const std::string&, bool);
const auto& get_file_list() const {return files;}
const auto& get_this_file() const {return this_file;}
const auto& get_lib_path() const {return lib_path;}
};
}
#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;}
};
}

View File

@@ -1,393 +1,458 @@
#ifdef _MSC_VER
#pragma warning (disable:4244)
#pragma warning (disable:4267)
#pragma warning (disable:4102)
#endif
#include "nasal_lexer.h"
#include "repl.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_single_opr(char c) {
return (
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=='~' || 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"+chrhex(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;
}
// check file exsits and it is a regular 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();
}
// 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) {
return typetbl.count(str)? typetbl.at(str):tok::null;
}
std::string lexer::utf8_gen() {
std::string str = "";
while(ptr<res.size() && res[ptr]<0) {
std::string tmp = "";
u32 nbytes = 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"+chrhex(tmp[0]);
for(u32 i = 1; i<tmp.size(); ++i) {
utf_info += " 0x"+chrhex(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() {
u32 begin_line = line;
u32 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::null)? type:tok::id, str
};
}
token lexer::num_gen() {
u32 begin_line = line;
u32 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::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::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::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::num, "0"};
}
}
column += str.length();
return {{begin_line, begin_column, line, column, filename}, tok::num, str};
}
token lexer::str_gen() {
u32 begin_line = line;
u32 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::str, str};
}
++column;
// if is not utf8, 1+utf8_hdchk should be 1
if (begin=='`' && str.length()!=1+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::str, str};
}
token lexer::single_opr() {
u32 begin_line = line;
u32 begin_column = column;
std::string str(1, res[ptr]);
++column;
tok type = get_type(str);
if (type==tok::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() {
u32 begin_line = line;
u32 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() {
u32 begin_line = line;
u32 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_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::eof, "<eof>"});
} else {
// if token sequence is empty, generate a default location
toks.push_back({{line, column, line, column, filename}, tok::eof, "<eof>"});
}
res = "";
return err;
}
}
#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;
}
}

View File

@@ -10,154 +10,159 @@
#include <sstream>
#include <vector>
#include <unordered_map>
#include <sys/stat.h>
#include "nasal.h"
#include "nasal_err.h"
#ifdef _MSC_VER
#define S_ISREG(m) (((m)&0xF000)==0x8000)
#endif
namespace nasal {
enum class tok:u32 {
null=0, // null token (default token type)
num, // number literal
str, // string literal
id, // identifier
tktrue, // keyword true
tkfalse, // keyword false
rfor, // loop keyword for
forindex, // loop keyword forindex
foreach, // loop keyword foreach
rwhile, // loop keyword while
var, // keyword for definition
func, // keyword for definition of function
brk, // loop keyword break
cont, // loop keyword continue
ret, // function keyword return
rif, // condition expression keyword if
elsif, // condition expression keyword elsif
relse, // condition expression keyword else
tknil, // nil literal
lcurve, // (
rcurve, // )
lbracket, // [
rbracket, // ]
lbrace, // {
rbrace, // }
semi, // ;
opand, // operator and
opor, // operator or
comma, // ,
dot, // .
ellipsis, // ...
quesmark, // ?
colon, // :
add, // operator +
sub, // operator -
mult, // operator *
div, // operator /
floater, // operator ~ and binary operator ~
btand, // bitwise operator &
btor, // bitwise operator |
btxor, // bitwise operator ^
opnot, // operator !
eq, // operator =
addeq, // operator +=
subeq, // operator -=
multeq, // operator *=
diveq, // operator /=
lnkeq, // operator ~=
btandeq, // operator &=
btoreq, // operator |=
btxoreq, // operator ^=
cmpeq, // operator ==
neq, // operator !=
less, // operator <
leq, // operator <=
grt, // operator >
geq, // operator >=
eof // <eof> end of token list
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
span loc; // location
tok type; // token type
std::string str; // content
token() = default;
token(const token&) = default;
};
class lexer {
private:
u32 line;
u32 column;
u64 line;
u64 column;
usize ptr;
std::string filename;
std::string res;
private:
error err;
u64 invalid_char;
std::vector<token> toks;
const std::unordered_map<std::string, tok> typetbl {
{"true" ,tok::tktrue },
{"false" ,tok::tkfalse },
{"for" ,tok::rfor },
{"forindex",tok::forindex},
{"foreach" ,tok::foreach },
{"while" ,tok::rwhile },
{"var" ,tok::var },
{"func" ,tok::func },
{"break" ,tok::brk },
{"continue",tok::cont },
{"return" ,tok::ret },
{"if" ,tok::rif },
{"elsif" ,tok::elsif },
{"else" ,tok::relse },
{"nil" ,tok::tknil },
{"(" ,tok::lcurve },
{")" ,tok::rcurve },
{"[" ,tok::lbracket},
{"]" ,tok::rbracket},
{"{" ,tok::lbrace },
{"}" ,tok::rbrace },
{";" ,tok::semi },
{"and" ,tok::opand },
{"or" ,tok::opor },
{"," ,tok::comma },
{"." ,tok::dot },
{"..." ,tok::ellipsis},
{"?" ,tok::quesmark},
{":" ,tok::colon },
{"+" ,tok::add },
{"-" ,tok::sub },
{"*" ,tok::mult },
{"/" ,tok::div },
{"~" ,tok::floater },
{"&" ,tok::btand },
{"|" ,tok::btor },
{"^" ,tok::btxor },
{"!" ,tok::opnot },
{"=" ,tok::eq },
{"+=" ,tok::addeq },
{"-=" ,tok::subeq },
{"*=" ,tok::multeq },
{"/=" ,tok::diveq },
{"~=" ,tok::lnkeq },
{"&=" ,tok::btandeq },
{"|=" ,tok::btoreq },
{"^=" ,tok::btxoreq },
{"==" ,tok::cmpeq },
{"!=" ,tok::neq },
{"<" ,tok::less },
{"<=" ,tok::leq },
{">" ,tok::grt },
{">=" ,tok::geq }
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);
@@ -165,6 +170,7 @@ private:
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);
@@ -176,13 +182,17 @@ private:
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) {}
lexer(): line(1), column(0), ptr(0),
filename(""), res(""),
invalid_char(0) {}
const error& scan(const std::string&);
const std::vector<token>& result() const {return toks;}
const auto& result() const {return toks;}
};
}

View File

@@ -1,41 +1,35 @@
#include "nasal_opcode.h"
#include "util/util.h"
namespace nasal {
const char* opname[] = {
"exit ", "repl ", "intl ", "loadg ",
"loadl ", "loadu ", "pnum ", "pnil ",
"pstr ", "newv ", "newh ", "newf ",
"happ ", "para ", "def ", "dyn ",
"lnot ", "usub ", "bitnot", "bitor ",
"bitxor", "bitand", "add ", "sub ",
"mult ", "div ", "lnk ", "addc ",
"subc ", "multc ", "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 ", "findx ", "feach ",
"callg ", "calll ", "upval ", "callv ",
"callvi", "callh ", "callfv", "callfh",
"callb ", "slcbeg", "slcend", "slice ",
"slice2", "mcallg", "mcalll", "mupval",
"mcallv", "mcallh", "ret "
};
void codestream::set(
const f64* num_buff,
const std::string* str_buff,
const nasal_builtin_table* native_table_ptr,
const std::string* file_list) {
nums = num_buff;
strs = str_buff;
natives = native_table_ptr;
void codestream::set(const f64* number_list,
const std::string* string_list,
const std::unordered_map<std::string, u64>& globals,
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;
global_variable.resize(globals.size());
for (auto& [name, index]: globals) {
global_variable[index] = name;
}
}
void codestream::set(const f64* number_list,
const std::string* string_list,
const std::vector<std::string>& globals,
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;
global_variable = globals;
}
void codestream::dump(std::ostream& out) const {
@@ -43,75 +37,123 @@ void codestream::dump(std::ostream& out) const {
using std::setfill;
using std::hex;
using std::dec;
auto op = code.op;
auto num = code.num;
const auto op = code.op;
const auto num = code.num;
// dump operand index and bytecode(hex format)
out << hex << "0x"
<< setw(6) << setfill('0') << index << " "
<< setw(2) << setfill('0') << static_cast<u32>(op) << " "
<< setw(2) << setfill('0') << ((num>>16)&0xff) << " "
<< setw(2) << setfill('0') << ((num>>8)&0xff) << " "
<< setw(2) << setfill('0') << (num&0xff) << " "
<<opname[op]<<" "<<dec;
<< 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) {
auto this_byte = ((num>>i)&0xff);
out << hex << setw(2) << setfill('0') << this_byte << dec << " ";
}
// dump operand name
out << " " << operand_name_table.at(static_cast<opcode_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_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
<< " (" << nums[num] << ")"; break;
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
<< " (" << rawstr(strs[num], 16) << ")"; break;
case op_addecp: case op_subecp:
case op_mulecp: case op_divecp:
out << hex << "0x" << num << dec
<< " (" << nums[num] << ") sp-1"; break;
out << hex << "0x" << num << dec;
out << " (\"" << util::rawstr(const_string[num], 32) << "\")";
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
<< " (" << rawstr(strs[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:
out << hex << "0x" << num << dec;
out << " (\"" << util::rawstr(const_string[num], 32) << "\") 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
<< " (" << nums[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:
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_calll:
case op_mcalll:
case op_loadl:
out << hex << "0x" << num << dec; break;
case op_loadg:
case op_mcallg:
case op_callg:
out << hex << "0x" << num << dec;
out << " (" << util::rawstr(global_variable[num], 32) << ")";
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:
out << hex << "0x" << num << dec;
out << " <" << natives[num].name << "@0x";
out << hex << reinterpret_cast<u64>(natives[num].func) << dec;
out << ">";
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
<< " (" << rawstr(strs[num], 16) << ")"; 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], 32) << "\")";
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 << ")";
out << " (" << files[code.fidx] << ":" << code.line << ")";
}
}

View File

@@ -1,107 +1,203 @@
#pragma once
#include "nasal.h"
#include "nasal_builtin.h"
#include "natives/builtin.h"
#include <iostream>
#include <vector>
#include <cstring>
#include <sstream>
#include <unordered_map>
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_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
enum opcode_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<opcode_type, std::string> operand_name_table = {
{opcode_type::op_exit, "exit "},
{opcode_type::op_repl, "repl "},
{opcode_type::op_intl, "intl "},
{opcode_type::op_loadg, "loadg "},
{opcode_type::op_loadl, "loadl "},
{opcode_type::op_loadu, "loadu "},
{opcode_type::op_dup, "dup "},
{opcode_type::op_pnum, "pnum "},
{opcode_type::op_pnil, "pnil "},
{opcode_type::op_pstr, "pstr "},
{opcode_type::op_newv, "newv "},
{opcode_type::op_newh, "newh "},
{opcode_type::op_newf, "newf "},
{opcode_type::op_happ, "happ "},
{opcode_type::op_para, "para "},
{opcode_type::op_deft, "def "},
{opcode_type::op_dyn, "dyn "},
{opcode_type::op_lnot, "lnot "},
{opcode_type::op_usub, "usub "},
{opcode_type::op_bnot, "bitnot"},
{opcode_type::op_btor, "bitor "},
{opcode_type::op_btxor, "bitxor"},
{opcode_type::op_btand, "bitand"},
{opcode_type::op_add, "add "},
{opcode_type::op_sub, "sub "},
{opcode_type::op_mul, "mult "},
{opcode_type::op_div, "div "},
{opcode_type::op_lnk, "lnk "},
{opcode_type::op_addc, "addc "},
{opcode_type::op_subc, "subc "},
{opcode_type::op_mulc, "multc "},
{opcode_type::op_divc, "divc "},
{opcode_type::op_lnkc, "lnkc "},
{opcode_type::op_addeq, "addeq "},
{opcode_type::op_subeq, "subeq "},
{opcode_type::op_muleq, "muleq "},
{opcode_type::op_diveq, "diveq "},
{opcode_type::op_lnkeq, "lnkeq "},
{opcode_type::op_btandeq, "bandeq"},
{opcode_type::op_btoreq, "boreq "},
{opcode_type::op_btxoreq, "bxoreq"},
{opcode_type::op_addeqc, "addeqc"},
{opcode_type::op_subeqc, "subeqc"},
{opcode_type::op_muleqc, "muleqc"},
{opcode_type::op_diveqc, "diveqc"},
{opcode_type::op_lnkeqc, "lnkeqc"},
{opcode_type::op_addecp, "addecp"},
{opcode_type::op_subecp, "subecp"},
{opcode_type::op_mulecp, "mulecp"},
{opcode_type::op_divecp, "divecp"},
{opcode_type::op_lnkecp, "lnkecp"},
{opcode_type::op_meq, "meq "},
{opcode_type::op_eq, "eq "},
{opcode_type::op_neq, "neq "},
{opcode_type::op_less, "less "},
{opcode_type::op_leq, "leq "},
{opcode_type::op_grt, "grt "},
{opcode_type::op_geq, "geq "},
{opcode_type::op_lessc, "lessc "},
{opcode_type::op_leqc, "leqc "},
{opcode_type::op_grtc, "grtc "},
{opcode_type::op_geqc, "geqc "},
{opcode_type::op_pop, "pop "},
{opcode_type::op_jmp, "jmp "},
{opcode_type::op_jt, "jt "},
{opcode_type::op_jf, "jf "},
{opcode_type::op_cnt, "cnt "},
{opcode_type::op_findex, "findx "},
{opcode_type::op_feach, "feach "},
{opcode_type::op_callg, "callg "},
{opcode_type::op_calll, "calll "},
{opcode_type::op_upval, "upval "},
{opcode_type::op_callv, "callv "},
{opcode_type::op_callvi, "callvi"},
{opcode_type::op_callh, "callh "},
{opcode_type::op_callfv, "callfv"},
{opcode_type::op_callfh, "callfh"},
{opcode_type::op_callb, "callb "},
{opcode_type::op_slcbeg, "slcbeg"},
{opcode_type::op_slcend, "slcend"},
{opcode_type::op_slc, "slice "},
{opcode_type::op_slc2, "slice2"},
{opcode_type::op_mcallg, "mcallg"},
{opcode_type::op_mcalll, "mcalll"},
{opcode_type::op_mupval, "mupval"},
{opcode_type::op_mcallv, "mcallv"},
{opcode_type::op_mcallh, "mcallh"},
{opcode_type::op_ret, "ret "}
};
struct opcode {
u8 op; // opcode
u16 fidx; // source code file index
u32 num; // immediate num
u32 line; // location line of source code
u64 num; // immediate num
u64 line; // location line of source code
opcode() = default;
opcode(const opcode&) = default;
opcode& operator=(const opcode&) = default;
@@ -110,24 +206,27 @@ struct opcode {
class codestream {
private:
opcode code;
const u32 index;
inline static const f64* nums = nullptr;
inline static const std::string* strs = nullptr;
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;
inline static std::vector<std::string> global_variable;
public:
codestream(const opcode& c, const u32 i): code(c), index(i) {}
static void set(
const f64*, const std::string*,
const nasal_builtin_table*,
const std::string* file_list = nullptr
);
codestream(const opcode& c, const u64 i): code(c), index(i) {}
static void set(const f64*,
const std::string*,
const std::unordered_map<std::string, u64>&,
const nasal_builtin_table*,
const std::string* file_list = nullptr);
static void set(const f64*,
const std::string*,
const std::vector<std::string>&,
const nasal_builtin_table*,
const std::string* file_list = nullptr);
void dump(std::ostream&) const;
friend std::ostream& operator<<(std::ostream&, const codestream&);
};
std::ostream& operator<<(std::ostream&, const codestream&);
extern const char* opname[];
}

File diff suppressed because it is too large Load Diff

View File

@@ -15,73 +15,79 @@ class parse {
#define prevspan (ptr!=0? toks[ptr-1].loc:toks[ptr].loc)
private:
u32 ptr;
u32 in_func; // count function block
u32 in_loop; // count loop block
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> tokname {
{tok::rfor ,"for" },
{tok::forindex,"forindex"},
{tok::foreach ,"foreach" },
{tok::rwhile ,"while" },
{tok::var ,"var" },
{tok::func ,"func" },
{tok::brk ,"break" },
{tok::cont ,"continue"},
{tok::ret ,"return" },
{tok::rif ,"if" },
{tok::elsif ,"elsif" },
{tok::relse ,"else" },
{tok::tknil ,"nil" },
{tok::lcurve ,"(" },
{tok::rcurve ,")" },
{tok::lbracket,"[" },
{tok::rbracket,"]" },
{tok::lbrace ,"{" },
{tok::rbrace ,"}" },
{tok::semi ,";" },
{tok::opand ,"and" },
{tok::opor ,"or" },
{tok::comma ,"," },
{tok::dot ,"." },
{tok::ellipsis,"..." },
{tok::quesmark,"?" },
{tok::colon ,":" },
{tok::add ,"+" },
{tok::sub ,"-" },
{tok::mult ,"*" },
{tok::div ,"/" },
{tok::floater ,"~" },
{tok::btand ,"&" },
{tok::btor ,"|" },
{tok::btxor ,"^" },
{tok::opnot ,"!" },
{tok::eq ,"=" },
{tok::addeq ,"+=" },
{tok::subeq ,"-=" },
{tok::multeq ,"*=" },
{tok::diveq ,"/=" },
{tok::lnkeq ,"~=" },
{tok::btandeq ,"&=" },
{tok::btoreq ,"|=" },
{tok::btxoreq ,"^=" },
{tok::cmpeq ,"==" },
{tok::neq ,"!=" },
{tok::less ,"<" },
{tok::leq ,"<=" },
{tok::grt ,">" },
{tok::geq ,">=" }
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&,std::string);
void die(const span&, const std::string&);
void next();
void match(tok, const char* info=nullptr);
void match(tok, const char* info = nullptr);
bool lookahead(tok);
bool lookahead_expression();
bool is_call(tok);
bool check_comma(const tok*);
bool check_tuple();
@@ -92,6 +98,7 @@ private:
void update_location(expr*);
private:
use_stmt* use_stmt_gen();
null_expr* null();
nil_expr* nil();
number_literal* num();
@@ -113,12 +120,14 @@ private:
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();
@@ -149,7 +158,9 @@ public:
}
public:
parse(): ptr(0), in_func(0), in_loop(0), toks(nullptr), root(nullptr) {}
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();

300
src/nasal_type.cpp Normal file
View File

@@ -0,0 +1,300 @@
#include "nasal_type.h"
#include "util/util.h"
#include <cstring>
#include <sstream>
namespace nasal {
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);
}
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);
}
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;
}
// mark print, to avoid infinite recursion
hash.printed = true;
static const char* sep[] = {", ", "}"};
usize iter = 0, size = hash.elems.size();
out << "{";
for (auto& i : hash.elems) {
out << i.first << ": " << i.second << sep[(++iter)==size];
}
// restore flag
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 << "<" << ghost.get_ghost_name();
out << "@0x" << std::hex << ghost.convert<u64>() << 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;
}
// mark print, to avoid infinite recursion
mp.printed = true;
static const char* sep[] = {", ", "}"};
usize iter = 0, size = mp.mapper.size();
out << "{";
for (auto& i : mp.mapper) {
out << i.first << ": " << *i.second << sep[(++iter)==size];
}
// restore flag
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;
}
}
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;
}
}

362
src/nasal_type.h Normal file
View File

@@ -0,0 +1,362 @@
#pragma once
#include "nasal.h"
#include "util/util.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
// nas_val includes gc-managed types
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 elem {
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();
};
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;
}
public:
// create new var object
static var none() {
return var(vm_type::vm_none, static_cast<u64>(0));
}
static var nil() {
return var(vm_type::vm_nil, static_cast<u64>(0));
}
static var ret(u64 pc) {
return var(vm_type::vm_ret, pc);
}
static var cnt(i64 n) {
return var(vm_type::vm_cnt, n);
}
static var num(f64 n) {
return var(vm_type::vm_num, n);
}
static var gcobj(nas_val* p) {
return var(p->type, p);
}
static var addr(var* p) {
return var(vm_type::vm_addr, p);
}
public:
// get value
var* addr() const { return val.addr; }
u64 ret() const { return val.ret; }
i64& cnt() { return val.cnt; }
f64 num() const { return val.num; }
public:
// get gc object
std::string& str() { return *val.gcobj->ptr.str; }
nas_vec& vec() { return *val.gcobj->ptr.vec; }
nas_hash& hash() { return *val.gcobj->ptr.hash; }
nas_func& func() { return *val.gcobj->ptr.func; }
nas_upval& upval() { return *val.gcobj->ptr.upval; }
nas_ghost& ghost() { return *val.gcobj->ptr.obj; }
nas_co& co() { return *val.gcobj->ptr.co; }
nas_map& map() { return *val.gcobj->ptr.map; }
public:
// get const gc object
const std::string& str() const { return *val.gcobj->ptr.str; }
const nas_vec& vec() const { return *val.gcobj->ptr.vec; }
const nas_hash& hash() const { return *val.gcobj->ptr.hash; }
const nas_func& func() const { return *val.gcobj->ptr.func; }
const nas_upval& upval() const { return *val.gcobj->ptr.upval; }
const nas_ghost& ghost() const { return *val.gcobj->ptr.obj; }
const nas_co& co() const { return *val.gcobj->ptr.co; }
const nas_map& map() const { return *val.gcobj->ptr.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; }
public:
// convert to number
f64 to_num() const {
return type != vm_type::vm_str
? val.num
: util::str_to_num(str().c_str());
}
// convert to string
std::string to_str();
inline bool object_check(const std::string&) const;
friend std::ostream& operator<<(std::ostream&, var&);
};
struct nas_vec {
std::vector<var> elems;
// mark if this is printed, avoid stack overflow
bool printed = false;
auto size() const { return elems.size(); }
var 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* get_memory(const i32 index) {
i32 size = elems.size();
if (index < -size || index >= size) {
return nullptr;
}
return &elems[index >= 0 ? index : index + size];
}
friend std::ostream& operator<<(std::ostream&, nas_vec&);
};
struct nas_hash {
std::unordered_map<std::string, var> elems;
// mark if this is printed, avoid stack overflow
bool printed = false;
auto size() const { return elems.size(); }
var get_value(const std::string&);
var* get_memory(const std::string&);
friend std::ostream& operator<<(std::ostream&, nas_hash&);
};
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();
friend std::ostream& operator<<(std::ostream&, nas_func&);
};
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();
friend std::ostream& operator<<(std::ostream&, const nas_ghost&);
public:
const auto& get_ghost_name() const { return type_name; }
public:
template<typename T>
T* get() { return static_cast<T*>(pointer); }
template<typename T>
T convert() const { return reinterpret_cast<T>(pointer); }
};
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();
friend std::ostream& operator<<(std::ostream&, const nas_co&);
};
struct nas_map {
bool printed = false;
std::unordered_map<std::string, var*> mapper;
public:
void clear() {
mapper.clear();
}
auto size() const { return mapper.size(); }
var get_value(const std::string&);
var* get_memory(const std::string&);
friend std::ostream& operator<<(std::ostream&, nas_map&);
};
const var zero = var::num(0);
const var one = var::num(1);
const var nil = var::nil();
inline bool var::object_check(const std::string& name) const {
return is_ghost() && ghost().type_name == name && ghost().pointer;
}
// use to print error log and return error value
static var nas_err(const std::string& func, const std::string& info) {
std::cerr << "[vm] " << func << ": " << info << "\n";
return var::none();
}
}

View File

@@ -1,24 +1,23 @@
#include "nasal_vm.h"
#include "util/util.h"
namespace nasal {
void vm::init(
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, i32>& global_symbol,
const std::vector<std::string>& filenames,
const std::vector<std::string>& argv
) {
cnum = nums.data();
cstr = strs.data();
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 = natives;
native_function = natives;
/* set context and global */
if (!is_repl_mode || first_exec_flag) {
@@ -31,14 +30,16 @@ void vm::init(
ngc.init(strs, argv);
/* init vm globals */
auto map_instance = ngc.alloc(vm_map);
auto map_instance = ngc.alloc(vm_type::vm_map);
global_symbol_name.resize(global_symbol.size());
global[global_symbol.at("globals")] = map_instance;
for(const auto& i : global_symbol) {
map_instance.map().mapper[i.first] = global+i.second;
for (const auto& i : global_symbol) {
map_instance.map().mapper[i.first] = global + i.second;
global_symbol_name[i.second] = i.first;
}
/* init vm arg */
auto arg_instance = ngc.alloc(vm_vec);
auto arg_instance = ngc.alloc(vm_type::vm_vec);
global[global_symbol.at("arg")] = arg_instance;
arg_instance.vec().elems = ngc.env_argv;
}
@@ -51,298 +52,659 @@ void vm::context_and_global_init() {
ctx.funcr = nil;
ctx.upvalr = nil;
/* set canary = stack[STACK_DEPTH-1] */
ctx.canary = ctx.stack+STACK_DEPTH-1;
/* 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<STACK_DEPTH; ++i) {
for (u32 i = 0; i<VM_STACK_DEPTH; ++i) {
ctx.stack[i] = nil;
global[i] = nil;
}
}
void vm::valinfo(var& val) {
const auto p = reinterpret_cast<u64>(val.val.gcobj);
void vm::return_address_info(const var& val) {
std::clog << "0x";
std::clog << std::hex << val.ret() << std::dec;
}
void vm::memory_address_info(const var& val) {
std::clog << "0x";
std::clog << std::hex << reinterpret_cast<u64>(val.addr()) << std::dec;
}
void vm::raw_string_info(var& val) {
std::clog << "\"" << util::rawstr(val.str(), 24) << "\"";
}
void vm::upvalue_info(var& val) {
std::clog << "[" << val.upval().size << " val] ";
if (val.upval().on_stack) {
std::clog << "offset:0x" << std::hex;
std::clog << reinterpret_cast<u64>(val.upval().stack_frame_offset);
std::clog << std::dec;
}
}
void vm::vector_value_info(var& val) {
std::clog << "[" << val.vec().size() << " val]";
}
void vm::hash_value_info(var& val, const usize max_show_elems) {
std::clog << "{";
usize count = 0;
for (const auto& i : val.hash().elems) {
++count;
if (count>max_show_elems) {
break;
}
std::clog << i.first;
if (count!=val.hash().size()) {
std::clog << ", ";
}
}
if (val.hash().size()>max_show_elems) {
std::clog << "...";
}
std::clog << "}";
}
void vm::coroutine_value_info(var& val) {
std::clog << "[ ";
switch(val.co().status) {
case nas_co::status::dead: std::clog << "dead"; break;
case nas_co::status::running: std::clog << "running"; break;
case nas_co::status::suspended: std::clog << "suspended"; break;
}
std::clog << " ] @0x";
std::clog << std::hex << reinterpret_cast<u64>(val.val.gcobj) << std::dec;
}
void vm::namespace_value_info(var& val, const usize max_show_elems) {
std::clog << "{";
usize count = 0;
for (const auto& i : val.map().mapper) {
++count;
if (count>max_show_elems) {
break;
}
std::clog << i.first;
if (count!=val.map().size()) {
std::clog << ", ";
}
}
if (val.map().size()>max_show_elems) {
std::clog << "...";
}
std::clog << "}";
}
void vm::value_name_form(const var& val) {
std::clog << "| ";
switch(val.type) {
case vm_none: std::clog << "| null |"; break;
case vm_ret: std::clog << "| pc | 0x" << std::hex
<< val.ret() << std::dec; break;
case vm_addr: std::clog << "| addr | 0x" << std::hex
<< reinterpret_cast<u64>(val.addr())
<< std::dec; break;
case vm_cnt: std::clog << "| cnt | " << val.cnt(); break;
case vm_nil: std::clog << "| nil |"; break;
case vm_num: std::clog << "| num | " << val.num(); break;
case vm_str: std::clog << "| str | <0x" << std::hex << p
<< "> " << rawstr(val.str(), 16)
<< std::dec; break;
case vm_func: std::clog << "| func | <0x" << std::hex << p
<< "> entry:0x" << val.func().entry
<< std::dec; break;
case vm_upval:std::clog << "| upval| <0x" << std::hex << p
<< std::dec << "> [" << val.upval().size
<< " val]"; break;
case vm_vec: std::clog << "| vec | <0x" << std::hex << p
<< std::dec << "> [" << val.vec().size()
<< " val]"; break;
case vm_hash: std::clog << "| hash | <0x" << std::hex << p
<< std::dec << "> {" << val.hash().size()
<< " val}"; break;
case vm_obj: std::clog << "| obj | <0x" << std::hex << p
<< "> obj:0x"
<< reinterpret_cast<u64>(val.obj().ptr)
<< std::dec; break;
case vm_co: std::clog << "| co | <0x" << std::hex << p
<< std::dec << "> coroutine"; break;
case 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;
case vm_type::vm_none: std::clog << "null "; break;
case vm_type::vm_ret: std::clog << "ret "; break;
case vm_type::vm_addr: std::clog << "addr "; break;
case vm_type::vm_cnt: std::clog << "cnt "; break;
case vm_type::vm_nil: std::clog << "nil "; break;
case vm_type::vm_num: std::clog << "num "; break;
case vm_type::vm_str: std::clog << "str "; break;
case vm_type::vm_func: std::clog << "func "; break;
case vm_type::vm_upval: std::clog << "upval"; break;
case vm_type::vm_vec: std::clog << "vec "; break;
case vm_type::vm_hash: std::clog << "hash "; break;
case vm_type::vm_ghost: std::clog << "ghost"; break;
case vm_type::vm_co: std::clog << "co "; break;
case vm_type::vm_map: std::clog << "map "; break;
default: std::clog << "err "; break;
}
std::clog << " | ";
}
void vm::value_info(var& val) {
value_name_form(val);
switch(val.type) {
case vm_type::vm_none: break;
case vm_type::vm_ret: return_address_info(val); break;
case vm_type::vm_addr: memory_address_info(val); break;
case vm_type::vm_cnt: std::clog << val.cnt(); break;
case vm_type::vm_nil: break;
case vm_type::vm_num: std::clog << val.num(); break;
case vm_type::vm_str: raw_string_info(val); break;
case vm_type::vm_func: std::clog << val.func(); break;
case vm_type::vm_upval: upvalue_info(val); break;
case vm_type::vm_vec: vector_value_info(val); break;
case vm_type::vm_hash: hash_value_info(val, 4); break;
case vm_type::vm_ghost: std::clog << val.ghost(); break;
case vm_type::vm_co: coroutine_value_info(val); break;
case vm_type::vm_map: namespace_value_info(val, 4); break;
default: std::clog << "unknown"; break;
}
std::clog << "\n";
}
void vm::traceback() {
void vm::function_detail_info(const nas_func& func) {
std::clog << "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;
}
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() {
// no function is called when error ocurred
if (!ctx.funcr.is_func()) {
return;
}
util::windows_code_page_manager cp;
cp.set_utf8_output();
var* bottom = ctx.stack;
var* top = ctx.top;
std::stack<u32> ret;
for(var* i = bottom; i<=top; ++i) {
if (i->type==vm_ret && i->ret()!=0) {
// generate trace back
std::vector<const nas_func*> functions;
std::vector<u64> callsite;
var* prev_func = &ctx.funcr;
functions.push_back(&prev_func->func());
for (var* i = top; i >= bottom; i--) {
// +-------+------------------+
// | ret | 0x3bf | <-- i + 1 (should not be 0, except coroutine)
// +-------+------------------+
// | addr | 0x7ff5f61ae020 | <-- i
// +-------+------------------+
// | upval | ... | <-- i - 1
// +-------+------------------+
// | locals| ... |
// +-------+------------------+
// | func | function | <-- i - 1 - prev_func->local_size - 1
// +-------+------------------+
if (i + 1 <= top && i[0].is_addr() && i[1].is_ret()) {
auto r_addr = i[1].ret();
callsite.push_back(r_addr);
i--;
i -= prev_func->func().local_size;
i--;
if (i >= bottom && i[0].is_func()) {
prev_func = i;
functions.push_back(&prev_func->func());
}
}
}
std::clog << "\ncall trace ";
std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n";
std::clog << " crash occurred at\n ";
function_detail_info(ctx.funcr.func());
std::clog << " at " << files[bytecode[ctx.pc].fidx] << ":";
std::clog << bytecode[ctx.pc].line << "\n";
if (callsite.empty()) {
cp.restore_code_page();
return;
}
const nas_func* prev = nullptr;
u64 prev_addr = 0;
u64 same_call_count = 0;
for (int i = 0; i < functions.size(); ++i) {
if (functions[i] == prev && callsite[i] == prev_addr) {
same_call_count++;
continue;
} else if (same_call_count) {
std::clog << " `--> " << same_call_count << " same call(s)\n";
same_call_count = 0;
}
// in coroutine
if (callsite[i] == 0 && ngc.cort) {
std::clog << " call by coroutine\n";
break;
}
std::clog << " call ";
function_detail_info(*functions[i]);
auto r_addr = callsite[i];
std::clog << " from " << files[bytecode[r_addr].fidx] << ":";
std::clog << bytecode[r_addr].line << "\n";
prev = functions[i];
prev_addr = r_addr;
}
if (same_call_count) {
std::clog << " `--> " << same_call_count << " same call(s)\n";
}
cp.restore_code_page();
}
void vm::trace_back() {
// generate trace back
std::stack<u64> 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 << "trace back ("
<< (ngc.cort? "coroutine":"main")
<< ")\n";
codestream::set(cnum, cstr, native.data(), files);
for(u32 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) {
// store the position program crashed
ret.push(ctx.pc);
std::clog << "\nback trace ";
std::clog << (ngc.cort? "(coroutine)":"(main)") << "\n";
codestream::set(
const_number,
const_string,
global_symbol_name,
native_function.data(),
files
);
for (u64 p = 0, same = 0, prev = 0xffffffff; !ret.empty(); prev = p, ret.pop()) {
if ((p = ret.top())==prev) {
++same;
continue;
}
if (same) {
} else if (same) {
std::clog << " 0x" << std::hex
<< std::setw(6) << std::setfill('0')
<< prev << std::dec << " "
<< std::setw(8) << std::setfill('0')
<< prev << std::dec << " "
<< same << " same call(s)\n";
same = 0;
}
same = 0;
std::clog << " " << codestream(bytecode[p], p) << "\n";
}
// the first called place has no same calls
}
void vm::stackinfo(const u32 limit = 10) {
void vm::stack_info(const u64 limit) {
var* top = ctx.top;
var* bottom = ctx.stack;
std::clog << "stack (0x" << std::hex << reinterpret_cast<u64>(bottom);
std::clog << std::dec << ", limit " << limit << ", total ";
const auto stack_address = reinterpret_cast<u64>(bottom);
std::clog << "\nstack (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) {
for (u32 i = 0; i<limit && top>=bottom; ++i, --top) {
std::clog << " 0x" << std::hex
<< std::setw(6) << std::setfill('0')
<< std::setw(8) << std::setfill('0')
<< static_cast<u64>(top-bottom) << std::dec
<< " ";
valinfo(top[0]);
value_info(top[0]);
}
}
void vm::reginfo() {
std::clog << "registers (" << (ngc.cort? "coroutine":"main")
<< ")\n" << std::hex
<< " [pc ] | pc | 0x" << ctx.pc << "\n"
<< " [global] | addr | 0x"
void vm::register_info() {
std::clog << "\nregister (" << (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"
<< " [ local ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.localr) << "\n"
<< " [memr ] | addr | 0x"
<< " [ memr ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.memr) << "\n"
<< " [canary] | addr | 0x"
<< " [ canary ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.canary) << "\n"
<< " [top ] | addr | 0x"
<< " [ top ] | addr | 0x"
<< reinterpret_cast<u64>(ctx.top) << "\n"
<< std::dec;
std::clog << " [funcr ] "; valinfo(ctx.funcr);
std::clog << " [upval ] "; valinfo(ctx.upvalr);
std::clog << " [ funcr ] "; value_info(ctx.funcr);
std::clog << " [ upval ] "; value_info(ctx.upvalr);
}
void vm::gstate() {
if (!global_size || global[0].type==vm_none) {
void vm::global_state() {
if (!global_size || global[0].is_none()) {
return;
}
std::clog << "global (0x" << std::hex
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(6)
<< std::setfill('0') << i << std::dec
for (usize i = 0; i<global_size; ++i) {
std::clog << " 0x" << std::hex << std::setw(8)
<< std::setfill('0') << static_cast<u64>(i) << std::dec
<< " ";
valinfo(global[i]);
auto name = global_symbol_name[i];
if (name.length()>=10) {
name = name.substr(0, 7) + "...";
} else {
}
std::clog << "| " << std::left << std::setw(10)
<< std::setfill(' ') << name << " "
<< std::internal;
value_info(global[i]);
}
}
void vm::lstate() {
if (!ctx.localr || !ctx.funcr.func().lsize) {
void vm::local_state() {
if (!ctx.localr || !ctx.funcr.func().local_size) {
return;
}
const u32 lsize = ctx.funcr.func().lsize;
std::clog << "local (0x" << std::hex << reinterpret_cast<u64>(ctx.localr)
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(6)
for (u32 i = 0; i<lsize; ++i) {
std::clog << " 0x" << std::hex << std::setw(8)
<< std::setfill('0') << i << std::dec
<< " ";
valinfo(ctx.localr[i]);
value_info(ctx.localr[i]);
}
}
void vm::ustate() {
if (ctx.funcr.type==vm_nil || ctx.funcr.func().upval.empty()) {
void vm::upvalue_state() {
if (ctx.funcr.is_nil() || ctx.funcr.func().upval.empty()) {
return;
}
std::clog << "upvalue\n";
std::clog << "\nupvalue\n";
auto& upval = ctx.funcr.func().upval;
for(u32 i = 0; i<upval.size(); ++i) {
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(6)
for (u32 j = 0; j<uv.size; ++j) {
std::clog << " 0x" << std::hex << std::setw(8)
<< std::setfill('0') << j << std::dec
<< " ";
valinfo(uv[j]);
value_info(uv[j]);
}
}
}
void vm::detail() {
reginfo();
gstate();
lstate();
ustate();
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";
traceback();
stackinfo();
const auto& file = files[bytecode[ctx.pc].fidx];
const auto line = bytecode[ctx.pc].line;
std::cerr << "[vm] error occurred at " << file << ":" << line << ": ";
std::cerr << str << "\n";
function_call_trace();
// trace back contains bytecode info, dump in verbose mode
if (verbose) {
trace_back();
}
// verbose will dump more values on stack
if (verbose) {
stack_info(64);
}
// show verbose crash info
if (verbose) {
detail();
all_state_detail();
}
if (!ngc.cort) {
if (!verbose) {
std::cerr << "\n[vm] use <-d> for detailed crash info.\n\n";
}
// 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.ctxreserve(); // switch context to main
ctx.top[0] = nil; // generate return value 'nil'
}
// 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
) {
init(gen.strs(), gen.nums(), gen.natives(),
gen.codes(), gen.globals(), linker.get_file_list(), argv);
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
// interrupt check macro for computed goto mode.
#define CHECK_INTERRUPT { \
if (interrupt_ptr && interrupt_ptr->load()) { \
throw std::runtime_error("VM execution interrupted by timeout"); \
} \
}
// using labels as values/computed goto
const void* oprs[] = {
&&vmexit, &&repl, &&intl, &&loadg,
&&loadl, &&loadu, &&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
&&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(auto& i : gen.codes()) {
for (const auto& i : gen.codes()) {
code.push_back(oprs[i.op]);
imm.push_back(i.num);
}
CHECK_INTERRUPT;
// goto the first operand
goto *code[ctx.pc];
#else
typedef void (vm::*nafunc)();
const nafunc oprs[] = {
nullptr, &vm::o_repl,
&vm::o_intl, &vm::o_loadg,
&vm::o_loadl, &vm::o_loadu,
&vm::o_pnum, &vm::o_pnil,
&vm::o_pstr, &vm::o_newv,
&vm::o_newh, &vm::o_newf,
&vm::o_happ, &vm::o_para,
&vm::o_deft, &vm::o_dyn,
&vm::o_lnot, &vm::o_usub,
&vm::o_bnot, &vm::o_btor,
&vm::o_btxor, &vm::o_btand,
&vm::o_add, &vm::o_sub,
&vm::o_mul, &vm::o_div,
&vm::o_lnk, &vm::o_addc,
&vm::o_subc, &vm::o_mulc,
&vm::o_divc, &vm::o_lnkc,
&vm::o_addeq, &vm::o_subeq,
&vm::o_muleq, &vm::o_diveq,
&vm::o_lnkeq, &vm::o_bandeq,
&vm::o_boreq, &vm::o_bxoreq,
&vm::o_addeqc, &vm::o_subeqc,
&vm::o_muleqc, &vm::o_diveqc,
&vm::o_lnkeqc, &vm::o_addecp,
&vm::o_subecp, &vm::o_mulecp,
&vm::o_divecp, &vm::o_lnkecp,
&vm::o_meq, &vm::o_eq,
&vm::o_neq, &vm::o_less,
&vm::o_leq, &vm::o_grt,
&vm::o_geq, &vm::o_lessc,
&vm::o_leqc, &vm::o_grtc,
&vm::o_geqc, &vm::o_pop,
&vm::o_jmp, &vm::o_jt,
&vm::o_jf, &vm::o_cnt,
&vm::o_findex, &vm::o_feach,
&vm::o_callg, &vm::o_calll,
&vm::o_upval, &vm::o_callv,
&vm::o_callvi, &vm::o_callh,
&vm::o_callfv, &vm::o_callfh,
&vm::o_callb, &vm::o_slcbeg,
&vm::o_slcend, &vm::o_slc,
&vm::o_slc2, &vm::o_mcallg,
&vm::o_mcalll, &vm::o_mupval,
&vm::o_mcallv, &vm::o_mcallh,
&vm::o_ret
};
std::vector<nafunc> code;
for(auto& i : gen.codes()) {
code.push_back(oprs[i.op]);
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]) {
while (code[ctx.pc]) {
if (interrupt_ptr && interrupt_ptr->load()) {
throw std::runtime_error("VM execution interrupted by timeout");
}
(this->*code[ctx.pc])();
if (ctx.top>=ctx.canary) {
die("stack overflow");
@@ -350,10 +712,10 @@ void vm::run(
++ctx.pc;
}
#endif
// all nasal programs should end here
vmexit:
if (verbose) {
ngc.info();
ngc.status.dump_info();
}
imm.clear();
if (!is_repl_mode) {
@@ -362,17 +724,19 @@ vmexit:
return;
#ifndef _MSC_VER
// may cause stackoverflow
// IR which may cause stackoverflow
#define exec_check(op) {\
op();\
CHECK_INTERRUPT;\
if (ctx.top<ctx.canary)\
goto *code[++ctx.pc];\
die("stack overflow");\
goto *code[++ctx.pc];\
}
// do not cause stackoverflow
// IR which does not cause stackoverflow
#define exec_nodie(op) {\
op();\
CHECK_INTERRUPT;\
goto *code[++ctx.pc];\
}
@@ -381,6 +745,7 @@ 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

File diff suppressed because it is too large Load Diff

399
src/nasal_web.cpp Normal file
View File

@@ -0,0 +1,399 @@
#include "nasal_web.h"
#include "nasal_vm.h"
#include "nasal_parse.h"
#include "nasal_codegen.h"
#include "nasal_import.h"
#include "optimizer.h"
#include "nasal_err.h"
#include "nasal_lexer.h"
#include "repl/repl.h"
#include <string>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <cstdio>
#include <stdexcept>
#include <chrono>
#include <vector>
#include <future>
#include <atomic>
namespace {
// Helper function implementations inside anonymous namespace
std::vector<std::string> split_string(const std::string& str, char delim) {
std::vector<std::string> result;
std::stringstream ss(str);
std::string item;
while (std::getline(ss, item, delim)) {
result.push_back(item);
}
return result;
}
std::string join_string(const std::vector<std::string>& vec, const std::string& delim) {
if (vec.empty()) return "";
std::stringstream ss;
ss << vec[0];
for (size_t i = 1; i < vec.size(); ++i) {
ss << delim << vec[i];
}
return ss.str();
}
}
struct NasalContext {
std::unique_ptr<nasal::vm> vm_instance;
std::string last_result;
std::string last_error;
std::chrono::seconds timeout{5}; // Default 5 second timeout
std::atomic<bool> interrupted{false};
NasalContext() {
vm_instance = std::make_unique<nasal::vm>();
vm_instance->set_interrupt_ptr(&interrupted);
}
~NasalContext() {
vm_instance.reset(); // Reset explicitly
}
};
struct WebReplContext {
std::unique_ptr<nasal::repl::repl> repl_instance;
std::vector<std::string> source;
std::string last_result;
std::string last_error;
bool allow_output;
bool initialized;
std::chrono::seconds timeout{1}; // Default 1 second timeout
WebReplContext() : allow_output(false), initialized(false) {
repl_instance = std::make_unique<nasal::repl::repl>();
}
};
void* nasal_init() {
return new NasalContext();
}
void nasal_cleanup(void* context) {
auto* ctx = static_cast<NasalContext*>(context);
ctx->vm_instance.reset();
delete ctx;
}
// Add new function to set timeout
void nasal_set_timeout(void* context, int seconds) {
auto* ctx = static_cast<NasalContext*>(context);
ctx->timeout = std::chrono::seconds(seconds);
}
const char* nasal_eval(void* context, const char* code, int show_time) {
using clk = std::chrono::high_resolution_clock;
const auto den = clk::duration::period::den;
auto* ctx = static_cast<NasalContext*>(context);
try {
nasal::lexer lex;
nasal::parse parse;
nasal::linker ld;
nasal::codegen gen;
// Create a unique temporary file
char temp_filename[256];
snprintf(temp_filename, sizeof(temp_filename), "/tmp/nasal_eval_%ld_XXXXXX", std::time(nullptr));
int fd = mkstemp(temp_filename);
if (fd == -1) {
throw std::runtime_error("Failed to create temporary file");
}
// Write the code to the temporary file
std::ofstream temp_file(temp_filename);
if (!temp_file.is_open()) {
close(fd);
throw std::runtime_error("Failed to open temporary file for writing");
}
temp_file << code;
temp_file.close();
close(fd);
// Capture stdout and stderr
std::stringstream output;
std::stringstream error_output;
auto old_cout = std::cout.rdbuf(output.rdbuf());
auto old_cerr = std::cerr.rdbuf(error_output.rdbuf());
// Process the code
if (lex.scan(std::string(temp_filename)).geterr()) {
ctx->last_error = error_output.str();
std::cout.rdbuf(old_cout);
std::cerr.rdbuf(old_cerr);
std::remove(temp_filename);
return ctx->last_error.c_str();
}
if (parse.compile(lex).geterr()) {
ctx->last_error = error_output.str();
std::cout.rdbuf(old_cout);
std::cerr.rdbuf(old_cerr);
std::remove(temp_filename);
return ctx->last_error.c_str();
}
if (ld.link(parse, false).geterr()) {
ctx->last_error = error_output.str();
std::cout.rdbuf(old_cout);
std::cerr.rdbuf(old_cerr);
std::remove(temp_filename);
return ctx->last_error.c_str();
}
auto opt = std::make_unique<nasal::optimizer>();
opt->do_optimization(parse.tree());
if (gen.compile(parse, ld, false, true).geterr()) {
ctx->last_error = error_output.str();
std::cout.rdbuf(old_cout);
std::cerr.rdbuf(old_cerr);
std::remove(temp_filename);
return ctx->last_error.c_str();
}
const auto start = show_time ? clk::now() : clk::time_point();
// Create a future for the VM execution
auto future = std::async(std::launch::async, [&]() {
// Wrap VM execution in try/catch
try {
ctx->vm_instance->run(gen, ld, {});
} catch (const std::exception& e) {
ctx->last_error = e.what();
throw std::runtime_error(ctx->last_error);
} catch (...) {
ctx->last_error = "Unknown error in VM run()";
throw std::runtime_error(ctx->last_error);
}
});
// Wait for completion or timeout
auto status = future.wait_for(ctx->timeout);
if (status == std::future_status::timeout) {
ctx->interrupted.store(true);
std::remove(temp_filename);
throw std::runtime_error("Execution timed out after " +
std::to_string(ctx->timeout.count()) + " seconds");
}
const auto end = show_time ? clk::now() : clk::time_point();
std::cout.rdbuf(old_cout);
std::cerr.rdbuf(old_cerr);
std::stringstream result;
result << output.str();
if (!error_output.str().empty()) {
result << error_output.str();
}
if (result.str().empty()) {
result << "Execution completed successfully.\n";
}
if (show_time) {
double execution_time = static_cast<double>((end-start).count())/den;
result << "\nExecution time: " << execution_time << "s";
}
ctx->last_result = result.str();
std::remove(temp_filename);
return ctx->last_result.c_str();
} catch (const std::exception& e) {
ctx->last_error = e.what();
return ctx->last_error.c_str();
}
}
const char* nasal_get_error(void* context) {
auto* ctx = static_cast<NasalContext*>(context);
return ctx->last_error.c_str();
}
void* nasal_repl_init() {
auto* ctx = new WebReplContext();
try {
// Initialize environment silently
nasal::repl::info::instance()->in_repl_mode = true;
ctx->repl_instance->get_runtime().set_repl_mode_flag(true);
ctx->repl_instance->get_runtime().set_detail_report_info(false);
// Run initial setup
ctx->repl_instance->set_source({});
if (!ctx->repl_instance->run()) {
ctx->last_error = "Failed to initialize REPL environment";
return ctx;
}
// Enable output after initialization
ctx->allow_output = true;
ctx->repl_instance->get_runtime().set_allow_repl_output_flag(true);
ctx->initialized = true;
} catch (const std::exception& e) {
ctx->last_error = std::string("Initialization error: ") + e.what();
}
return ctx;
}
void nasal_repl_cleanup(void* context) {
delete static_cast<WebReplContext*>(context);
}
// Add new function to set REPL timeout
void nasal_repl_set_timeout(void* context, int seconds) {
auto* ctx = static_cast<WebReplContext*>(context);
ctx->timeout = std::chrono::seconds(seconds);
}
const char* nasal_repl_eval(void* context, const char* line) {
auto* ctx = static_cast<WebReplContext*>(context);
if (!ctx->initialized) {
ctx->last_error = "REPL not properly initialized";
return ctx->last_error.c_str();
}
try {
std::string input_line(line);
// Handle empty input
if (input_line.empty()) {
ctx->last_result = "";
return ctx->last_result.c_str();
}
// Handle REPL commands
if (input_line[0] == '.') {
if (input_line == ".help" || input_line == ".h") {
ctx->last_result =
"Nasal REPL commands:\n"
" .help .h show this help message\n"
" .clear .c clear screen\n"
" .exit .e exit repl\n"
" .quit .q exit repl\n"
" .source .s show source\n";
return ctx->last_result.c_str();
}
else if (input_line == ".clear" || input_line == ".c") {
ctx->last_result = "\033c"; // Special marker for clear screen
return ctx->last_result.c_str();
}
else if (input_line == ".exit" || input_line == ".e" ||
input_line == ".quit" || input_line == ".q") {
ctx->last_result = "__EXIT__"; // Special marker for exit
return ctx->last_result.c_str();
}
else if (input_line == ".source" || input_line == ".s") {
// Return accumulated source
ctx->last_result = ctx->source.empty() ?
"(no source)" :
join_string(ctx->source, "\n");
return ctx->last_result.c_str();
}
else {
ctx->last_error = "no such command \"" + input_line + "\", input \".help\" for help";
return ctx->last_error.c_str();
}
}
// Add the line to source
ctx->source.push_back(input_line);
// Capture output
std::stringstream output;
auto old_cout = std::cout.rdbuf(output.rdbuf());
auto old_cerr = std::cerr.rdbuf(output.rdbuf());
// Create a copy of the source for the async task
auto source_copy = ctx->source;
// Create a future for the REPL execution using the existing instance
auto future = std::async(std::launch::async, [repl = ctx->repl_instance.get(), source_copy]() {
repl->get_runtime().set_repl_mode_flag(true);
repl->get_runtime().set_allow_repl_output_flag(true);
repl->set_source(source_copy);
return repl->run();
});
// Wait for completion or timeout
auto status = future.wait_for(ctx->timeout);
// Restore output streams first
std::cout.rdbuf(old_cout);
std::cerr.rdbuf(old_cerr);
if (status == std::future_status::timeout) {
ctx->source.pop_back(); // Remove the line that caused timeout
// Reset the REPL instance state
ctx->repl_instance->get_runtime().set_repl_mode_flag(true);
ctx->repl_instance->get_runtime().set_allow_repl_output_flag(true);
ctx->repl_instance->set_source(ctx->source);
throw std::runtime_error("Execution timed out after " +
std::to_string(ctx->timeout.count()) + " seconds");
}
bool success = future.get();
std::string result = output.str();
if (!success) {
ctx->last_error = result;
ctx->source.pop_back(); // Remove failed line
return ctx->last_error.c_str();
}
ctx->last_result = result;
return ctx->last_result.c_str();
} catch (const std::exception& e) {
ctx->last_error = std::string("Error: ") + e.what();
ctx->source.pop_back(); // Remove failed line
return ctx->last_error.c_str();
}
}
int nasal_repl_is_complete(void* context, const char* line) {
auto* ctx = static_cast<WebReplContext*>(context);
if (!ctx->initialized) {
return -1; // Error state
}
// Handle empty input
if (!line || strlen(line) == 0) {
return 0; // Complete
}
// Handle REPL commands
if (line[0] == '.') {
return 0; // Commands are always complete
}
// Create a temporary source vector with existing source plus new line
std::vector<std::string> temp_source = ctx->source;
temp_source.push_back(line);
// Use the REPL's check_need_more_input method
int result = ctx->repl_instance->check_need_more_input(temp_source);
return result; // Ensure a return value is provided
}
// Add this function to expose version info
const char* nasal_repl_get_version() {
static std::string version_info =
std::string("version ") + __nasver__ +
" (" + __DATE__ + " " + __TIME__ + ")";
return version_info.c_str();
}

29
src/nasal_web.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef __NASAL_WEB_H__
#define __NASAL_WEB_H__
#include "nasal.h"
#ifdef __cplusplus
extern "C" {
#endif
// Main API functions
NASAL_EXPORT void* nasal_init();
NASAL_EXPORT void nasal_cleanup(void* context);
NASAL_EXPORT void nasal_set_timeout(void* context, int seconds);
NASAL_EXPORT const char* nasal_eval(void* context, const char* code, int show_time);
NASAL_EXPORT const char* nasal_get_error(void* context);
// REPL
NASAL_EXPORT void* nasal_repl_init();
NASAL_EXPORT void nasal_repl_cleanup(void* repl_context);
NASAL_EXPORT void nasal_repl_set_timeout(void* repl_context, int seconds);
NASAL_EXPORT const char* nasal_repl_eval(void* repl_context, const char* line);
NASAL_EXPORT int nasal_repl_is_complete(void* repl_context, const char* line);
NASAL_EXPORT const char* nasal_repl_get_version();
#ifdef __cplusplus
}
#endif
#endif

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[];
}

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

@@ -0,0 +1,857 @@
#include "natives/builtin.h"
#include "util/util.h"
#include <chrono>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/ioctl.h>
#include <unistd.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 separator = local[1];
var str = local[2];
if (!separator.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& sep = separator.str();
const auto& s = str.str();
// avoid being sweeped
auto res = ngc->temp = ngc->alloc(vm_type::vm_vec);
auto& vec = res.vec().elems;
// empty separator means split every char
if (!sep.length()) {
for (auto i : s) {
vec.push_back(ngc->newstr(i));
}
ngc->temp = nil;
return res;
}
usize last = 0;
usize pos = s.find(sep, 0);
while (pos!=std::string::npos) {
if (pos>last) {
vec.push_back(ngc->newstr(s.substr(last, pos-last)));
}
last = pos + sep.length();
pos = s.find(sep, last);
}
if (last!=s.length()) {
vec.push_back(ngc->newstr(s.substr(last)));
}
ngc->temp = nil;
return res;
}
var builtin_split_with_empty_substr(context* ctx, gc* ngc) {
auto local = ctx->localr;
var separator = local[1];
var str = local[2];
if (!separator.is_str()) {
return nas_err(
"native::split_with_empty_substr",
"\"separator\" must be string"
);
}
if (!str.is_str()) {
return nas_err(
"native::split_with_empty_substr",
"\"str\" must be string"
);
}
const auto& sep = separator.str();
const auto& s = str.str();
// avoid being sweeped
auto res = ngc->temp = ngc->alloc(vm_type::vm_vec);
auto& vec = res.vec().elems;
// empty separator means split every char
if (!sep.length()) {
for (auto i : s) {
vec.push_back(ngc->newstr(i));
}
ngc->temp = nil;
return res;
}
usize last = 0;
usize pos = s.find(sep, 0);
while (pos!=std::string::npos) {
if (pos>=last) {
vec.push_back(ngc->newstr(s.substr(last, pos-last)));
}
last = pos + sep.length();
pos = s.find(sep, 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_version(context* ctx, gc* ngc) {
return ngc->newstr(__nasver__);
}
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 = object.ghost().get<time_stamp>();
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 = object.ghost().get<time_stamp>();
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 = object.ghost().get<time_stamp>();
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) {
var res = ngc->alloc(vm_type::vm_hash);
auto& map = res.hash().elems;
// using ms
map["total"] = var::num(ngc->status.gc_time_ms());
map["average"] = var::num(ngc->status.avg_time_ms());
map["mark_count"] = var::num(ngc->status.total_mark_count);
map["sweep_count"] = var::num(ngc->status.total_sweep_count);
map["avg_mark"] = var::num(ngc->status.avg_mark_time_ms());
map["avg_sweep"] = var::num(ngc->status.avg_sweep_time_ms());
map["max_mark"] = var::num(ngc->status.max_mark_time_ms());
map["max_sweep"] = var::num(ngc->status.max_sweep_time_ms());
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 << arg.ghost().convert<u64>() << 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;
}
var builtin_terminal_size(context* ctx, gc* ngc) {
var res = ngc->alloc(vm_type::vm_hash);
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) {
auto rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
auto cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
res.hash().elems["rows"] = var::num(rows);
res.hash().elems["cols"] = var::num(cols);
}
#else
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
res.hash().elems["rows"] = var::num(w.ws_row);
res.hash().elems["cols"] = var::num(w.ws_col);
#endif
return res;
}
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},
{"__split_with_empty_substr", builtin_split_with_empty_substr},
{"__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},
{"__version", builtin_version},
{"__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},
{"__terminal_size", builtin_terminal_size},
{nullptr, nullptr}
};
}

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

@@ -0,0 +1,99 @@
#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_split_with_empty_substr(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*);
var builtin_version(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*);
var builtin_terminal_size(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[];
}

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

@@ -0,0 +1,217 @@
#include "natives/dylib_lib.h"
#include "util/util.h"
#include "util/fs.h"
#include <cstdlib>
#include <vector>
namespace nasal {
const auto dynamic_library_type_name = "nasal::dynamic_library";
const auto function_address_type_name = "nasal::function_address";
void dynamic_library_destructor(void* pointer) {
#ifdef _WIN32
FreeLibrary(static_cast<HMODULE>(pointer));
#else
dlclose(pointer);
#endif
}
std::vector<std::string> possible_dylib_path() {
const auto env_path = std::string(getenv("PATH"));
const auto sep = (util::is_windows()? ";":":");
const auto path_front = util::is_windows()? "\\module\\":"/module/";
// do split string
std::vector<std::string> env_path_vec = {"."};
usize last = 0;
usize pos = env_path.find(sep, 0);
while (pos != std::string::npos) {
if (pos > last) {
env_path_vec.push_back(env_path.substr(last, pos - last));
}
last = pos + 1;
pos = env_path.find(sep, last);
}
if (last != env_path.length()) {
env_path_vec.push_back(env_path.substr(last));
}
for (auto& p : env_path_vec) {
p += path_front;
}
return env_path_vec;
}
std::string search_dynamic_library_path(const std::string& dlname) {
const auto ext = (util::is_windows()? ".dll":".so");
const auto lib_path = (util::is_windows()? ".\\":"./") + dlname + ext;
if (fs::exists(lib_path)) {
return lib_path;
}
// macos may use .dylib as extension
if (util::is_macos()) {
const auto dylib_path = "./" + dlname + ".dylib";
if (fs::exists(dylib_path)) {
return dylib_path;
}
}
// search library in PATH
const auto possible_path = possible_dylib_path();
for (const auto& p : possible_path) {
const auto env_p = p + lib_path;
if (fs::exists(env_p)) {
return env_p;
}
}
// macos may use .dylib as extension
if (util::is_macos()) {
const auto dylib_path = "./" + dlname + ".dylib";
for (const auto& p : possible_path) {
const auto env_p = p + dylib_path;
if (fs::exists(env_p)) {
return env_p;
}
}
}
return "";
}
var builtin_dlopen(context* ctx, gc* ngc) {
auto dl = ctx->localr[1];
if (!dl.is_str()) {
return nas_err("dylib::dlopen", "\"libname\" must be string");
}
const auto dlname = search_dynamic_library_path(dl.str());
if (dlname.empty()) {
return nas_err("dylib::dlopen",
"cannot find dynamic library <" + dl.str() + ">"
);
}
// get library pointer
#ifdef _WIN32
wchar_t* wide_string = new wchar_t[dlname.size()+1];
if (!wide_string) {
return nas_err("dylib::dlopen", "malloc failed");
}
memset(wide_string, 0, sizeof(wchar_t) * dlname.size() + 1);
mbstowcs(wide_string, dlname.c_str(), dlname.size() + 1);
// load library by using wide string name
void* dynamic_library_pointer = LoadLibraryA(dlname.c_str());
delete []wide_string;
#else
void* dynamic_library_pointer = dlopen(
dlname.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 <" + dl.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(
library_object.ghost().convert<HMODULE>(), "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 function_object.ghost().convert<module_func>()(
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 function_object.ghost().convert<module_func>()(
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}
};
}

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

@@ -0,0 +1,32 @@
#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
#include <cstring>
#include <sstream>
#include <vector>
namespace nasal {
void dynamic_library_destructor(void*);
std::vector<std::string> possible_dylib_path();
std::string search_dynamic_library_path(const std::string&);
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[];
}

View File

@@ -1,14 +1,15 @@
#include "fg_props.h"
#include "natives/fg_props.h"
#include <fstream>
namespace nasal {
var builtin_logprint(var* local, gc& ngc) {
var level = local[1];
var elems = local[2];
if (elems.type!=vm_vec) {
return nas_err("logprint", "received argument is not vector.");
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())) {
@@ -21,13 +22,12 @@ var builtin_logprint(var* local, gc& ngc) {
case SG_DEV_ALERT: out << "[DEV_ALERT]"; break;
case SG_MANDATORY_INFO: out << "[MANDATORY_INFO]"; break;
default:
return nas_err("logprint",
"incorrect log level " +
std::to_string(level.num())
return nas_err("fg_env::logprint",
"incorrect log level " + std::to_string(level.num())
);
}
for(auto& i : elems.vec().elems) {
out << i << " ";
for (auto& value : elems.vec().elems) {
out << value << " ";
}
out << "\n";
return nil;

View File

@@ -2,7 +2,7 @@
#include "nasal.h"
#include "nasal_gc.h"
#include "nasal_builtin.h"
#include "natives/builtin.h"
namespace nasal {
@@ -15,7 +15,7 @@ namespace nasal {
#define SG_DEV_ALERT 8
#define SG_MANDATORY_INFO 9
var builtin_logprint(var*, gc&);
var builtin_logprint(context*, gc*);
extern nasal_builtin_table flight_gear_native[];

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

@@ -0,0 +1,251 @@
#include "natives/io_lib.h"
#include "util/fs.h"
#include <fstream>
#include <sys/stat.h>
namespace nasal {
const auto file_type_name = "nasal::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 failed to open, just return nil for check
if (!file_descriptor) {
return nil;
}
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(),
file_descriptor.ghost().get<FILE>()
);
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(),
file_descriptor.ghost().get<FILE>()
)));
}
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(
file_descriptor.ghost().get<FILE>(),
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(file_descriptor.ghost().get<FILE>())
));
}
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(file_descriptor.ghost().get<FILE>()))!=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 failed to stat, return nil
if (stat(name.str().c_str(), &buffer) < 0) {
return nil;
}
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(file_descriptor.ghost().get<FILE>())
));
}
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 = json_object.ghost().get<json>();
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 = json_object.ghost().get<json>();
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 = json_object.ghost().get<json>();
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[];
}

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