From 3818bdcad86455bfb2ae482182836010a3549e50 Mon Sep 17 00:00:00 2001 From: cloudfreexiao <996442717qqcom@gmail.com> Date: Sun, 4 Jul 2021 21:50:17 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=B3=20chore(=E5=B7=A5=E5=85=B7):=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20=E5=AE=A2=E6=88=B7=E7=AB=AF=20=E6=A8=A1?= =?UTF-8?q?=E6=8B=9F=E5=B7=A5=E5=85=B7=20=E4=BB=A5=E5=8F=8Agoscon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 3 + docs/README.md | 8 +- framework/3rd/goscon | 1 + framework/3rd/readme.txt | 0 framework/3rd/termbox_next/.gitignore | 7 + framework/3rd/termbox_next/license | 19 + framework/3rd/termbox_next/makefile | 40 + framework/3rd/termbox_next/readme.md | 57 + .../3rd/termbox_next/src/demo/keyboard.c | 827 ++++++++ framework/3rd/termbox_next/src/demo/makefile | 30 + framework/3rd/termbox_next/src/demo/output.c | 156 ++ framework/3rd/termbox_next/src/demo/paint.c | 183 ++ .../3rd/termbox_next/src/demo/truecolor.c | 69 + framework/3rd/termbox_next/src/input.c | 319 +++ framework/3rd/termbox_next/src/memstream.c | 36 + framework/3rd/termbox_next/src/memstream.h | 20 + framework/3rd/termbox_next/src/ringbuffer.c | 195 ++ framework/3rd/termbox_next/src/ringbuffer.h | 26 + framework/3rd/termbox_next/src/term.c | 412 ++++ framework/3rd/termbox_next/src/term.h | 38 + framework/3rd/termbox_next/src/termbox.c | 885 ++++++++ framework/3rd/termbox_next/src/termbox.h | 307 +++ framework/3rd/termbox_next/src/utf8.c | 106 + framework/3rd/termbox_next/tools/astylerc | 27 + .../termbox_next/tools/collect_terminfo.py | 108 + framework/lualib-src/Makefile | 6 + framework/lualib-src/lua-ecs/Makefile | 31 + framework/lualib-src/lua-ecs/luaecs.c | 910 +++++++++ framework/lualib-src/lua-ecs/luaecs.h | 69 + framework/lualib-src/lua-rc4/.gitignore | 2 + framework/lualib-src/lua-rc4/Makefile | 26 + framework/lualib-src/lua-rc4/luabinding.c | 89 + framework/lualib-src/lua-rc4/rc4.c | 99 + framework/lualib-src/lua-rc4/rc4.h | 51 + framework/lualib-src/lua-rc4/test.lua | 53 + framework/lualib-src/lua-termfx/Makefile | 86 + framework/lualib-src/lua-termfx/mini_utf8.h | 272 +++ framework/lualib-src/lua-termfx/tbutils.c | 218 ++ framework/lualib-src/lua-termfx/tbutils.h | 102 + framework/lualib-src/lua-termfx/termfx.c | 1797 +++++++++++++++++ framework/lualib-src/lua-termfx/termfx.h | 25 + .../lualib-src/lua-termfx/termfx_color.c | 219 ++ framework/lualib-src/luasocket/.gitignore | 15 + framework/lualib-src/luasocket/README | 11 + framework/lualib-src/luasocket/luasocket.sln | 35 + framework/lualib-src/luasocket/macosx.cmd | 1 + framework/lualib-src/luasocket/makefile | 49 + framework/lualib-src/luasocket/makefile.dist | 139 ++ framework/lualib-src/luasocket/mime.vcxproj | 204 ++ framework/lualib-src/luasocket/mingw.cmd | 1 + framework/lualib-src/luasocket/socket.vcxproj | 215 ++ framework/lualib-src/luasocket/src/auxiliar.c | 154 ++ framework/lualib-src/luasocket/src/auxiliar.h | 54 + framework/lualib-src/luasocket/src/buffer.c | 270 +++ framework/lualib-src/luasocket/src/buffer.h | 52 + framework/lualib-src/luasocket/src/compat.c | 39 + framework/lualib-src/luasocket/src/compat.h | 22 + framework/lualib-src/luasocket/src/except.c | 129 ++ framework/lualib-src/luasocket/src/except.h | 46 + framework/lualib-src/luasocket/src/ftp.lua | 329 +++ .../lualib-src/luasocket/src/headers.lua | 104 + framework/lualib-src/luasocket/src/http.lua | 420 ++++ framework/lualib-src/luasocket/src/inet.c | 537 +++++ framework/lualib-src/luasocket/src/inet.h | 56 + framework/lualib-src/luasocket/src/io.c | 28 + framework/lualib-src/luasocket/src/io.h | 70 + framework/lualib-src/luasocket/src/ltn12.lua | 319 +++ .../lualib-src/luasocket/src/luasocket.c | 104 + .../lualib-src/luasocket/src/luasocket.h | 36 + framework/lualib-src/luasocket/src/makefile | 461 +++++ framework/lualib-src/luasocket/src/mbox.lua | 92 + framework/lualib-src/luasocket/src/mime.c | 852 ++++++++ framework/lualib-src/luasocket/src/mime.h | 22 + framework/lualib-src/luasocket/src/mime.lua | 89 + framework/lualib-src/luasocket/src/options.c | 454 +++++ framework/lualib-src/luasocket/src/options.h | 102 + framework/lualib-src/luasocket/src/pierror.h | 28 + framework/lualib-src/luasocket/src/select.c | 214 ++ framework/lualib-src/luasocket/src/select.h | 23 + framework/lualib-src/luasocket/src/serial.c | 171 ++ framework/lualib-src/luasocket/src/smtp.lua | 256 +++ framework/lualib-src/luasocket/src/socket.h | 73 + framework/lualib-src/luasocket/src/socket.lua | 149 ++ framework/lualib-src/luasocket/src/tcp.c | 471 +++++ framework/lualib-src/luasocket/src/tcp.h | 43 + framework/lualib-src/luasocket/src/timeout.c | 226 +++ framework/lualib-src/luasocket/src/timeout.h | 40 + framework/lualib-src/luasocket/src/tp.lua | 134 ++ framework/lualib-src/luasocket/src/udp.c | 488 +++++ framework/lualib-src/luasocket/src/udp.h | 39 + framework/lualib-src/luasocket/src/unix.c | 69 + framework/lualib-src/luasocket/src/unix.h | 26 + .../lualib-src/luasocket/src/unixdgram.c | 405 ++++ .../lualib-src/luasocket/src/unixdgram.h | 28 + .../lualib-src/luasocket/src/unixstream.c | 355 ++++ .../lualib-src/luasocket/src/unixstream.h | 29 + framework/lualib-src/luasocket/src/url.lua | 331 +++ framework/lualib-src/luasocket/src/usocket.c | 454 +++++ framework/lualib-src/luasocket/src/usocket.h | 59 + framework/lualib-src/luasocket/src/wsocket.c | 434 ++++ framework/lualib-src/luasocket/src/wsocket.h | 33 + framework/lualib-src/luasocket/vc32.bat | 3 + framework/lualib-src/luasocket/vc64.bat | 3 + framework/lualib-src/luasocket/win32.cmd | 1 + framework/lualib-src/luasocket/win64.cmd | 1 + framework/lualib/3rd/lecs/component.lua | 12 - framework/lualib/3rd/lecs/entity.lua | 87 - framework/lualib/3rd/lecs/filter.lua | 65 - framework/lualib/3rd/lecs/global.lua | 29 - framework/lualib/3rd/lecs/init.lua | 8 - framework/lualib/3rd/lecs/pool.lua | 234 --- framework/lualib/3rd/lecs/system.lua | 37 - framework/lualib/3rd/lecs/test.lua | 44 - framework/lualib/3rd/lecs/tiny_ecs.lua | 864 -------- framework/lualib/3rd/lecs/world.lua | 137 -- framework/lualib/3rd/termfx/blittest.lua | 81 + framework/lualib/3rd/termfx/colortest.lua | 115 ++ .../lualib/3rd/termfx/copyregiontest.lua | 55 + framework/lualib/3rd/termfx/printtest.lua | 61 + framework/lualib/3rd/termfx/screenshot.lua | 88 + framework/lualib/3rd/termfx/simpleui.lua | 133 ++ framework/lualib/3rd/termfx/termfxtest.lua | 254 +++ framework/lualib/3rd/zeus/ecs.lua | 169 ++ framework/service/ws_agent.lua | 45 - framework/service/ws_gate.lua | 230 --- framework/service/ws_watchdog.lua | 79 - framework/simulation/buffer_queue.lua | 256 +++ framework/simulation/conn.lua | 256 +++ framework/simulation/network.lua | 154 ++ framework/simulation/sconn.lua | 485 +++++ tools/LuaMacro/config.ld | 8 - tools/LuaMacro/docgen | 3 - tools/LuaMacro/docgen.bat | 1 - tools/LuaMacro/luam | 280 --- tools/LuaMacro/macro.lua | 713 ------- tools/LuaMacro/macro/Getter.lua | 320 --- tools/LuaMacro/macro/TokenList.lua | 201 -- tools/LuaMacro/macro/all.lua | 5 - tools/LuaMacro/macro/assert.lua | 74 - tools/LuaMacro/macro/builtin.lua | 161 -- tools/LuaMacro/macro/clexer.lua | 169 -- tools/LuaMacro/macro/do.lua | 75 - tools/LuaMacro/macro/forall.lua | 70 - tools/LuaMacro/macro/ifelse.lua | 90 - tools/LuaMacro/macro/lambda.lua | 22 - tools/LuaMacro/macro/lc.lua | 343 ---- tools/LuaMacro/macro/lexer.lua | 179 -- tools/LuaMacro/macro/lib/class.lua | 35 - tools/LuaMacro/macro/lib/test.lua | 144 -- tools/LuaMacro/macro/module.lua | 132 -- tools/LuaMacro/macro/try.lua | 47 - tools/LuaMacro/macro/with.lua | 31 - tools/LuaMacro/readme.md | 1010 --------- 153 files changed, 20137 insertions(+), 5985 deletions(-) create mode 160000 framework/3rd/goscon delete mode 100644 framework/3rd/readme.txt create mode 100755 framework/3rd/termbox_next/.gitignore create mode 100755 framework/3rd/termbox_next/license create mode 100755 framework/3rd/termbox_next/makefile create mode 100755 framework/3rd/termbox_next/readme.md create mode 100755 framework/3rd/termbox_next/src/demo/keyboard.c create mode 100755 framework/3rd/termbox_next/src/demo/makefile create mode 100755 framework/3rd/termbox_next/src/demo/output.c create mode 100755 framework/3rd/termbox_next/src/demo/paint.c create mode 100755 framework/3rd/termbox_next/src/demo/truecolor.c create mode 100755 framework/3rd/termbox_next/src/input.c create mode 100755 framework/3rd/termbox_next/src/memstream.c create mode 100755 framework/3rd/termbox_next/src/memstream.h create mode 100755 framework/3rd/termbox_next/src/ringbuffer.c create mode 100755 framework/3rd/termbox_next/src/ringbuffer.h create mode 100755 framework/3rd/termbox_next/src/term.c create mode 100755 framework/3rd/termbox_next/src/term.h create mode 100755 framework/3rd/termbox_next/src/termbox.c create mode 100755 framework/3rd/termbox_next/src/termbox.h create mode 100755 framework/3rd/termbox_next/src/utf8.c create mode 100755 framework/3rd/termbox_next/tools/astylerc create mode 100755 framework/3rd/termbox_next/tools/collect_terminfo.py create mode 100755 framework/lualib-src/lua-ecs/Makefile create mode 100755 framework/lualib-src/lua-ecs/luaecs.c create mode 100755 framework/lualib-src/lua-ecs/luaecs.h create mode 100644 framework/lualib-src/lua-rc4/.gitignore create mode 100755 framework/lualib-src/lua-rc4/Makefile create mode 100755 framework/lualib-src/lua-rc4/luabinding.c create mode 100755 framework/lualib-src/lua-rc4/rc4.c create mode 100755 framework/lualib-src/lua-rc4/rc4.h create mode 100755 framework/lualib-src/lua-rc4/test.lua create mode 100755 framework/lualib-src/lua-termfx/Makefile create mode 100755 framework/lualib-src/lua-termfx/mini_utf8.h create mode 100755 framework/lualib-src/lua-termfx/tbutils.c create mode 100755 framework/lualib-src/lua-termfx/tbutils.h create mode 100755 framework/lualib-src/lua-termfx/termfx.c create mode 100755 framework/lualib-src/lua-termfx/termfx.h create mode 100755 framework/lualib-src/lua-termfx/termfx_color.c create mode 100644 framework/lualib-src/luasocket/.gitignore create mode 100644 framework/lualib-src/luasocket/README create mode 100644 framework/lualib-src/luasocket/luasocket.sln create mode 100644 framework/lualib-src/luasocket/macosx.cmd create mode 100755 framework/lualib-src/luasocket/makefile create mode 100644 framework/lualib-src/luasocket/makefile.dist create mode 100755 framework/lualib-src/luasocket/mime.vcxproj create mode 100644 framework/lualib-src/luasocket/mingw.cmd create mode 100755 framework/lualib-src/luasocket/socket.vcxproj create mode 100644 framework/lualib-src/luasocket/src/auxiliar.c create mode 100644 framework/lualib-src/luasocket/src/auxiliar.h create mode 100644 framework/lualib-src/luasocket/src/buffer.c create mode 100644 framework/lualib-src/luasocket/src/buffer.h create mode 100644 framework/lualib-src/luasocket/src/compat.c create mode 100644 framework/lualib-src/luasocket/src/compat.h create mode 100644 framework/lualib-src/luasocket/src/except.c create mode 100644 framework/lualib-src/luasocket/src/except.h create mode 100644 framework/lualib-src/luasocket/src/ftp.lua create mode 100644 framework/lualib-src/luasocket/src/headers.lua create mode 100644 framework/lualib-src/luasocket/src/http.lua create mode 100644 framework/lualib-src/luasocket/src/inet.c create mode 100644 framework/lualib-src/luasocket/src/inet.h create mode 100644 framework/lualib-src/luasocket/src/io.c create mode 100644 framework/lualib-src/luasocket/src/io.h create mode 100644 framework/lualib-src/luasocket/src/ltn12.lua create mode 100755 framework/lualib-src/luasocket/src/luasocket.c create mode 100644 framework/lualib-src/luasocket/src/luasocket.h create mode 100755 framework/lualib-src/luasocket/src/makefile create mode 100644 framework/lualib-src/luasocket/src/mbox.lua create mode 100755 framework/lualib-src/luasocket/src/mime.c create mode 100644 framework/lualib-src/luasocket/src/mime.h create mode 100644 framework/lualib-src/luasocket/src/mime.lua create mode 100644 framework/lualib-src/luasocket/src/options.c create mode 100644 framework/lualib-src/luasocket/src/options.h create mode 100644 framework/lualib-src/luasocket/src/pierror.h create mode 100644 framework/lualib-src/luasocket/src/select.c create mode 100644 framework/lualib-src/luasocket/src/select.h create mode 100644 framework/lualib-src/luasocket/src/serial.c create mode 100644 framework/lualib-src/luasocket/src/smtp.lua create mode 100644 framework/lualib-src/luasocket/src/socket.h create mode 100644 framework/lualib-src/luasocket/src/socket.lua create mode 100644 framework/lualib-src/luasocket/src/tcp.c create mode 100644 framework/lualib-src/luasocket/src/tcp.h create mode 100644 framework/lualib-src/luasocket/src/timeout.c create mode 100644 framework/lualib-src/luasocket/src/timeout.h create mode 100644 framework/lualib-src/luasocket/src/tp.lua create mode 100644 framework/lualib-src/luasocket/src/udp.c create mode 100644 framework/lualib-src/luasocket/src/udp.h create mode 100644 framework/lualib-src/luasocket/src/unix.c create mode 100644 framework/lualib-src/luasocket/src/unix.h create mode 100644 framework/lualib-src/luasocket/src/unixdgram.c create mode 100644 framework/lualib-src/luasocket/src/unixdgram.h create mode 100644 framework/lualib-src/luasocket/src/unixstream.c create mode 100644 framework/lualib-src/luasocket/src/unixstream.h create mode 100644 framework/lualib-src/luasocket/src/url.lua create mode 100644 framework/lualib-src/luasocket/src/usocket.c create mode 100644 framework/lualib-src/luasocket/src/usocket.h create mode 100755 framework/lualib-src/luasocket/src/wsocket.c create mode 100644 framework/lualib-src/luasocket/src/wsocket.h create mode 100755 framework/lualib-src/luasocket/vc32.bat create mode 100755 framework/lualib-src/luasocket/vc64.bat create mode 100755 framework/lualib-src/luasocket/win32.cmd create mode 100755 framework/lualib-src/luasocket/win64.cmd delete mode 100755 framework/lualib/3rd/lecs/component.lua delete mode 100755 framework/lualib/3rd/lecs/entity.lua delete mode 100755 framework/lualib/3rd/lecs/filter.lua delete mode 100755 framework/lualib/3rd/lecs/global.lua delete mode 100755 framework/lualib/3rd/lecs/init.lua delete mode 100755 framework/lualib/3rd/lecs/pool.lua delete mode 100755 framework/lualib/3rd/lecs/system.lua delete mode 100755 framework/lualib/3rd/lecs/test.lua delete mode 100755 framework/lualib/3rd/lecs/tiny_ecs.lua delete mode 100755 framework/lualib/3rd/lecs/world.lua create mode 100755 framework/lualib/3rd/termfx/blittest.lua create mode 100755 framework/lualib/3rd/termfx/colortest.lua create mode 100755 framework/lualib/3rd/termfx/copyregiontest.lua create mode 100755 framework/lualib/3rd/termfx/printtest.lua create mode 100755 framework/lualib/3rd/termfx/screenshot.lua create mode 100755 framework/lualib/3rd/termfx/simpleui.lua create mode 100755 framework/lualib/3rd/termfx/termfxtest.lua create mode 100755 framework/lualib/3rd/zeus/ecs.lua delete mode 100755 framework/service/ws_agent.lua delete mode 100755 framework/service/ws_gate.lua delete mode 100755 framework/service/ws_watchdog.lua create mode 100644 framework/simulation/buffer_queue.lua create mode 100644 framework/simulation/conn.lua create mode 100644 framework/simulation/network.lua create mode 100644 framework/simulation/sconn.lua delete mode 100644 tools/LuaMacro/config.ld delete mode 100644 tools/LuaMacro/docgen delete mode 100644 tools/LuaMacro/docgen.bat delete mode 100755 tools/LuaMacro/luam delete mode 100644 tools/LuaMacro/macro.lua delete mode 100644 tools/LuaMacro/macro/Getter.lua delete mode 100644 tools/LuaMacro/macro/TokenList.lua delete mode 100644 tools/LuaMacro/macro/all.lua delete mode 100644 tools/LuaMacro/macro/assert.lua delete mode 100644 tools/LuaMacro/macro/builtin.lua delete mode 100644 tools/LuaMacro/macro/clexer.lua delete mode 100644 tools/LuaMacro/macro/do.lua delete mode 100644 tools/LuaMacro/macro/forall.lua delete mode 100644 tools/LuaMacro/macro/ifelse.lua delete mode 100644 tools/LuaMacro/macro/lambda.lua delete mode 100644 tools/LuaMacro/macro/lc.lua delete mode 100644 tools/LuaMacro/macro/lexer.lua delete mode 100644 tools/LuaMacro/macro/lib/class.lua delete mode 100644 tools/LuaMacro/macro/lib/test.lua delete mode 100644 tools/LuaMacro/macro/module.lua delete mode 100644 tools/LuaMacro/macro/try.lua delete mode 100644 tools/LuaMacro/macro/with.lua delete mode 100644 tools/LuaMacro/readme.md diff --git a/.gitmodules b/.gitmodules index e5fe818..b62a1d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "framework/skynet"] path = framework/skynet url = https://github.com/cloudfreexiao/skynet.git +[submodule "framework/3rd/goscon"] + path = framework/3rd/goscon + url = https://github.com/cloudfreexiao/goscon.git diff --git a/docs/README.md b/docs/README.md index ba8920a..f9cec89 100644 --- a/docs/README.md +++ b/docs/README.md @@ -59,4 +59,10 @@ https://blog.codingnow.com/2007/11/inertia_thinking.html Filebeat+Kafka+Logstash+ElasticSearch+Kibana 日志采集方案 - skynet debug console 使用 - - https://blog.hanxi.cc/p/73/ \ No newline at end of file + - https://blog.hanxi.cc/p/73/ + + 玩家有很多情况下都可能掉线,比如3G信号不稳定或者接电话是我们最需要考虑的情况。根据这个情况,划分为临时掉线和彻底掉线。 假设所有掉线都进入临时掉线状态,然后经过指定之间计时后自动进入彻底掉线模式。 彻底掉线后,游戏服务器应该清理掉 {user_id}@{shard}@{gate}@subid:handshakeid:randomkey,若客户端回来则会触发客户端进行Auth阶段认证和Login登录. + +如果客户端想重复和游戏服务器建立连接,它不需要再次去登陆服务器登陆。只需要把上次的 handshakeid 递增,并重新生成一个 randomkey ,去和游戏服务器(或者Gate)握手即可。 小于当前handshakeid的请求都不进行处理。 + +如何判断临时掉线状态发生?玩家心跳超时是玩家掉线的唯一标志! 玩家主动退出怎么处理?如果客户端有主动退出功能,应该由客户端主动发出logout消息给游戏服务器 \ No newline at end of file diff --git a/framework/3rd/goscon b/framework/3rd/goscon new file mode 160000 index 0000000..2d6546b --- /dev/null +++ b/framework/3rd/goscon @@ -0,0 +1 @@ +Subproject commit 2d6546b99b7f42b53824fdd89a62780e94252a46 diff --git a/framework/3rd/readme.txt b/framework/3rd/readme.txt deleted file mode 100644 index e69de29..0000000 diff --git a/framework/3rd/termbox_next/.gitignore b/framework/3rd/termbox_next/.gitignore new file mode 100755 index 0000000..afcccde --- /dev/null +++ b/framework/3rd/termbox_next/.gitignore @@ -0,0 +1,7 @@ +bin +obj +src/demo/*.o +src/demo/keyboard +src/demo/output +src/demo/paint +src/demo/truecolor diff --git a/framework/3rd/termbox_next/license b/framework/3rd/termbox_next/license new file mode 100755 index 0000000..e9bb4ea --- /dev/null +++ b/framework/3rd/termbox_next/license @@ -0,0 +1,19 @@ +Copyright (C) 2010-2013 nsf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/framework/3rd/termbox_next/makefile b/framework/3rd/termbox_next/makefile new file mode 100755 index 0000000..aa2270b --- /dev/null +++ b/framework/3rd/termbox_next/makefile @@ -0,0 +1,40 @@ +NAME=termbox +CC=gcc +FLAGS+=-std=c99 -pedantic -Wall -Werror -g + +OS:=$(shell uname -s) +ifeq ($(OS),Linux) + FLAGS+=-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE +endif + +BIND=bin +SRCD=src +OBJD=obj +INCL=-I$(SRCD) + +SRCS=$(SRCD)/termbox.c +SRCS+=$(SRCD)/input.c +SRCS+=$(SRCD)/memstream.c +SRCS+=$(SRCD)/ringbuffer.c +SRCS+=$(SRCD)/term.c +SRCS+=$(SRCD)/utf8.c + +OBJS:=$(patsubst $(SRCD)/%.c,$(OBJD)/$(SRCD)/%.o,$(SRCS)) + +.PHONY:all +all:$(BIND)/$(NAME).a + +$(OBJD)/%.o:%.c + @echo "building source object $@" + @mkdir -p $(@D) + @$(CC) $(INCL) $(FLAGS) -c -o $@ $< + +$(BIND)/$(NAME).a:$(OBJS) + @echo "compiling $@" + @mkdir -p $(BIND) + @ar rvs $(BIND)/$(NAME).a $(OBJS) + +clean: + @echo "cleaning workspace" + @rm -rf $(BIND) + @rm -rf $(OBJD) diff --git a/framework/3rd/termbox_next/readme.md b/framework/3rd/termbox_next/readme.md new file mode 100755 index 0000000..d20e68d --- /dev/null +++ b/framework/3rd/termbox_next/readme.md @@ -0,0 +1,57 @@ +# Termbox +[Termbox](https://github.com/nsf/termbox) +was a promising Text User Interface (TUI) library. +Unfortunately, its original author +[changed his mind](https://github.com/nsf/termbox/issues/37#issuecomment-261075481) +about consoles and despite the +[community's efforts](https://github.com/nsf/termbox/pull/104#issuecomment-300308156) +to keep the library's development going, preferred to let it die. Before it happened, +[some people](https://wiki.musl-libc.org/alternatives.html) +already noticed the robustness of the initial architecture +[became compromised](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff#commitcomment-3790714) +in a nonsensical refactoring frenzy. Now, the author refuses to merge features +like true-color support, invoking some +[spurious correlations](https://github.com/nsf/termbox/pull/104#issuecomment-300292223) +we will discuss no further. + +## The new Termbox-next +This fork was made to restore the codebase to its original quality (before +[66c3f91](https://github.com/nsf/termbox/commit/66c3f91b14e24510319bce6b5cc2fecf8cf5abff)) +while providing all the functionnalities of the current implementation. +This was achieved by branching at +[a2e217f](https://github.com/nsf/termbox/commit/a2e217f0fb97e6bbd589136ea3945f9d5a123d81) +and cherry-picking all the commits up to +[d63b83a](https://github.com/nsf/termbox/commit/d63b83af04e0fd6da836bb8f37e5cec72a1dc95a) +if they weren't harmful. + +## Changes +A lot of things changed during the process: + - *waf*, the original build system, was completely removed from the + project and replaced by make. + - anything related to python was removed as well + +## Getting started +Termbox's interface only consists of 12 functions: +``` +tb_init() // initialization +tb_shutdown() // shutdown + +tb_width() // width of the terminal screen +tb_height() // height of the terminal screen + +tb_clear() // clear buffer +tb_present() // sync internal buffer with terminal + +tb_put_cell() +tb_change_cell() +tb_blit() // drawing functions + +tb_select_input_mode() // change input mode +tb_peek_event() // peek a keyboard event +tb_poll_event() // wait for a keyboard event +``` +See src/termbox.h header file for full detail. + +## TL;DR +`make` to build a static version of the lib under bin/termbox.a +`cd src/demo && make` to build the example programs in src/demo/ diff --git a/framework/3rd/termbox_next/src/demo/keyboard.c b/framework/3rd/termbox_next/src/demo/keyboard.c new file mode 100755 index 0000000..3c25c5a --- /dev/null +++ b/framework/3rd/termbox_next/src/demo/keyboard.c @@ -0,0 +1,827 @@ +#include +#include +#include +#include +#include "termbox.h" + +struct key +{ + unsigned char x; + unsigned char y; + uint32_t ch; +}; + +#define STOP {0,0,0} +struct key K_ESC[] = {{1, 1, 'E'}, {2, 1, 'S'}, {3, 1, 'C'}, STOP}; +struct key K_F1[] = {{6, 1, 'F'}, {7, 1, '1'}, STOP}; +struct key K_F2[] = {{9, 1, 'F'}, {10, 1, '2'}, STOP}; +struct key K_F3[] = {{12, 1, 'F'}, {13, 1, '3'}, STOP}; +struct key K_F4[] = {{15, 1, 'F'}, {16, 1, '4'}, STOP}; +struct key K_F5[] = {{19, 1, 'F'}, {20, 1, '5'}, STOP}; +struct key K_F6[] = {{22, 1, 'F'}, {23, 1, '6'}, STOP}; +struct key K_F7[] = {{25, 1, 'F'}, {26, 1, '7'}, STOP}; +struct key K_F8[] = {{28, 1, 'F'}, {29, 1, '8'}, STOP}; +struct key K_F9[] = {{33, 1, 'F'}, {34, 1, '9'}, STOP}; +struct key K_F10[] = {{36, 1, 'F'}, {37, 1, '1'}, {38, 1, '0'}, STOP}; +struct key K_F11[] = {{40, 1, 'F'}, {41, 1, '1'}, {42, 1, '1'}, STOP}; +struct key K_F12[] = {{44, 1, 'F'}, {45, 1, '1'}, {46, 1, '2'}, STOP}; +struct key K_PRN[] = {{50, 1, 'P'}, {51, 1, 'R'}, {52, 1, 'N'}, STOP}; +struct key K_SCR[] = {{54, 1, 'S'}, {55, 1, 'C'}, {56, 1, 'R'}, STOP}; +struct key K_BRK[] = {{58, 1, 'B'}, {59, 1, 'R'}, {60, 1, 'K'}, STOP}; +struct key K_LED1[] = {{66, 1, '-'}, STOP}; +struct key K_LED2[] = {{70, 1, '-'}, STOP}; +struct key K_LED3[] = {{74, 1, '-'}, STOP}; + +struct key K_TILDE[] = {{1, 4, '`'}, STOP}; +struct key K_TILDE_SHIFT[] = {{1, 4, '~'}, STOP}; +struct key K_1[] = {{4, 4, '1'}, STOP}; +struct key K_1_SHIFT[] = {{4, 4, '!'}, STOP}; +struct key K_2[] = {{7, 4, '2'}, STOP}; +struct key K_2_SHIFT[] = {{7, 4, '@'}, STOP}; +struct key K_3[] = {{10, 4, '3'}, STOP}; +struct key K_3_SHIFT[] = {{10, 4, '#'}, STOP}; +struct key K_4[] = {{13, 4, '4'}, STOP}; +struct key K_4_SHIFT[] = {{13, 4, '$'}, STOP}; +struct key K_5[] = {{16, 4, '5'}, STOP}; +struct key K_5_SHIFT[] = {{16, 4, '%'}, STOP}; +struct key K_6[] = {{19, 4, '6'}, STOP}; +struct key K_6_SHIFT[] = {{19, 4, '^'}, STOP}; +struct key K_7[] = {{22, 4, '7'}, STOP}; +struct key K_7_SHIFT[] = {{22, 4, '&'}, STOP}; +struct key K_8[] = {{25, 4, '8'}, STOP}; +struct key K_8_SHIFT[] = {{25, 4, '*'}, STOP}; +struct key K_9[] = {{28, 4, '9'}, STOP}; +struct key K_9_SHIFT[] = {{28, 4, '('}, STOP}; +struct key K_0[] = {{31, 4, '0'}, STOP}; +struct key K_0_SHIFT[] = {{31, 4, ')'}, STOP}; +struct key K_MINUS[] = {{34, 4, '-'}, STOP}; +struct key K_MINUS_SHIFT[] = {{34, 4, '_'}, STOP}; +struct key K_EQUALS[] = {{37, 4, '='}, STOP}; +struct key K_EQUALS_SHIFT[] = {{37, 4, '+'}, STOP}; +struct key K_BACKSLASH[] = {{40, 4, '\\'}, STOP}; +struct key K_BACKSLASH_SHIFT[] = {{40, 4, '|'}, STOP}; +struct key K_BACKSPACE[] = {{44, 4, 0x2190}, {45, 4, 0x2500}, {46, 4, 0x2500}, STOP}; +struct key K_INS[] = {{50, 4, 'I'}, {51, 4, 'N'}, {52, 4, 'S'}, STOP}; +struct key K_HOM[] = {{54, 4, 'H'}, {55, 4, 'O'}, {56, 4, 'M'}, STOP}; +struct key K_PGU[] = {{58, 4, 'P'}, {59, 4, 'G'}, {60, 4, 'U'}, STOP}; +struct key K_K_NUMLOCK[] = {{65, 4, 'N'}, STOP}; +struct key K_K_SLASH[] = {{68, 4, '/'}, STOP}; +struct key K_K_STAR[] = {{71, 4, '*'}, STOP}; +struct key K_K_MINUS[] = {{74, 4, '-'}, STOP}; + +struct key K_TAB[] = {{1, 6, 'T'}, {2, 6, 'A'}, {3, 6, 'B'}, STOP}; +struct key K_q[] = {{6, 6, 'q'}, STOP}; +struct key K_Q[] = {{6, 6, 'Q'}, STOP}; +struct key K_w[] = {{9, 6, 'w'}, STOP}; +struct key K_W[] = {{9, 6, 'W'}, STOP}; +struct key K_e[] = {{12, 6, 'e'}, STOP}; +struct key K_E[] = {{12, 6, 'E'}, STOP}; +struct key K_r[] = {{15, 6, 'r'}, STOP}; +struct key K_R[] = {{15, 6, 'R'}, STOP}; +struct key K_t[] = {{18, 6, 't'}, STOP}; +struct key K_T[] = {{18, 6, 'T'}, STOP}; +struct key K_y[] = {{21, 6, 'y'}, STOP}; +struct key K_Y[] = {{21, 6, 'Y'}, STOP}; +struct key K_u[] = {{24, 6, 'u'}, STOP}; +struct key K_U[] = {{24, 6, 'U'}, STOP}; +struct key K_i[] = {{27, 6, 'i'}, STOP}; +struct key K_I[] = {{27, 6, 'I'}, STOP}; +struct key K_o[] = {{30, 6, 'o'}, STOP}; +struct key K_O[] = {{30, 6, 'O'}, STOP}; +struct key K_p[] = {{33, 6, 'p'}, STOP}; +struct key K_P[] = {{33, 6, 'P'}, STOP}; +struct key K_LSQB[] = {{36, 6, '['}, STOP}; +struct key K_LCUB[] = {{36, 6, '{'}, STOP}; +struct key K_RSQB[] = {{39, 6, ']'}, STOP}; +struct key K_RCUB[] = {{39, 6, '}'}, STOP}; +struct key K_ENTER[] = +{ + {43, 6, 0x2591}, {44, 6, 0x2591}, {45, 6, 0x2591}, {46, 6, 0x2591}, + {43, 7, 0x2591}, {44, 7, 0x2591}, {45, 7, 0x21B5}, {46, 7, 0x2591}, + {41, 8, 0x2591}, {42, 8, 0x2591}, {43, 8, 0x2591}, {44, 8, 0x2591}, + {45, 8, 0x2591}, {46, 8, 0x2591}, STOP +}; +struct key K_DEL[] = {{50, 6, 'D'}, {51, 6, 'E'}, {52, 6, 'L'}, STOP}; +struct key K_END[] = {{54, 6, 'E'}, {55, 6, 'N'}, {56, 6, 'D'}, STOP}; +struct key K_PGD[] = {{58, 6, 'P'}, {59, 6, 'G'}, {60, 6, 'D'}, STOP}; +struct key K_K_7[] = {{65, 6, '7'}, STOP}; +struct key K_K_8[] = {{68, 6, '8'}, STOP}; +struct key K_K_9[] = {{71, 6, '9'}, STOP}; +struct key K_K_PLUS[] = {{74, 6, ' '}, {74, 7, '+'}, {74, 8, ' '}, STOP}; + +struct key K_CAPS[] = {{1, 8, 'C'}, {2, 8, 'A'}, {3, 8, 'P'}, {4, 8, 'S'}, STOP}; +struct key K_a[] = {{7, 8, 'a'}, STOP}; +struct key K_A[] = {{7, 8, 'A'}, STOP}; +struct key K_s[] = {{10, 8, 's'}, STOP}; +struct key K_S[] = {{10, 8, 'S'}, STOP}; +struct key K_d[] = {{13, 8, 'd'}, STOP}; +struct key K_D[] = {{13, 8, 'D'}, STOP}; +struct key K_f[] = {{16, 8, 'f'}, STOP}; +struct key K_F[] = {{16, 8, 'F'}, STOP}; +struct key K_g[] = {{19, 8, 'g'}, STOP}; +struct key K_G[] = {{19, 8, 'G'}, STOP}; +struct key K_h[] = {{22, 8, 'h'}, STOP}; +struct key K_H[] = {{22, 8, 'H'}, STOP}; +struct key K_j[] = {{25, 8, 'j'}, STOP}; +struct key K_J[] = {{25, 8, 'J'}, STOP}; +struct key K_k[] = {{28, 8, 'k'}, STOP}; +struct key K_K[] = {{28, 8, 'K'}, STOP}; +struct key K_l[] = {{31, 8, 'l'}, STOP}; +struct key K_L[] = {{31, 8, 'L'}, STOP}; +struct key K_SEMICOLON[] = {{34, 8, ';'}, STOP}; +struct key K_PARENTHESIS[] = {{34, 8, ':'}, STOP}; +struct key K_QUOTE[] = {{37, 8, '\''}, STOP}; +struct key K_DOUBLEQUOTE[] = {{37, 8, '"'}, STOP}; +struct key K_K_4[] = {{65, 8, '4'}, STOP}; +struct key K_K_5[] = {{68, 8, '5'}, STOP}; +struct key K_K_6[] = {{71, 8, '6'}, STOP}; + +struct key K_LSHIFT[] = {{1, 10, 'S'}, {2, 10, 'H'}, {3, 10, 'I'}, {4, 10, 'F'}, {5, 10, 'T'}, STOP}; +struct key K_z[] = {{9, 10, 'z'}, STOP}; +struct key K_Z[] = {{9, 10, 'Z'}, STOP}; +struct key K_x[] = {{12, 10, 'x'}, STOP}; +struct key K_X[] = {{12, 10, 'X'}, STOP}; +struct key K_c[] = {{15, 10, 'c'}, STOP}; +struct key K_C[] = {{15, 10, 'C'}, STOP}; +struct key K_v[] = {{18, 10, 'v'}, STOP}; +struct key K_V[] = {{18, 10, 'V'}, STOP}; +struct key K_b[] = {{21, 10, 'b'}, STOP}; +struct key K_B[] = {{21, 10, 'B'}, STOP}; +struct key K_n[] = {{24, 10, 'n'}, STOP}; +struct key K_N[] = {{24, 10, 'N'}, STOP}; +struct key K_m[] = {{27, 10, 'm'}, STOP}; +struct key K_M[] = {{27, 10, 'M'}, STOP}; +struct key K_COMMA[] = {{30, 10, ','}, STOP}; +struct key K_LANB[] = {{30, 10, '<'}, STOP}; +struct key K_PERIOD[] = {{33, 10, '.'}, STOP}; +struct key K_RANB[] = {{33, 10, '>'}, STOP}; +struct key K_SLASH[] = {{36, 10, '/'}, STOP}; +struct key K_QUESTION[] = {{36, 10, '?'}, STOP}; +struct key K_RSHIFT[] = {{42, 10, 'S'}, {43, 10, 'H'}, {44, 10, 'I'}, {45, 10, 'F'}, {46, 10, 'T'}, STOP}; +struct key K_ARROW_UP[] = {{54, 10, '('}, {55, 10, 0x2191}, {56, 10, ')'}, STOP}; +struct key K_K_1[] = {{65, 10, '1'}, STOP}; +struct key K_K_2[] = {{68, 10, '2'}, STOP}; +struct key K_K_3[] = {{71, 10, '3'}, STOP}; +struct key K_K_ENTER[] = {{74, 10, 0x2591}, {74, 11, 0x2591}, {74, 12, 0x2591}, STOP}; + +struct key K_LCTRL[] = {{1, 12, 'C'}, {2, 12, 'T'}, {3, 12, 'R'}, {4, 12, 'L'}, STOP}; +struct key K_LWIN[] = {{6, 12, 'W'}, {7, 12, 'I'}, {8, 12, 'N'}, STOP}; +struct key K_LALT[] = {{10, 12, 'A'}, {11, 12, 'L'}, {12, 12, 'T'}, STOP}; +struct key K_SPACE[] = +{ + {14, 12, ' '}, {15, 12, ' '}, {16, 12, ' '}, {17, 12, ' '}, {18, 12, ' '}, + {19, 12, 'S'}, {20, 12, 'P'}, {21, 12, 'A'}, {22, 12, 'C'}, {23, 12, 'E'}, + {24, 12, ' '}, {25, 12, ' '}, {26, 12, ' '}, {27, 12, ' '}, {28, 12, ' '}, + STOP +}; +struct key K_RALT[] = {{30, 12, 'A'}, {31, 12, 'L'}, {32, 12, 'T'}, STOP}; +struct key K_RWIN[] = {{34, 12, 'W'}, {35, 12, 'I'}, {36, 12, 'N'}, STOP}; +struct key K_RPROP[] = {{38, 12, 'P'}, {39, 12, 'R'}, {40, 12, 'O'}, {41, 12, 'P'}, STOP}; +struct key K_RCTRL[] = {{43, 12, 'C'}, {44, 12, 'T'}, {45, 12, 'R'}, {46, 12, 'L'}, STOP}; +struct key K_ARROW_LEFT[] = {{50, 12, '('}, {51, 12, 0x2190}, {52, 12, ')'}, STOP}; +struct key K_ARROW_DOWN[] = {{54, 12, '('}, {55, 12, 0x2193}, {56, 12, ')'}, STOP}; +struct key K_ARROW_RIGHT[] = {{58, 12, '('}, {59, 12, 0x2192}, {60, 12, ')'}, STOP}; +struct key K_K_0[] = {{65, 12, ' '}, {66, 12, '0'}, {67, 12, ' '}, {68, 12, ' '}, STOP}; +struct key K_K_PERIOD[] = {{71, 12, '.'}, STOP}; + +struct combo +{ + struct key* keys[6]; +}; + +struct combo combos[] = +{ + {{K_TILDE, K_2, K_LCTRL, K_RCTRL, 0}}, + {{K_A, K_LCTRL, K_RCTRL, 0}}, + {{K_B, K_LCTRL, K_RCTRL, 0}}, + {{K_C, K_LCTRL, K_RCTRL, 0}}, + {{K_D, K_LCTRL, K_RCTRL, 0}}, + {{K_E, K_LCTRL, K_RCTRL, 0}}, + {{K_F, K_LCTRL, K_RCTRL, 0}}, + {{K_G, K_LCTRL, K_RCTRL, 0}}, + {{K_H, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}}, + {{K_I, K_TAB, K_LCTRL, K_RCTRL, 0}}, + {{K_J, K_LCTRL, K_RCTRL, 0}}, + {{K_K, K_LCTRL, K_RCTRL, 0}}, + {{K_L, K_LCTRL, K_RCTRL, 0}}, + {{K_M, K_ENTER, K_K_ENTER, K_LCTRL, K_RCTRL, 0}}, + {{K_N, K_LCTRL, K_RCTRL, 0}}, + {{K_O, K_LCTRL, K_RCTRL, 0}}, + {{K_P, K_LCTRL, K_RCTRL, 0}}, + {{K_Q, K_LCTRL, K_RCTRL, 0}}, + {{K_R, K_LCTRL, K_RCTRL, 0}}, + {{K_S, K_LCTRL, K_RCTRL, 0}}, + {{K_T, K_LCTRL, K_RCTRL, 0}}, + {{K_U, K_LCTRL, K_RCTRL, 0}}, + {{K_V, K_LCTRL, K_RCTRL, 0}}, + {{K_W, K_LCTRL, K_RCTRL, 0}}, + {{K_X, K_LCTRL, K_RCTRL, 0}}, + {{K_Y, K_LCTRL, K_RCTRL, 0}}, + {{K_Z, K_LCTRL, K_RCTRL, 0}}, + {{K_LSQB, K_ESC, K_3, K_LCTRL, K_RCTRL, 0}}, + {{K_4, K_BACKSLASH, K_LCTRL, K_RCTRL, 0}}, + {{K_RSQB, K_5, K_LCTRL, K_RCTRL, 0}}, + {{K_6, K_LCTRL, K_RCTRL, 0}}, + {{K_7, K_SLASH, K_MINUS_SHIFT, K_LCTRL, K_RCTRL, 0}}, + {{K_SPACE, 0}}, + {{K_1_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_DOUBLEQUOTE, K_LSHIFT, K_RSHIFT, 0}}, + {{K_3_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_4_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_5_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_7_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_QUOTE, 0}}, + {{K_9_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_0_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_8_SHIFT, K_K_STAR, K_LSHIFT, K_RSHIFT, 0}}, + {{K_EQUALS_SHIFT, K_K_PLUS, K_LSHIFT, K_RSHIFT, 0}}, + {{K_COMMA, 0}}, + {{K_MINUS, K_K_MINUS, 0}}, + {{K_PERIOD, K_K_PERIOD, 0}}, + {{K_SLASH, K_K_SLASH, 0}}, + {{K_0, K_K_0, 0}}, + {{K_1, K_K_1, 0}}, + {{K_2, K_K_2, 0}}, + {{K_3, K_K_3, 0}}, + {{K_4, K_K_4, 0}}, + {{K_5, K_K_5, 0}}, + {{K_6, K_K_6, 0}}, + {{K_7, K_K_7, 0}}, + {{K_8, K_K_8, 0}}, + {{K_9, K_K_9, 0}}, + {{K_PARENTHESIS, K_LSHIFT, K_RSHIFT, 0}}, + {{K_SEMICOLON, 0}}, + {{K_LANB, K_LSHIFT, K_RSHIFT, 0}}, + {{K_EQUALS, 0}}, + {{K_RANB, K_LSHIFT, K_RSHIFT, 0}}, + {{K_QUESTION, K_LSHIFT, K_RSHIFT, 0}}, + {{K_2_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_A, K_LSHIFT, K_RSHIFT, 0}}, + {{K_B, K_LSHIFT, K_RSHIFT, 0}}, + {{K_C, K_LSHIFT, K_RSHIFT, 0}}, + {{K_D, K_LSHIFT, K_RSHIFT, 0}}, + {{K_E, K_LSHIFT, K_RSHIFT, 0}}, + {{K_F, K_LSHIFT, K_RSHIFT, 0}}, + {{K_G, K_LSHIFT, K_RSHIFT, 0}}, + {{K_H, K_LSHIFT, K_RSHIFT, 0}}, + {{K_I, K_LSHIFT, K_RSHIFT, 0}}, + {{K_J, K_LSHIFT, K_RSHIFT, 0}}, + {{K_K, K_LSHIFT, K_RSHIFT, 0}}, + {{K_L, K_LSHIFT, K_RSHIFT, 0}}, + {{K_M, K_LSHIFT, K_RSHIFT, 0}}, + {{K_N, K_LSHIFT, K_RSHIFT, 0}}, + {{K_O, K_LSHIFT, K_RSHIFT, 0}}, + {{K_P, K_LSHIFT, K_RSHIFT, 0}}, + {{K_Q, K_LSHIFT, K_RSHIFT, 0}}, + {{K_R, K_LSHIFT, K_RSHIFT, 0}}, + {{K_S, K_LSHIFT, K_RSHIFT, 0}}, + {{K_T, K_LSHIFT, K_RSHIFT, 0}}, + {{K_U, K_LSHIFT, K_RSHIFT, 0}}, + {{K_V, K_LSHIFT, K_RSHIFT, 0}}, + {{K_W, K_LSHIFT, K_RSHIFT, 0}}, + {{K_X, K_LSHIFT, K_RSHIFT, 0}}, + {{K_Y, K_LSHIFT, K_RSHIFT, 0}}, + {{K_Z, K_LSHIFT, K_RSHIFT, 0}}, + {{K_LSQB, 0}}, + {{K_BACKSLASH, 0}}, + {{K_RSQB, 0}}, + {{K_6_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_MINUS_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_TILDE, 0}}, + {{K_a, 0}}, + {{K_b, 0}}, + {{K_c, 0}}, + {{K_d, 0}}, + {{K_e, 0}}, + {{K_f, 0}}, + {{K_g, 0}}, + {{K_h, 0}}, + {{K_i, 0}}, + {{K_j, 0}}, + {{K_k, 0}}, + {{K_l, 0}}, + {{K_m, 0}}, + {{K_n, 0}}, + {{K_o, 0}}, + {{K_p, 0}}, + {{K_q, 0}}, + {{K_r, 0}}, + {{K_s, 0}}, + {{K_t, 0}}, + {{K_u, 0}}, + {{K_v, 0}}, + {{K_w, 0}}, + {{K_x, 0}}, + {{K_y, 0}}, + {{K_z, 0}}, + {{K_LCUB, K_LSHIFT, K_RSHIFT, 0}}, + {{K_BACKSLASH_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_RCUB, K_LSHIFT, K_RSHIFT, 0}}, + {{K_TILDE_SHIFT, K_LSHIFT, K_RSHIFT, 0}}, + {{K_8, K_BACKSPACE, K_LCTRL, K_RCTRL, 0}} +}; + +struct combo func_combos[] = +{ + {{K_F1, 0}}, + {{K_F2, 0}}, + {{K_F3, 0}}, + {{K_F4, 0}}, + {{K_F5, 0}}, + {{K_F6, 0}}, + {{K_F7, 0}}, + {{K_F8, 0}}, + {{K_F9, 0}}, + {{K_F10, 0}}, + {{K_F11, 0}}, + {{K_F12, 0}}, + {{K_INS, 0}}, + {{K_DEL, 0}}, + {{K_HOM, 0}}, + {{K_END, 0}}, + {{K_PGU, 0}}, + {{K_PGD, 0}}, + {{K_ARROW_UP, 0}}, + {{K_ARROW_DOWN, 0}}, + {{K_ARROW_LEFT, 0}}, + {{K_ARROW_RIGHT, 0}} +}; + +void print_tb(const char* str, int x, int y, uint32_t fg, uint32_t bg) +{ + while (*str) + { + uint32_t uni; + str += utf8_char_to_unicode(&uni, str); + tb_change_cell(x, y, uni, fg, bg); + x++; + } +} + +void printf_tb(int x, int y, uint32_t fg, uint32_t bg, const char* fmt, ...) +{ + char buf[4096]; + va_list vl; + va_start(vl, fmt); + vsnprintf(buf, sizeof(buf), fmt, vl); + va_end(vl); + print_tb(buf, x, y, fg, bg); +} + +void draw_key(struct key* k, uint32_t fg, uint32_t bg) +{ + while (k->x) + { + tb_change_cell(k->x + 2, k->y + 4, k->ch, fg, bg); + k++; + } +} + +void draw_keyboard() +{ + int i; + tb_change_cell(0, 0, 0x250C, TB_WHITE, TB_DEFAULT); + tb_change_cell(79, 0, 0x2510, TB_WHITE, TB_DEFAULT); + tb_change_cell(0, 23, 0x2514, TB_WHITE, TB_DEFAULT); + tb_change_cell(79, 23, 0x2518, TB_WHITE, TB_DEFAULT); + + for (i = 1; i < 79; ++i) + { + tb_change_cell(i, 0, 0x2500, TB_WHITE, TB_DEFAULT); + tb_change_cell(i, 23, 0x2500, TB_WHITE, TB_DEFAULT); + tb_change_cell(i, 17, 0x2500, TB_WHITE, TB_DEFAULT); + tb_change_cell(i, 4, 0x2500, TB_WHITE, TB_DEFAULT); + } + + for (i = 1; i < 23; ++i) + { + tb_change_cell(0, i, 0x2502, TB_WHITE, TB_DEFAULT); + tb_change_cell(79, i, 0x2502, TB_WHITE, TB_DEFAULT); + } + + tb_change_cell(0, 17, 0x251C, TB_WHITE, TB_DEFAULT); + tb_change_cell(79, 17, 0x2524, TB_WHITE, TB_DEFAULT); + tb_change_cell(0, 4, 0x251C, TB_WHITE, TB_DEFAULT); + tb_change_cell(79, 4, 0x2524, TB_WHITE, TB_DEFAULT); + + for (i = 5; i < 17; ++i) + { + tb_change_cell(1, i, 0x2588, TB_YELLOW, TB_YELLOW); + tb_change_cell(78, i, 0x2588, TB_YELLOW, TB_YELLOW); + } + + draw_key(K_ESC, TB_WHITE, TB_BLUE); + draw_key(K_F1, TB_WHITE, TB_BLUE); + draw_key(K_F2, TB_WHITE, TB_BLUE); + draw_key(K_F3, TB_WHITE, TB_BLUE); + draw_key(K_F4, TB_WHITE, TB_BLUE); + draw_key(K_F5, TB_WHITE, TB_BLUE); + draw_key(K_F6, TB_WHITE, TB_BLUE); + draw_key(K_F7, TB_WHITE, TB_BLUE); + draw_key(K_F8, TB_WHITE, TB_BLUE); + draw_key(K_F9, TB_WHITE, TB_BLUE); + draw_key(K_F10, TB_WHITE, TB_BLUE); + draw_key(K_F11, TB_WHITE, TB_BLUE); + draw_key(K_F12, TB_WHITE, TB_BLUE); + draw_key(K_PRN, TB_WHITE, TB_BLUE); + draw_key(K_SCR, TB_WHITE, TB_BLUE); + draw_key(K_BRK, TB_WHITE, TB_BLUE); + draw_key(K_LED1, TB_WHITE, TB_BLUE); + draw_key(K_LED2, TB_WHITE, TB_BLUE); + draw_key(K_LED3, TB_WHITE, TB_BLUE); + + draw_key(K_TILDE, TB_WHITE, TB_BLUE); + draw_key(K_1, TB_WHITE, TB_BLUE); + draw_key(K_2, TB_WHITE, TB_BLUE); + draw_key(K_3, TB_WHITE, TB_BLUE); + draw_key(K_4, TB_WHITE, TB_BLUE); + draw_key(K_5, TB_WHITE, TB_BLUE); + draw_key(K_6, TB_WHITE, TB_BLUE); + draw_key(K_7, TB_WHITE, TB_BLUE); + draw_key(K_8, TB_WHITE, TB_BLUE); + draw_key(K_9, TB_WHITE, TB_BLUE); + draw_key(K_0, TB_WHITE, TB_BLUE); + draw_key(K_MINUS, TB_WHITE, TB_BLUE); + draw_key(K_EQUALS, TB_WHITE, TB_BLUE); + draw_key(K_BACKSLASH, TB_WHITE, TB_BLUE); + draw_key(K_BACKSPACE, TB_WHITE, TB_BLUE); + draw_key(K_INS, TB_WHITE, TB_BLUE); + draw_key(K_HOM, TB_WHITE, TB_BLUE); + draw_key(K_PGU, TB_WHITE, TB_BLUE); + draw_key(K_K_NUMLOCK, TB_WHITE, TB_BLUE); + draw_key(K_K_SLASH, TB_WHITE, TB_BLUE); + draw_key(K_K_STAR, TB_WHITE, TB_BLUE); + draw_key(K_K_MINUS, TB_WHITE, TB_BLUE); + + draw_key(K_TAB, TB_WHITE, TB_BLUE); + draw_key(K_q, TB_WHITE, TB_BLUE); + draw_key(K_w, TB_WHITE, TB_BLUE); + draw_key(K_e, TB_WHITE, TB_BLUE); + draw_key(K_r, TB_WHITE, TB_BLUE); + draw_key(K_t, TB_WHITE, TB_BLUE); + draw_key(K_y, TB_WHITE, TB_BLUE); + draw_key(K_u, TB_WHITE, TB_BLUE); + draw_key(K_i, TB_WHITE, TB_BLUE); + draw_key(K_o, TB_WHITE, TB_BLUE); + draw_key(K_p, TB_WHITE, TB_BLUE); + draw_key(K_LSQB, TB_WHITE, TB_BLUE); + draw_key(K_RSQB, TB_WHITE, TB_BLUE); + draw_key(K_ENTER, TB_WHITE, TB_BLUE); + draw_key(K_DEL, TB_WHITE, TB_BLUE); + draw_key(K_END, TB_WHITE, TB_BLUE); + draw_key(K_PGD, TB_WHITE, TB_BLUE); + draw_key(K_K_7, TB_WHITE, TB_BLUE); + draw_key(K_K_8, TB_WHITE, TB_BLUE); + draw_key(K_K_9, TB_WHITE, TB_BLUE); + draw_key(K_K_PLUS, TB_WHITE, TB_BLUE); + + draw_key(K_CAPS, TB_WHITE, TB_BLUE); + draw_key(K_a, TB_WHITE, TB_BLUE); + draw_key(K_s, TB_WHITE, TB_BLUE); + draw_key(K_d, TB_WHITE, TB_BLUE); + draw_key(K_f, TB_WHITE, TB_BLUE); + draw_key(K_g, TB_WHITE, TB_BLUE); + draw_key(K_h, TB_WHITE, TB_BLUE); + draw_key(K_j, TB_WHITE, TB_BLUE); + draw_key(K_k, TB_WHITE, TB_BLUE); + draw_key(K_l, TB_WHITE, TB_BLUE); + draw_key(K_SEMICOLON, TB_WHITE, TB_BLUE); + draw_key(K_QUOTE, TB_WHITE, TB_BLUE); + draw_key(K_K_4, TB_WHITE, TB_BLUE); + draw_key(K_K_5, TB_WHITE, TB_BLUE); + draw_key(K_K_6, TB_WHITE, TB_BLUE); + + draw_key(K_LSHIFT, TB_WHITE, TB_BLUE); + draw_key(K_z, TB_WHITE, TB_BLUE); + draw_key(K_x, TB_WHITE, TB_BLUE); + draw_key(K_c, TB_WHITE, TB_BLUE); + draw_key(K_v, TB_WHITE, TB_BLUE); + draw_key(K_b, TB_WHITE, TB_BLUE); + draw_key(K_n, TB_WHITE, TB_BLUE); + draw_key(K_m, TB_WHITE, TB_BLUE); + draw_key(K_COMMA, TB_WHITE, TB_BLUE); + draw_key(K_PERIOD, TB_WHITE, TB_BLUE); + draw_key(K_SLASH, TB_WHITE, TB_BLUE); + draw_key(K_RSHIFT, TB_WHITE, TB_BLUE); + draw_key(K_ARROW_UP, TB_WHITE, TB_BLUE); + draw_key(K_K_1, TB_WHITE, TB_BLUE); + draw_key(K_K_2, TB_WHITE, TB_BLUE); + draw_key(K_K_3, TB_WHITE, TB_BLUE); + draw_key(K_K_ENTER, TB_WHITE, TB_BLUE); + + draw_key(K_LCTRL, TB_WHITE, TB_BLUE); + draw_key(K_LWIN, TB_WHITE, TB_BLUE); + draw_key(K_LALT, TB_WHITE, TB_BLUE); + draw_key(K_SPACE, TB_WHITE, TB_BLUE); + draw_key(K_RCTRL, TB_WHITE, TB_BLUE); + draw_key(K_RPROP, TB_WHITE, TB_BLUE); + draw_key(K_RWIN, TB_WHITE, TB_BLUE); + draw_key(K_RALT, TB_WHITE, TB_BLUE); + draw_key(K_ARROW_LEFT, TB_WHITE, TB_BLUE); + draw_key(K_ARROW_DOWN, TB_WHITE, TB_BLUE); + draw_key(K_ARROW_RIGHT, TB_WHITE, TB_BLUE); + draw_key(K_K_0, TB_WHITE, TB_BLUE); + draw_key(K_K_PERIOD, TB_WHITE, TB_BLUE); + + printf_tb(33, 1, TB_MAGENTA | TB_BOLD, TB_DEFAULT, "Keyboard demo!"); + printf_tb(21, 2, TB_MAGENTA, TB_DEFAULT, + "(press CTRL+X and then CTRL+Q to exit)"); + printf_tb(15, 3, TB_MAGENTA, TB_DEFAULT, + "(press CTRL+X and then CTRL+C to change input mode)"); + + int inputmode = tb_select_input_mode(0); + char inputmode_str[64]; + + if (inputmode & TB_INPUT_ESC) + { + sprintf(inputmode_str, "TB_INPUT_ESC"); + } + + if (inputmode & TB_INPUT_ALT) + { + sprintf(inputmode_str, "TB_INPUT_ALT"); + } + + if (inputmode & TB_INPUT_MOUSE) + { + sprintf(inputmode_str + 12, " | TB_INPUT_MOUSE"); + } + + printf_tb(3, 18, TB_WHITE, TB_DEFAULT, "Input mode: %s", inputmode_str); +} + +const char* funckeymap(int k) +{ + static const char* fcmap[] = + { + "CTRL+2, CTRL+~", + "CTRL+A", + "CTRL+B", + "CTRL+C", + "CTRL+D", + "CTRL+E", + "CTRL+F", + "CTRL+G", + "CTRL+H, BACKSPACE", + "CTRL+I, TAB", + "CTRL+J", + "CTRL+K", + "CTRL+L", + "CTRL+M, ENTER", + "CTRL+N", + "CTRL+O", + "CTRL+P", + "CTRL+Q", + "CTRL+R", + "CTRL+S", + "CTRL+T", + "CTRL+U", + "CTRL+V", + "CTRL+W", + "CTRL+X", + "CTRL+Y", + "CTRL+Z", + "CTRL+3, ESC, CTRL+[", + "CTRL+4, CTRL+\\", + "CTRL+5, CTRL+]", + "CTRL+6", + "CTRL+7, CTRL+/, CTRL+_", + "SPACE" + }; + static const char* fkmap[] = + { + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "INSERT", + "DELETE", + "HOME", + "END", + "PGUP", + "PGDN", + "ARROW UP", + "ARROW DOWN", + "ARROW LEFT", + "ARROW RIGHT" + }; + + if (k == TB_KEY_CTRL_8) + { + return "CTRL+8, BACKSPACE 2"; // 0x7F + } + else if (k >= TB_KEY_ARROW_RIGHT && k <= 0xFFFF) + { + return fkmap[0xFFFF - k]; + } + else if (k <= TB_KEY_SPACE) + { + return fcmap[k]; + } + + return "UNKNOWN"; +} + +void pretty_print_press(struct tb_event* ev) +{ + char buf[7]; + buf[utf8_unicode_to_char(buf, ev->ch)] = '\0'; + printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Key: "); + printf_tb(8, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->key); + printf_tb(8, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->key); + printf_tb(8, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->key); + printf_tb(8, 22, TB_RED, TB_DEFAULT, "string: %s", funckeymap(ev->key)); + + printf_tb(54, 19, TB_WHITE, TB_DEFAULT, "Char: "); + printf_tb(60, 19, TB_YELLOW, TB_DEFAULT, "decimal: %d", ev->ch); + printf_tb(60, 20, TB_GREEN, TB_DEFAULT, "hex: 0x%X", ev->ch); + printf_tb(60, 21, TB_CYAN, TB_DEFAULT, "octal: 0%o", ev->ch); + printf_tb(60, 22, TB_RED, TB_DEFAULT, "string: %s", buf); + + printf_tb(54, 18, TB_WHITE, TB_DEFAULT, "Modifier: %s", + (ev->mod) ? "TB_MOD_ALT" : "none"); + +} + +void pretty_print_resize(struct tb_event* ev) +{ + printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Resize event: %d x %d", ev->w, ev->h); +} + +int counter = 0; + +void pretty_print_mouse(struct tb_event* ev) +{ + printf_tb(3, 19, TB_WHITE, TB_DEFAULT, "Mouse event: %d x %d", ev->x, ev->y); + char* btn = ""; + + switch (ev->key) + { + case TB_KEY_MOUSE_LEFT: + btn = "MouseLeft: %d"; + break; + + case TB_KEY_MOUSE_MIDDLE: + btn = "MouseMiddle: %d"; + break; + + case TB_KEY_MOUSE_RIGHT: + btn = "MouseRight: %d"; + break; + + case TB_KEY_MOUSE_WHEEL_UP: + btn = "MouseWheelUp: %d"; + break; + + case TB_KEY_MOUSE_WHEEL_DOWN: + btn = "MouseWheelDown: %d"; + break; + + case TB_KEY_MOUSE_RELEASE: + btn = "MouseRelease: %d"; + } + + counter++; + printf_tb(43, 19, TB_WHITE, TB_DEFAULT, "Key: "); + printf_tb(48, 19, TB_YELLOW, TB_DEFAULT, btn, counter); +} + +void dispatch_press(struct tb_event* ev) +{ + if (ev->mod & TB_MOD_ALT) + { + draw_key(K_LALT, TB_WHITE, TB_RED); + draw_key(K_RALT, TB_WHITE, TB_RED); + } + + struct combo* k = 0; + + if (ev->key >= TB_KEY_ARROW_RIGHT) + { + k = &func_combos[0xFFFF - ev->key]; + } + else if (ev->ch < 128) + { + if (ev->ch == 0 && ev->key < 128) + { + k = &combos[ev->key]; + } + else + { + k = &combos[ev->ch]; + } + } + + if (!k) + { + return; + } + + struct key** keys = k->keys; + + while (*keys) + { + draw_key(*keys, TB_WHITE, TB_RED); + keys++; + } +} + +int main(int argc, char** argv) +{ + (void) argc; + (void) argv; + int ret; + + ret = tb_init(); + + if (ret) + { + fprintf(stderr, "tb_init() failed with error code %d\n", ret); + return 1; + } + + tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); + struct tb_event ev; + + tb_clear(); + draw_keyboard(); + tb_present(); + int inputmode = 0; + int ctrlxpressed = 0; + + while (tb_poll_event(&ev)) + { + switch (ev.type) + { + case TB_EVENT_KEY: + if (ev.key == TB_KEY_CTRL_Q && ctrlxpressed) + { + tb_shutdown(); + return 0; + } + + if (ev.key == TB_KEY_CTRL_C && ctrlxpressed) + { + static int chmap[] = + { + TB_INPUT_ESC | TB_INPUT_MOUSE, // 101 + TB_INPUT_ALT | TB_INPUT_MOUSE, // 110 + TB_INPUT_ESC, // 001 + TB_INPUT_ALT, // 010 + }; + inputmode++; + + if (inputmode >= 4) + { + inputmode = 0; + } + + tb_select_input_mode(chmap[inputmode]); + } + + if (ev.key == TB_KEY_CTRL_X) + { + ctrlxpressed = 1; + } + else + { + ctrlxpressed = 0; + } + + tb_clear(); + draw_keyboard(); + dispatch_press(&ev); + pretty_print_press(&ev); + tb_present(); + break; + + case TB_EVENT_RESIZE: + tb_clear(); + draw_keyboard(); + pretty_print_resize(&ev); + tb_present(); + break; + + case TB_EVENT_MOUSE: + tb_clear(); + draw_keyboard(); + pretty_print_mouse(&ev); + tb_present(); + break; + + default: + break; + } + } + + tb_shutdown(); + return 0; +} diff --git a/framework/3rd/termbox_next/src/demo/makefile b/framework/3rd/termbox_next/src/demo/makefile new file mode 100755 index 0000000..74ec8dd --- /dev/null +++ b/framework/3rd/termbox_next/src/demo/makefile @@ -0,0 +1,30 @@ +CC=gcc +FLAGS=-std=c99 -pedantic -Wall -Werror -g -static +INCL=-I../ +BIND=../../bin + +%.o:%.c + @echo "building source object $@" + @$(CC) $(INCL) $(FLAGS) -c -o $@ $< + +all:keyboard output paint truecolor + +keyboard:keyboard.o + @echo "compiling $@" + @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a + +output:output.o + @echo "compiling $@" + @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a + +paint:paint.o + @echo "compiling $@" + @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a + +truecolor:truecolor.o + @echo "compiling $@" + @$(CC) $(INCL) $(FLAGS) -o $@ $@.o $(BIND)/termbox.a + +clean: + @echo "cleaning workspace" + @rm -rf *.o keyboard output paint truecolor diff --git a/framework/3rd/termbox_next/src/demo/output.c b/framework/3rd/termbox_next/src/demo/output.c new file mode 100755 index 0000000..447975e --- /dev/null +++ b/framework/3rd/termbox_next/src/demo/output.c @@ -0,0 +1,156 @@ +#include +#include +#include "../termbox.h" + +static const char chars[] = "nnnnnnnnnbbbbbbbbbuuuuuuuuuBBBBBBBBB"; + +static const uint32_t all_attrs[] = +{ + 0, + TB_BOLD, + TB_UNDERLINE, + TB_BOLD | TB_UNDERLINE, +}; + +static int next_char(int current) +{ + current++; + + if (!chars[current]) + { + current = 0; + } + + return current; +} + +static void draw_line(int x, int y, uint32_t bg) +{ + int a, c; + int current_char = 0; + + for (a = 0; a < 4; a++) + { + for (c = TB_DEFAULT; c <= TB_WHITE; c++) + { + uint32_t fg = all_attrs[a] | c; + tb_change_cell(x, y, chars[current_char], fg, bg); + current_char = next_char(current_char); + x++; + } + } +} + +static void print_combinations_table(int sx, int sy, const uint32_t* attrs, + int attrs_n) +{ + int i, c; + + for (i = 0; i < attrs_n; i++) + { + for (c = TB_DEFAULT; c <= TB_WHITE; c++) + { + uint32_t bg = attrs[i] | c; + draw_line(sx, sy, bg); + sy++; + } + } +} + +static void draw_all() +{ + tb_clear(); + + tb_select_output_mode(TB_OUTPUT_NORMAL); + static const uint32_t col1[] = {0, TB_BOLD}; + static const uint32_t col2[] = {TB_REVERSE}; + print_combinations_table(1, 1, col1, 2); + print_combinations_table(2 + strlen(chars), 1, col2, 1); + tb_present(); + + tb_select_output_mode(TB_OUTPUT_GRAYSCALE); + int c, x, y; + + for (x = 0, y = 23; x < 24; ++x) + { + tb_change_cell(x, y, '@', x, 0); + tb_change_cell(x + 25, y, ' ', 0, x); + } + + tb_present(); + + tb_select_output_mode(TB_OUTPUT_216); + y++; + + for (c = 0, x = 0; c < 216; ++c, ++x) + { + if (!(x % 24)) + { + x = 0; + ++y; + } + + tb_change_cell(x, y, '@', c, 0); + tb_change_cell(x + 25, y, ' ', 0, c); + } + + tb_present(); + + tb_select_output_mode(TB_OUTPUT_256); + y++; + + for (c = 0, x = 0; c < 256; ++c, ++x) + { + if (!(x % 24)) + { + x = 0; + ++y; + } + + tb_change_cell(x, y, '+', c | ((y & 1) ? TB_UNDERLINE : 0), 0); + tb_change_cell(x + 25, y, ' ', 0, c); + } + + tb_present(); +} + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + int ret = tb_init(); + + if (ret) + { + fprintf(stderr, "tb_init() failed with error code %d\n", ret); + return 1; + } + + draw_all(); + + struct tb_event ev; + + while (tb_poll_event(&ev)) + { + switch (ev.type) + { + case TB_EVENT_KEY: + switch (ev.key) + { + case TB_KEY_ESC: + goto done; + break; + } + + break; + + case TB_EVENT_RESIZE: + draw_all(); + break; + } + } + +done: + tb_shutdown(); + return 0; +} diff --git a/framework/3rd/termbox_next/src/demo/paint.c b/framework/3rd/termbox_next/src/demo/paint.c new file mode 100755 index 0000000..1f68c2d --- /dev/null +++ b/framework/3rd/termbox_next/src/demo/paint.c @@ -0,0 +1,183 @@ +#include "../termbox.h" +#include +#include +#include + +static int curCol = 0; +static int curRune = 0; +static struct tb_cell* backbuf; +static int bbw = 0, bbh = 0; + +static const uint32_t runes[] = +{ + 0x20, // ' ' + 0x2591, // '░' + 0x2592, // '▒' + 0x2593, // '▓' + 0x2588, // '█' +}; + +#define len(a) (sizeof(a)/sizeof(a[0])) + +static const uint32_t colors[] = +{ + TB_BLACK, + TB_RED, + TB_GREEN, + TB_YELLOW, + TB_BLUE, + TB_MAGENTA, + TB_CYAN, + TB_WHITE, +}; + +void updateAndDrawButtons(int* current, int x, int y, int mx, int my, int n, + void (*attrFunc)(int, uint32_t*, uint32_t*, uint32_t*)) +{ + int lx = x; + int ly = y; + + for (int i = 0; i < n; i++) + { + if (lx <= mx && mx <= lx + 3 && ly <= my && my <= ly + 1) + { + *current = i; + } + + uint32_t r; + uint32_t fg, bg; + (*attrFunc)(i, &r, &fg, &bg); + tb_change_cell(lx + 0, ly + 0, r, fg, bg); + tb_change_cell(lx + 1, ly + 0, r, fg, bg); + tb_change_cell(lx + 2, ly + 0, r, fg, bg); + tb_change_cell(lx + 3, ly + 0, r, fg, bg); + tb_change_cell(lx + 0, ly + 1, r, fg, bg); + tb_change_cell(lx + 1, ly + 1, r, fg, bg); + tb_change_cell(lx + 2, ly + 1, r, fg, bg); + tb_change_cell(lx + 3, ly + 1, r, fg, bg); + lx += 4; + } + + lx = x; + ly = y; + + for (int i = 0; i < n; i++) + { + if (*current == i) + { + uint32_t fg = TB_RED | TB_BOLD; + uint32_t bg = TB_DEFAULT; + tb_change_cell(lx + 0, ly + 2, '^', fg, bg); + tb_change_cell(lx + 1, ly + 2, '^', fg, bg); + tb_change_cell(lx + 2, ly + 2, '^', fg, bg); + tb_change_cell(lx + 3, ly + 2, '^', fg, bg); + } + + lx += 4; + } +} + +void runeAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg) +{ + *r = runes[i]; + *fg = TB_DEFAULT; + *bg = TB_DEFAULT; +} + +void colorAttrFunc(int i, uint32_t* r, uint32_t* fg, uint32_t* bg) +{ + *r = ' '; + *fg = TB_DEFAULT; + *bg = colors[i]; +} + +void updateAndRedrawAll(int mx, int my) +{ + tb_clear(); + + if (mx != -1 && my != -1) + { + backbuf[bbw * my + mx].ch = runes[curRune]; + backbuf[bbw * my + mx].fg = colors[curCol]; + } + + memcpy(tb_cell_buffer(), backbuf, sizeof(struct tb_cell)*bbw * bbh); + int h = tb_height(); + updateAndDrawButtons(&curRune, 0, 0, mx, my, len(runes), runeAttrFunc); + updateAndDrawButtons(&curCol, 0, h - 3, mx, my, len(colors), colorAttrFunc); + tb_present(); +} + +void reallocBackBuffer(int w, int h) +{ + bbw = w; + bbh = h; + + if (backbuf) + { + free(backbuf); + } + + backbuf = calloc(sizeof(struct tb_cell), w * h); +} + +int main(int argv, char** argc) +{ + (void)argc; + (void)argv; + int code = tb_init(); + + if (code < 0) + { + fprintf(stderr, "termbox init failed, code: %d\n", code); + return -1; + } + + tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE); + int w = tb_width(); + int h = tb_height(); + reallocBackBuffer(w, h); + updateAndRedrawAll(-1, -1); + + for (;;) + { + struct tb_event ev; + int mx = -1; + int my = -1; + int t = tb_poll_event(&ev); + + if (t == -1) + { + tb_shutdown(); + fprintf(stderr, "termbox poll event error\n"); + return -1; + } + + switch (t) + { + case TB_EVENT_KEY: + if (ev.key == TB_KEY_ESC) + { + tb_shutdown(); + return 0; + } + + break; + + case TB_EVENT_MOUSE: + if (ev.key == TB_KEY_MOUSE_LEFT) + { + mx = ev.x; + my = ev.y; + } + + break; + + case TB_EVENT_RESIZE: + reallocBackBuffer(ev.w, ev.h); + break; + } + + updateAndRedrawAll(mx, my); + } +} diff --git a/framework/3rd/termbox_next/src/demo/truecolor.c b/framework/3rd/termbox_next/src/demo/truecolor.c new file mode 100755 index 0000000..33609fd --- /dev/null +++ b/framework/3rd/termbox_next/src/demo/truecolor.c @@ -0,0 +1,69 @@ +#include "termbox.h" + +int main() +{ + tb_init(); + tb_select_output_mode(TB_OUTPUT_TRUECOLOR); + int w = tb_width(); + int h = tb_height(); + uint32_t bg = 0x000000, fg = 0x000000; + tb_clear(); + int z = 0; + + for (int y = 1; y < h; y++) + { + for (int x = 1; x < w; x++) + { + uint32_t ch; + utf8_char_to_unicode(&ch, "x"); + fg = 0; + + if (z % 2 == 0) + { + fg |= TB_BOLD; + } + + if (z % 3 == 0) + { + fg |= TB_UNDERLINE; + } + + if (z % 5 == 0) + { + fg |= TB_REVERSE; + } + + tb_change_cell(x, y, ch, fg, bg); + bg += 0x000101; + z++; + } + + bg += 0x080000; + + if (bg > 0xFFFFFF) + { + bg = 0; + } + } + + tb_present(); + + while (1) + { + struct tb_event ev; + int t = tb_poll_event(&ev); + + if (t == -1) + { + break; + } + + if (t == TB_EVENT_KEY) + { + break; + } + } + + tb_shutdown(); + return 0; +} diff --git a/framework/3rd/termbox_next/src/input.c b/framework/3rd/termbox_next/src/input.c new file mode 100755 index 0000000..2d3f84d --- /dev/null +++ b/framework/3rd/termbox_next/src/input.c @@ -0,0 +1,319 @@ +#include +#include +#include +#include +#include + +#include "term.h" + +#define BUFFER_SIZE_MAX 16 + +// if s1 starts with s2 returns 1, else 0 +static int starts_with(const char* s1, const char* s2) +{ + // nice huh? + while (*s2) + { + if (*s1++ != *s2++) + { + return 0; + } + } + + return 1; +} + +static int parse_mouse_event(struct tb_event* event, const char* buf, int len) +{ + if ((len >= 6) && starts_with(buf, "\033[M")) + { + // X10 mouse encoding, the simplest one + // \033 [ M Cb Cx Cy + int b = buf[3] - 32; + + switch (b & 3) + { + case 0: + if ((b & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_UP; + } + else + { + event->key = TB_KEY_MOUSE_LEFT; + } + + break; + + case 1: + if ((b & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_DOWN; + } + else + { + event->key = TB_KEY_MOUSE_MIDDLE; + } + + break; + + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + + default: + return -6; + } + + event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default + + if ((b & 32) != 0) + { + event->mod |= TB_MOD_MOTION; + } + + // the coord is 1,1 for upper left + event->x = (uint8_t)buf[4] - 1 - 32; + event->y = (uint8_t)buf[5] - 1 - 32; + return 6; + } + else if (starts_with(buf, "\033[<") || starts_with(buf, "\033[")) + { + // xterm 1006 extended mode or urxvt 1015 extended mode + // xterm: \033 [ < Cb ; Cx ; Cy (M or m) + // urxvt: \033 [ Cb ; Cx ; Cy M + int i, mi = -1, starti = -1; + int isM, isU, s1 = -1, s2 = -1; + int n1 = 0, n2 = 0, n3 = 0; + + for (i = 0; i < len; i++) + { + // We search the first (s1) and the last (s2) ';' + if (buf[i] == ';') + { + if (s1 == -1) + { + s1 = i; + } + + s2 = i; + } + + // We search for the first 'm' or 'M' + if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) + { + mi = i; + break; + } + } + + if (mi == -1) + { + return 0; + } + + // whether it's a capital M or not + isM = (buf[mi] == 'M'); + + if (buf[2] == '<') + { + isU = 0; + starti = 3; + } + else + { + isU = 1; + starti = 2; + } + + if (s1 == -1 || s2 == -1 || s1 == s2) + { + return 0; + } + + n1 = strtoul(&buf[starti], NULL, 10); + n2 = strtoul(&buf[s1 + 1], NULL, 10); + n3 = strtoul(&buf[s2 + 1], NULL, 10); + + if (isU) + { + n1 -= 32; + } + + switch (n1 & 3) + { + case 0: + if ((n1 & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_UP; + } + else + { + event->key = TB_KEY_MOUSE_LEFT; + } + + break; + + case 1: + if ((n1 & 64) != 0) + { + event->key = TB_KEY_MOUSE_WHEEL_DOWN; + } + else + { + event->key = TB_KEY_MOUSE_MIDDLE; + } + + break; + + case 2: + event->key = TB_KEY_MOUSE_RIGHT; + break; + + case 3: + event->key = TB_KEY_MOUSE_RELEASE; + break; + + default: + return mi + 1; + } + + if (!isM) + { + // on xterm mouse release is signaled by lowercase m + event->key = TB_KEY_MOUSE_RELEASE; + } + + event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default + + if ((n1 & 32) != 0) + { + event->mod |= TB_MOD_MOTION; + } + + event->x = (uint8_t)n2 - 1; + event->y = (uint8_t)n3 - 1; + return mi + 1; + } + + return 0; +} + +// convert escape sequence to event, and return consumed bytes on success (failure == 0) +static int parse_escape_seq(struct tb_event* event, const char* buf, int len) +{ + int mouse_parsed = parse_mouse_event(event, buf, len); + + if (mouse_parsed != 0) + { + return mouse_parsed; + } + + // it's pretty simple here, find 'starts_with' match and return success, else return failure + int i; + + for (i = 0; keys[i]; i++) + { + if (starts_with(buf, keys[i])) + { + event->ch = 0; + event->key = 0xFFFF - i; + return strlen(keys[i]); + } + } + + return 0; +} + +bool extract_event(struct tb_event* event, struct ringbuffer* inbuf, + int inputmode) +{ + char buf[BUFFER_SIZE_MAX + 1]; + int nbytes = ringbuffer_data_size(inbuf); + + if (nbytes > BUFFER_SIZE_MAX) + { + nbytes = BUFFER_SIZE_MAX; + } + + if (nbytes == 0) + { + return false; + } + + ringbuffer_read(inbuf, buf, nbytes); + buf[nbytes] = '\0'; + + if (buf[0] == '\033') + { + int n = parse_escape_seq(event, buf, nbytes); + + if (n != 0) + { + bool success = true; + + if (n < 0) + { + success = false; + n = -n; + } + + ringbuffer_pop(inbuf, 0, n); + return success; + } + else + { + // it's not escape sequence, then it's ALT or ESC, check inputmode + if (inputmode & TB_INPUT_ESC) + { + // if we're in escape mode, fill ESC event, pop buffer, return success + event->ch = 0; + event->key = TB_KEY_ESC; + event->mod = 0; + ringbuffer_pop(inbuf, 0, 1); + return true; + } + else if (inputmode & TB_INPUT_ALT) + { + // if we're in alt mode, set ALT modifier to event and redo parsing + event->mod = TB_MOD_ALT; + ringbuffer_pop(inbuf, 0, 1); + return extract_event(event, inbuf, inputmode); + } + + assert(!"never got here"); + } + } + + // if we're here, this is not an escape sequence and not an alt sequence + // so, it's a FUNCTIONAL KEY or a UNICODE character + + // first of all check if it's a functional key*/ + if ((unsigned char)buf[0] <= TB_KEY_SPACE || + (unsigned char)buf[0] == TB_KEY_BACKSPACE2) + { + // fill event, pop buffer, return success + event->ch = 0; + event->key = (uint16_t)buf[0]; + ringbuffer_pop(inbuf, 0, 1); + return true; + } + + // feh... we got utf8 here + + // check if there is all bytes + if (nbytes >= utf8_char_length(buf[0])) + { + // everything ok, fill event, pop buffer, return success + utf8_char_to_unicode(&event->ch, buf); + event->key = 0; + ringbuffer_pop(inbuf, 0, utf8_char_length(buf[0])); + return true; + } + + return false; +} diff --git a/framework/3rd/termbox_next/src/memstream.c b/framework/3rd/termbox_next/src/memstream.c new file mode 100755 index 0000000..d218b54 --- /dev/null +++ b/framework/3rd/termbox_next/src/memstream.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include "memstream.h" + +void memstream_init(struct memstream* s, int fd, void* buffer, size_t len) +{ + s->file = fd; + s->data = buffer; + s->pos = 0; + s->capa = len; +} + +void memstream_flush(struct memstream* s) +{ + write(s->file, s->data, s->pos); + s->pos = 0; +} + +void memstream_write(struct memstream* s, void* source, size_t len) +{ + unsigned char* data = source; + + if (s->pos + len > s->capa) + { + memstream_flush(s); + } + + memcpy(s->data + s->pos, data, len); + s->pos += len; +} + +void memstream_puts(struct memstream* s, const char* str) +{ + memstream_write(s, (void*) str, strlen(str)); +} diff --git a/framework/3rd/termbox_next/src/memstream.h b/framework/3rd/termbox_next/src/memstream.h new file mode 100755 index 0000000..c5d864a --- /dev/null +++ b/framework/3rd/termbox_next/src/memstream.h @@ -0,0 +1,20 @@ +#ifndef H_MEMSTREAM +#define H_MEMSTREAM + +#include +#include + +struct memstream +{ + size_t pos; + size_t capa; + int file; + unsigned char* data; +}; + +void memstream_init(struct memstream* s, int fd, void* buffer, size_t len); +void memstream_flush(struct memstream* s); +void memstream_write(struct memstream* s, void* source, size_t len); +void memstream_puts(struct memstream* s, const char* str); + +#endif diff --git a/framework/3rd/termbox_next/src/ringbuffer.c b/framework/3rd/termbox_next/src/ringbuffer.c new file mode 100755 index 0000000..a8de0f6 --- /dev/null +++ b/framework/3rd/termbox_next/src/ringbuffer.c @@ -0,0 +1,195 @@ +#include "ringbuffer.h" +#include +#include +#include +#include // for ptrdiff_t + +int init_ringbuffer(struct ringbuffer* r, size_t size) +{ + r->buf = (char*)malloc(size); + + if (!r->buf) + { + return ERINGBUFFER_ALLOC_FAIL; + } + + r->size = size; + clear_ringbuffer(r); + + return 0; +} + +void free_ringbuffer(struct ringbuffer* r) +{ + free(r->buf); +} + +void clear_ringbuffer(struct ringbuffer* r) +{ + r->begin = 0; + r->end = 0; +} + +size_t ringbuffer_free_space(struct ringbuffer* r) +{ + if (r->begin == 0 && r->end == 0) + { + return r->size; + } + + if (r->begin < r->end) + { + return r->size - (r->end - r->begin) - 1; + } + else + { + return r->begin - r->end - 1; + } +} + +size_t ringbuffer_data_size(struct ringbuffer* r) +{ + if (r->begin == 0 && r->end == 0) + { + return 0; + } + + if (r->begin <= r->end) + { + return r->end - r->begin + 1; + } + else + { + return r->size - (r->begin - r->end) + 1; + } +} + + +void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size) +{ + if (ringbuffer_free_space(r) < size) + { + return; + } + + if (r->begin == 0 && r->end == 0) + { + memcpy(r->buf, data, size); + r->begin = r->buf; + r->end = r->buf + size - 1; + return; + } + + r->end++; + + if (r->begin < r->end) + { + if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) + { + // we can fit without cut + memcpy(r->end, data, size); + r->end += size - 1; + } + else + { + // make a cut + size_t s = r->buf + r->size - r->end; + memcpy(r->end, data, s); + size -= s; + memcpy(r->buf, (char*)data + s, size); + r->end = r->buf + size - 1; + } + } + else + { + memcpy(r->end, data, size); + r->end += size - 1; + } +} + +void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size) +{ + if (ringbuffer_data_size(r) < size) + { + return; + } + + int need_clear = 0; + + if (ringbuffer_data_size(r) == size) + { + need_clear = 1; + } + + if (r->begin < r->end) + { + if (data) + { + memcpy(data, r->begin, size); + } + + r->begin += size; + } + else + { + if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) + { + if (data) + { + memcpy(data, r->begin, size); + } + + r->begin += size; + } + else + { + size_t s = r->buf + r->size - r->begin; + + if (data) + { + memcpy(data, r->begin, s); + } + + size -= s; + + if (data) + { + memcpy((char*)data + s, r->buf, size); + } + + r->begin = r->buf + size; + } + } + + if (need_clear) + { + clear_ringbuffer(r); + } +} + +void ringbuffer_read(struct ringbuffer* r, void* data, size_t size) +{ + if (ringbuffer_data_size(r) < size) + { + return; + } + + if (r->begin < r->end) + { + memcpy(data, r->begin, size); + } + else + { + if ((size_t)(r->buf + (ptrdiff_t)r->size - r->begin) >= size) + { + memcpy(data, r->begin, size); + } + else + { + size_t s = r->buf + r->size - r->begin; + memcpy(data, r->begin, s); + size -= s; + memcpy((char*)data + s, r->buf, size); + } + } +} diff --git a/framework/3rd/termbox_next/src/ringbuffer.h b/framework/3rd/termbox_next/src/ringbuffer.h new file mode 100755 index 0000000..9a8b0d6 --- /dev/null +++ b/framework/3rd/termbox_next/src/ringbuffer.h @@ -0,0 +1,26 @@ +#ifndef H_RINGBUFFER +#define H_RINGBUFFER + +#include + +#define ERINGBUFFER_ALLOC_FAIL -1 + +struct ringbuffer +{ + char* buf; + size_t size; + + char* begin; + char* end; +}; + +int init_ringbuffer(struct ringbuffer* r, size_t size); +void free_ringbuffer(struct ringbuffer* r); +void clear_ringbuffer(struct ringbuffer* r); +size_t ringbuffer_free_space(struct ringbuffer* r); +size_t ringbuffer_data_size(struct ringbuffer* r); +void ringbuffer_push(struct ringbuffer* r, const void* data, size_t size); +void ringbuffer_pop(struct ringbuffer* r, void* data, size_t size); +void ringbuffer_read(struct ringbuffer* r, void* data, size_t size); + +#endif diff --git a/framework/3rd/termbox_next/src/term.c b/framework/3rd/termbox_next/src/term.c new file mode 100755 index 0000000..c94f394 --- /dev/null +++ b/framework/3rd/termbox_next/src/term.c @@ -0,0 +1,412 @@ +#include +#include +#include +#include +#include + +#include "term.h" +#define ENTER_MOUSE_SEQ "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h" +#define EXIT_MOUSE_SEQ "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l" + +#define EUNSUPPORTED_TERM -1 + +// rxvt-256color +static const char* rxvt_256color_keys[] = +{ + "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* rxvt_256color_funcs[] = +{ + "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +// Eterm +static const char* eterm_keys[] = +{ + "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* eterm_funcs[] = +{ + "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "", "", "", "", +}; + +// screen +static const char* screen_keys[] = +{ + "\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", + "\033OA", "\033OB", "\033OD", "\033OC", NULL +}; +static const char* screen_funcs[] = +{ + "\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", + "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +// rxvt-unicode +static const char* rxvt_unicode_keys[] = +{ + "\033[11~", "\033[12~", "\033[13~", "\033[14~", "\033[15~", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[7~", "\033[8~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* rxvt_unicode_funcs[] = +{ + "\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", + "\033[7m", "\033=", "\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +// linux +static const char* linux_keys[] = +{ + "\033[[A", "\033[[B", "\033[[C", "\033[[D", "\033[[E", "\033[17~", + "\033[18~", "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", + "\033[2~", "\033[3~", "\033[1~", "\033[4~", "\033[5~", "\033[6~", + "\033[A", "\033[B", "\033[D", "\033[C", NULL +}; +static const char* linux_funcs[] = +{ + "", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", + "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "", +}; + +// xterm +static const char* xterm_keys[] = +{ + "\033OP", "\033OQ", "\033OR", "\033OS", "\033[15~", "\033[17~", "\033[18~", + "\033[19~", "\033[20~", "\033[21~", "\033[23~", "\033[24~", "\033[2~", + "\033[3~", "\033OH", "\033OF", "\033[5~", "\033[6~", "\033OA", "\033OB", + "\033OD", "\033OC", NULL +}; +static const char* xterm_funcs[] = +{ + "\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", + "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", + "\033[?1h\033=", "\033[?1l\033>", ENTER_MOUSE_SEQ, EXIT_MOUSE_SEQ, +}; + +struct term +{ + const char* name; + const char** keys; + const char** funcs; +}; + +static struct term terms[] = +{ + {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs}, + {"Eterm", eterm_keys, eterm_funcs}, + {"screen", screen_keys, screen_funcs}, + {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs}, + {"linux", linux_keys, linux_funcs}, + {"xterm", xterm_keys, xterm_funcs}, + {0, 0, 0}, +}; + +static int init_from_terminfo = 0; +const char** keys; +const char** funcs; + +static int try_compatible(const char* term, const char* name, + const char** tkeys, const char** tfuncs) +{ + if (strstr(term, name)) + { + keys = tkeys; + funcs = tfuncs; + return 0; + } + + return EUNSUPPORTED_TERM; +} + +static int init_term_builtin(void) +{ + int i; + const char* term = getenv("TERM"); + + if (term) + { + for (i = 0; terms[i].name; i++) + { + if (!strcmp(terms[i].name, term)) + { + keys = terms[i].keys; + funcs = terms[i].funcs; + return 0; + } + } + + // let's do some heuristic, maybe it's a compatible terminal + if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0) + { + return 0; + } + + if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0) + { + return 0; + } + + // let's assume that 'cygwin' is xterm compatible + if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0) + { + return 0; + } + } + + return EUNSUPPORTED_TERM; +} + +// terminfo +static char* read_file(const char* file) +{ + FILE* f = fopen(file, "rb"); + + if (!f) + { + return 0; + } + + struct stat st; + + if (fstat(fileno(f), &st) != 0) + { + fclose(f); + return 0; + } + + char* data = malloc(st.st_size); + + if (!data) + { + fclose(f); + return 0; + } + + if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) + { + fclose(f); + free(data); + return 0; + } + + fclose(f); + return data; +} + +static char* terminfo_try_path(const char* path, const char* term) +{ + char tmp[4096]; + snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term); + tmp[sizeof(tmp) - 1] = '\0'; + char* data = read_file(tmp); + + if (data) + { + return data; + } + + // fallback to darwin specific dirs structure + snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term); + tmp[sizeof(tmp) - 1] = '\0'; + return read_file(tmp); +} + +static char* load_terminfo(void) +{ + char tmp[4096]; + const char* term = getenv("TERM"); + + if (!term) + { + return 0; + } + + // if TERMINFO is set, no other directory should be searched + const char* terminfo = getenv("TERMINFO"); + + if (terminfo) + { + return terminfo_try_path(terminfo, term); + } + + // next, consider ~/.terminfo + const char* home = getenv("HOME"); + + if (home) + { + snprintf(tmp, sizeof(tmp), "%s/.terminfo", home); + tmp[sizeof(tmp) - 1] = '\0'; + char* data = terminfo_try_path(tmp, term); + + if (data) + { + return data; + } + } + + // next, TERMINFO_DIRS + const char* dirs = getenv("TERMINFO_DIRS"); + + if (dirs) + { + snprintf(tmp, sizeof(tmp), "%s", dirs); + tmp[sizeof(tmp) - 1] = '\0'; + char* dir = strtok(tmp, ":"); + + while (dir) + { + const char* cdir = dir; + + if (strcmp(cdir, "") == 0) + { + cdir = "/usr/share/terminfo"; + } + + char* data = terminfo_try_path(cdir, term); + + if (data) + { + return data; + } + + dir = strtok(0, ":"); + } + } + + // fallback to /usr/share/terminfo + return terminfo_try_path("/usr/share/terminfo", term); +} + +#define TI_MAGIC 0432 +#define TI_ALT_MAGIC 542 +#define TI_HEADER_LENGTH 12 +#define TB_KEYS_NUM 22 + +static const char* terminfo_copy_string(char* data, int str, int table) +{ + const int16_t off = *(int16_t*)(data + str); + const char* src = data + table + off; + int len = strlen(src); + char* dst = malloc(len + 1); + strcpy(dst, src); + return dst; +} + +const int16_t ti_funcs[] = +{ + 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88, +}; + +const int16_t ti_keys[] = +{ + // apparently not a typo; 67 is F10 for whatever reason + 66, 68, 69, 70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, + 81, 87, 61, 79, 83, +}; + +int init_term(void) +{ + int i; + char* data = load_terminfo(); + + if (!data) + { + init_from_terminfo = 0; + return init_term_builtin(); + } + + int16_t* header = (int16_t*)data; + + const int number_sec_len = header[0] == TI_ALT_MAGIC ? 4 : 2; + + if ((header[1] + header[2]) % 2) + { + // old quirk to align everything on word boundaries + header[2] += 1; + } + + const int str_offset = TI_HEADER_LENGTH + + header[1] + header[2] + number_sec_len * header[3]; + const int table_offset = str_offset + 2 * header[4]; + + keys = malloc(sizeof(const char*) * (TB_KEYS_NUM + 1)); + + for (i = 0; i < TB_KEYS_NUM; i++) + { + keys[i] = terminfo_copy_string(data, + str_offset + 2 * ti_keys[i], table_offset); + } + + keys[i] = NULL; + + funcs = malloc(sizeof(const char*) * T_FUNCS_NUM); + + // the last two entries are reserved for mouse. because the table offset is + // not there, the two entries have to fill in manually + for (i = 0; i < T_FUNCS_NUM - 2; i++) + { + funcs[i] = terminfo_copy_string(data, + str_offset + 2 * ti_funcs[i], table_offset); + } + + funcs[T_FUNCS_NUM - 2] = ENTER_MOUSE_SEQ; + funcs[T_FUNCS_NUM - 1] = EXIT_MOUSE_SEQ; + init_from_terminfo = 1; + free(data); + return 0; +} + +void shutdown_term(void) +{ + if (init_from_terminfo) + { + int i; + + for (i = 0; i < TB_KEYS_NUM; i++) + { + free((void*)keys[i]); + } + + // the last two entries are reserved for mouse. because the table offset + // is not there, the two entries have to fill in manually and do not + // need to be freed. + for (i = 0; i < T_FUNCS_NUM - 2; i++) + { + free((void*)funcs[i]); + } + + free(keys); + free(funcs); + } +} diff --git a/framework/3rd/termbox_next/src/term.h b/framework/3rd/termbox_next/src/term.h new file mode 100755 index 0000000..8f4d93d --- /dev/null +++ b/framework/3rd/termbox_next/src/term.h @@ -0,0 +1,38 @@ +#ifndef H_TERM +#define H_TERM + +#include "termbox.h" +#include "ringbuffer.h" +#include + +#define EUNSUPPORTED_TERM -1 + +enum +{ + T_ENTER_CA, + T_EXIT_CA, + T_SHOW_CURSOR, + T_HIDE_CURSOR, + T_CLEAR_SCREEN, + T_SGR0, + T_UNDERLINE, + T_BOLD, + T_BLINK, + T_REVERSE, + T_ENTER_KEYPAD, + T_EXIT_KEYPAD, + T_ENTER_MOUSE, + T_EXIT_MOUSE, + T_FUNCS_NUM, +}; + +extern const char** keys; +extern const char** funcs; + +// true on success, false on failure +bool extract_event(struct tb_event* event, struct ringbuffer* inbuf, + int inputmode); +int init_term(void); +void shutdown_term(void); + +#endif diff --git a/framework/3rd/termbox_next/src/termbox.c b/framework/3rd/termbox_next/src/termbox.c new file mode 100755 index 0000000..72a4335 --- /dev/null +++ b/framework/3rd/termbox_next/src/termbox.c @@ -0,0 +1,885 @@ +#include "term.h" +#include "termbox.h" +#include "memstream.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct cellbuf +{ + int width; + int height; + struct tb_cell* cells; +}; + +#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)] +#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1) +#define LAST_COORD_INIT -1 + +static struct termios orig_tios; + +static struct cellbuf back_buffer; +static struct cellbuf front_buffer; +static unsigned char write_buffer_data[32 * 1024]; +static struct memstream write_buffer; + +static int termw = -1; +static int termh = -1; + +static int inputmode = TB_INPUT_ESC; +static int outputmode = TB_OUTPUT_NORMAL; + +static struct ringbuffer inbuf; + +static int out; +static FILE* in; + +static int out_fileno; +static int in_fileno; + +static int winch_fds[2]; + +static int lastx = LAST_COORD_INIT; +static int lasty = LAST_COORD_INIT; +static int cursor_x = -1; +static int cursor_y = -1; + +static uint32_t background = TB_DEFAULT; +static uint32_t foreground = TB_DEFAULT; + +static void write_cursor(int x, int y); +static void write_sgr(uint32_t fg, uint32_t bg); + +static void cellbuf_init(struct cellbuf* buf, int width, int height); +static void cellbuf_resize(struct cellbuf* buf, int width, int height); +static void cellbuf_clear(struct cellbuf* buf); +static void cellbuf_free(struct cellbuf* buf); + +static void update_size(void); +static void update_term_size(void); +static void send_attr(uint32_t fg, uint32_t bg); +static void send_char(int x, int y, uint32_t c); +static void send_clear(void); +static void sigwinch_handler(int xxx); +static int wait_fill_event(struct tb_event* event, struct timeval* timeout); + +// may happen in a different thread +static volatile int buffer_size_change_request; + +int tb_init_file(const char* name) +{ + out = open(name, O_WRONLY); + in = fopen(name, "r"); + + if (out == -1 || !in) + { + if (out != -1) + { + close(out); + } + + if (in) + { + fclose(in); + } + + return TB_EFAILED_TO_OPEN_TTY; + } + + out_fileno = out; + in_fileno = fileno(in); + + if (init_term() < 0) + { + close(out); + fclose(in); + + return TB_EUNSUPPORTED_TERMINAL; + } + + if (pipe(winch_fds) < 0) + { + close(out); + fclose(in); + + return TB_EPIPE_TRAP_ERROR; + } + + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigwinch_handler; + sa.sa_flags = 0; + sigaction(SIGWINCH, &sa, 0); + tcgetattr(out_fileno, &orig_tios); + + struct termios tios; + + memcpy(&tios, &orig_tios, sizeof(tios)); + tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP + | INLCR | IGNCR | ICRNL | IXON); + tios.c_oflag &= ~OPOST; + tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tios.c_cflag &= ~(CSIZE | PARENB); + tios.c_cflag |= CS8; + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + tcsetattr(out_fileno, TCSAFLUSH, &tios); + + memstream_init(&write_buffer, out_fileno, write_buffer_data, + sizeof(write_buffer_data)); + memstream_puts(&write_buffer, funcs[T_ENTER_CA]); + memstream_puts(&write_buffer, funcs[T_ENTER_KEYPAD]); + memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]); + send_clear(); + + update_term_size(); + cellbuf_init(&back_buffer, termw, termh); + cellbuf_init(&front_buffer, termw, termh); + cellbuf_clear(&back_buffer); + cellbuf_clear(&front_buffer); + init_ringbuffer(&inbuf, 4096); + return 0; +} + +int tb_init(void) +{ + return tb_init_file("/dev/tty"); +} + +void tb_shutdown(void) +{ + if (termw == -1) + { + fputs("tb_shutdown() should not be called twice.", stderr); + abort(); + } + + memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]); + memstream_puts(&write_buffer, funcs[T_SGR0]); + memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]); + memstream_puts(&write_buffer, funcs[T_EXIT_CA]); + memstream_puts(&write_buffer, funcs[T_EXIT_KEYPAD]); + memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]); + memstream_flush(&write_buffer); + tcsetattr(out_fileno, TCSAFLUSH, &orig_tios); + + shutdown_term(); + close(out); + fclose(in); + close(winch_fds[0]); + close(winch_fds[1]); + + cellbuf_free(&back_buffer); + cellbuf_free(&front_buffer); + free_ringbuffer(&inbuf); + termw = termh = -1; +} + +void tb_present(void) +{ + int x, y, w, i; + struct tb_cell* back, *front; + + // invalidate cursor position + lastx = LAST_COORD_INIT; + lasty = LAST_COORD_INIT; + + if (buffer_size_change_request) + { + update_size(); + buffer_size_change_request = 0; + } + + for (y = 0; y < front_buffer.height; ++y) + { + for (x = 0; x < front_buffer.width;) + { + back = &CELL(&back_buffer, x, y); + front = &CELL(&front_buffer, x, y); + w = wcwidth(back->ch); + + if (w < 1) + { + w = 1; + } + + if (memcmp(back, front, sizeof(struct tb_cell)) == 0) + { + x += w; + continue; + } + + memcpy(front, back, sizeof(struct tb_cell)); + send_attr(back->fg, back->bg); + + if (w > 1 && x >= front_buffer.width - (w - 1)) + { + // Not enough room for wide ch, so send spaces + for (i = x; i < front_buffer.width; ++i) + { + send_char(i, y, ' '); + } + } + else + { + send_char(x, y, back->ch); + + for (i = 1; i < w; ++i) + { + front = &CELL(&front_buffer, x + i, y); + front->ch = 0; + front->fg = back->fg; + front->bg = back->bg; + } + } + + x += w; + } + } + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) + { + write_cursor(cursor_x, cursor_y); + } + + memstream_flush(&write_buffer); +} + +void tb_set_cursor(int cx, int cy) +{ + if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy)) + { + memstream_puts(&write_buffer, funcs[T_SHOW_CURSOR]); + } + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy)) + { + memstream_puts(&write_buffer, funcs[T_HIDE_CURSOR]); + } + + cursor_x = cx; + cursor_y = cy; + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) + { + write_cursor(cursor_x, cursor_y); + } +} + +void tb_put_cell(int x, int y, const struct tb_cell* cell) +{ + if ((unsigned)x >= (unsigned)back_buffer.width) + { + return; + } + + if ((unsigned)y >= (unsigned)back_buffer.height) + { + return; + } + + CELL(&back_buffer, x, y) = *cell; +} + +void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, uint32_t bg) +{ + struct tb_cell c = {ch, fg, bg}; + tb_put_cell(x, y, &c); +} + +void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells) +{ + if (x + w < 0 || x >= back_buffer.width) + { + return; + } + + if (y + h < 0 || y >= back_buffer.height) + { + return; + } + + int xo = 0, yo = 0, ww = w, hh = h; + + if (x < 0) + { + xo = -x; + ww -= xo; + x = 0; + } + + if (y < 0) + { + yo = -y; + hh -= yo; + y = 0; + } + + if (ww > back_buffer.width - x) + { + ww = back_buffer.width - x; + } + + if (hh > back_buffer.height - y) + { + hh = back_buffer.height - y; + } + + int sy; + struct tb_cell* dst = &CELL(&back_buffer, x, y); + const struct tb_cell* src = cells + yo * w + xo; + size_t size = sizeof(struct tb_cell) * ww; + + for (sy = 0; sy < hh; ++sy) + { + memcpy(dst, src, size); + dst += back_buffer.width; + src += w; + } +} + +struct tb_cell* tb_cell_buffer(void) +{ + return back_buffer.cells; +} + +int tb_poll_event(struct tb_event* event) +{ + return wait_fill_event(event, 0); +} + +int tb_peek_event(struct tb_event* event, int timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000; + return wait_fill_event(event, &tv); +} + +int tb_width(void) +{ + return termw; +} + +int tb_height(void) +{ + return termh; +} + +void tb_clear(void) +{ + if (buffer_size_change_request) + { + update_size(); + buffer_size_change_request = 0; + } + + cellbuf_clear(&back_buffer); +} + +int tb_select_input_mode(int mode) +{ + if (mode) + { + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0) + { + mode |= TB_INPUT_ESC; + } + + // technically termbox can handle that, but let's be nice + // and show here what mode is actually used + if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT)) + { + mode &= ~TB_INPUT_ALT; + } + + inputmode = mode; + + if (mode & TB_INPUT_MOUSE) + { + memstream_puts(&write_buffer, funcs[T_ENTER_MOUSE]); + memstream_flush(&write_buffer); + } + else + { + memstream_puts(&write_buffer, funcs[T_EXIT_MOUSE]); + memstream_flush(&write_buffer); + } + } + + return inputmode; +} + +int tb_select_output_mode(int mode) +{ + if (mode) + { + outputmode = mode; + } + + return outputmode; +} + +void tb_set_clear_attributes(uint32_t fg, uint32_t bg) +{ + foreground = fg; + background = bg; +} + +static unsigned convertnum(uint32_t num, char* buf) +{ + unsigned i, l = 0; + int ch; + + do + { + buf[l++] = '0' + (num % 10); + num /= 10; + } + while (num); + + for (i = 0; i < l / 2; i++) + { + ch = buf[i]; + buf[i] = buf[l - 1 - i]; + buf[l - 1 - i] = ch; + } + + return l; +} + +#define WRITE_LITERAL(X) memstream_write(&write_buffer, (X), sizeof(X) -1) +#define WRITE_INT(X) memstream_write(&write_buffer, buf, convertnum((X), buf)) + +static void write_cursor(int x, int y) +{ + char buf[32]; + WRITE_LITERAL("\033["); + WRITE_INT(y + 1); + WRITE_LITERAL(";"); + WRITE_INT(x + 1); + WRITE_LITERAL("H"); +} + +static void write_sgr(uint32_t fg, uint32_t bg) +{ + char buf[32]; + + if (outputmode != TB_OUTPUT_TRUECOLOR && fg == TB_DEFAULT && bg == TB_DEFAULT) + { + return; + } + + switch (outputmode) + { + case TB_OUTPUT_TRUECOLOR: + WRITE_LITERAL("\033[38;2;"); + WRITE_INT(fg >> 16 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(fg >> 8 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(fg & 0xFF); + WRITE_LITERAL(";48;2;"); + WRITE_INT(bg >> 16 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(bg >> 8 & 0xFF); + WRITE_LITERAL(";"); + WRITE_INT(bg & 0xFF); + WRITE_LITERAL("m"); + break; + + case TB_OUTPUT_256: + case TB_OUTPUT_216: + case TB_OUTPUT_GRAYSCALE: + WRITE_LITERAL("\033["); + + if (fg != TB_DEFAULT) + { + WRITE_LITERAL("38;5;"); + WRITE_INT(fg); + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL(";"); + } + } + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL("48;5;"); + WRITE_INT(bg); + } + + WRITE_LITERAL("m"); + break; + + case TB_OUTPUT_NORMAL: + default: + WRITE_LITERAL("\033["); + + if (fg != TB_DEFAULT) + { + WRITE_LITERAL("3"); + WRITE_INT(fg - 1); + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL(";"); + } + } + + if (bg != TB_DEFAULT) + { + WRITE_LITERAL("4"); + WRITE_INT(bg - 1); + } + + WRITE_LITERAL("m"); + break; + } +} + +static void cellbuf_init(struct cellbuf* buf, int width, int height) +{ + buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height); + assert(buf->cells); + buf->width = width; + buf->height = height; +} + +static void cellbuf_resize(struct cellbuf* buf, int width, int height) +{ + if (buf->width == width && buf->height == height) + { + return; + } + + int oldw = buf->width; + int oldh = buf->height; + struct tb_cell* oldcells = buf->cells; + + cellbuf_init(buf, width, height); + cellbuf_clear(buf); + + int minw = (width < oldw) ? width : oldw; + int minh = (height < oldh) ? height : oldh; + int i; + + for (i = 0; i < minh; ++i) + { + struct tb_cell* csrc = oldcells + (i * oldw); + struct tb_cell* cdst = buf->cells + (i * width); + memcpy(cdst, csrc, sizeof(struct tb_cell) * minw); + } + + free(oldcells); +} + +static void cellbuf_clear(struct cellbuf* buf) +{ + int i; + int ncells = buf->width * buf->height; + + for (i = 0; i < ncells; ++i) + { + buf->cells[i].ch = ' '; + buf->cells[i].fg = foreground; + buf->cells[i].bg = background; + } +} + +static void cellbuf_free(struct cellbuf* buf) +{ + free(buf->cells); +} + +static void get_term_size(int* w, int* h) +{ + struct winsize sz; + memset(&sz, 0, sizeof(sz)); + + ioctl(out_fileno, TIOCGWINSZ, &sz); + + if (w) + { + *w = sz.ws_col; + } + + if (h) + { + *h = sz.ws_row; + } +} + +static void update_term_size(void) +{ + struct winsize sz; + memset(&sz, 0, sizeof(sz)); + + ioctl(out_fileno, TIOCGWINSZ, &sz); + + termw = sz.ws_col; + termh = sz.ws_row; +} + +static void send_attr(uint32_t fg, uint32_t bg) +{ +#define LAST_ATTR_INIT 0xFFFFFFFF + static uint32_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT; + + if (fg != lastfg || bg != lastbg) + { + memstream_puts(&write_buffer, funcs[T_SGR0]); + uint32_t fgcol; + uint32_t bgcol; + + switch (outputmode) + { + case TB_OUTPUT_TRUECOLOR: + fgcol = fg; + bgcol = bg; + break; + + case TB_OUTPUT_256: + fgcol = fg & 0xFF; + bgcol = bg & 0xFF; + break; + + case TB_OUTPUT_216: + fgcol = fg & 0xFF; + + if (fgcol > 215) + { + fgcol = 7; + } + + bgcol = bg & 0xFF; + + if (bgcol > 215) + { + bgcol = 0; + } + + fgcol += 0x10; + bgcol += 0x10; + break; + + case TB_OUTPUT_GRAYSCALE: + fgcol = fg & 0xFF; + + if (fgcol > 23) + { + fgcol = 23; + } + + bgcol = bg & 0xFF; + + if (bgcol > 23) + { + bgcol = 0; + } + + fgcol += 0xe8; + bgcol += 0xe8; + break; + + case TB_OUTPUT_NORMAL: + default: + fgcol = fg & 0x0F; + bgcol = bg & 0x0F; + } + + if (fg & TB_BOLD) + { + memstream_puts(&write_buffer, funcs[T_BOLD]); + } + + if (bg & TB_BOLD) + { + memstream_puts(&write_buffer, funcs[T_BLINK]); + } + + if (fg & TB_UNDERLINE) + { + memstream_puts(&write_buffer, funcs[T_UNDERLINE]); + } + + if ((fg & TB_REVERSE) || (bg & TB_REVERSE)) + { + memstream_puts(&write_buffer, funcs[T_REVERSE]); + } + + write_sgr(fgcol, bgcol); + + lastfg = fg; + lastbg = bg; + } +} + +static void send_char(int x, int y, uint32_t c) +{ + char buf[7]; + int bw = utf8_unicode_to_char(buf, c); + buf[bw] = '\0'; + + if (x - 1 != lastx || y != lasty) + { + write_cursor(x, y); + } + + lastx = x; + lasty = y; + + if (!c) + { + buf[0] = ' '; // replace 0 with whitespace + } + + memstream_puts(&write_buffer, buf); +} + +static void send_clear(void) +{ + send_attr(foreground, background); + memstream_puts(&write_buffer, funcs[T_CLEAR_SCREEN]); + + if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y)) + { + write_cursor(cursor_x, cursor_y); + } + + memstream_flush(&write_buffer); + + // we need to invalidate cursor position too and these two vars are + // used only for simple cursor positioning optimization, cursor + // actually may be in the correct place, but we simply discard + // optimization once and it gives us simple solution for the case when + // cursor moved + lastx = LAST_COORD_INIT; + lasty = LAST_COORD_INIT; +} + +static void sigwinch_handler(int xxx) +{ + (void) xxx; + const int zzz = 1; + write(winch_fds[1], &zzz, sizeof(int)); +} + +static void update_size(void) +{ + update_term_size(); + cellbuf_resize(&back_buffer, termw, termh); + cellbuf_resize(&front_buffer, termw, termh); + cellbuf_clear(&front_buffer); + send_clear(); +} + +static int wait_fill_event(struct tb_event* event, struct timeval* timeout) +{ +#define ENOUGH_DATA_FOR_INPUT_PARSING 128 + int result; + char buf[ENOUGH_DATA_FOR_INPUT_PARSING]; + fd_set events; + memset(event, 0, sizeof(struct tb_event)); + + // try to extract event from input buffer, return on success + event->type = TB_EVENT_KEY; + + if (extract_event(event, &inbuf, inputmode)) + { + return event->type; + } + + // it looks like input buffer is incomplete, let's try the short path + size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in); + + if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in)) + { + clearerr(in); + } + + if (r > 0) + { + if (ringbuffer_free_space(&inbuf) < r) + { + return -1; + } + + ringbuffer_push(&inbuf, buf, r); + + if (extract_event(event, &inbuf, inputmode)) + { + return event->type; + } + } + + // no stuff in FILE's internal buffer, block in select + while (1) + { + FD_ZERO(&events); + FD_SET(in_fileno, &events); + FD_SET(winch_fds[0], &events); + int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno; + result = select(maxfd + 1, &events, 0, 0, timeout); + + if (!result) + { + return 0; + } + + if (FD_ISSET(in_fileno, &events)) + { + event->type = TB_EVENT_KEY; + size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in); + + if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in)) + { + clearerr(in); + } + + if (r == 0) + { + continue; + } + + // if there is no free space in input buffer, return error + if (ringbuffer_free_space(&inbuf) < r) + { + return -1; + } + + // fill buffer + ringbuffer_push(&inbuf, buf, r); + + if (extract_event(event, &inbuf, inputmode)) + { + return event->type; + } + } + + if (FD_ISSET(winch_fds[0], &events)) + { + event->type = TB_EVENT_RESIZE; + int zzz = 0; + read(winch_fds[0], &zzz, sizeof(int)); + buffer_size_change_request = 1; + get_term_size(&event->w, &event->h); + return TB_EVENT_RESIZE; + } + } +} diff --git a/framework/3rd/termbox_next/src/termbox.h b/framework/3rd/termbox_next/src/termbox.h new file mode 100755 index 0000000..c3cbcb6 --- /dev/null +++ b/framework/3rd/termbox_next/src/termbox.h @@ -0,0 +1,307 @@ +#ifndef H_TERMBOX +#define H_TERMBOX +#include + +// shared objects +#if __GNUC__ >= 4 + #define SO_IMPORT __attribute__((visibility("default"))) +#else + #define SO_IMPORT +#endif + +// c++ +#ifdef __cplusplus +extern "C" { +#endif + +// Key constants. See also struct tb_event's key field. +// These are a safe subset of terminfo keys, which exist on all popular +// terminals. Termbox uses only them to stay truly portable. +#define TB_KEY_F1 (0xFFFF-0) +#define TB_KEY_F2 (0xFFFF-1) +#define TB_KEY_F3 (0xFFFF-2) +#define TB_KEY_F4 (0xFFFF-3) +#define TB_KEY_F5 (0xFFFF-4) +#define TB_KEY_F6 (0xFFFF-5) +#define TB_KEY_F7 (0xFFFF-6) +#define TB_KEY_F8 (0xFFFF-7) +#define TB_KEY_F9 (0xFFFF-8) +#define TB_KEY_F10 (0xFFFF-9) +#define TB_KEY_F11 (0xFFFF-10) +#define TB_KEY_F12 (0xFFFF-11) +#define TB_KEY_INSERT (0xFFFF-12) +#define TB_KEY_DELETE (0xFFFF-13) +#define TB_KEY_HOME (0xFFFF-14) +#define TB_KEY_END (0xFFFF-15) +#define TB_KEY_PGUP (0xFFFF-16) +#define TB_KEY_PGDN (0xFFFF-17) +#define TB_KEY_ARROW_UP (0xFFFF-18) +#define TB_KEY_ARROW_DOWN (0xFFFF-19) +#define TB_KEY_ARROW_LEFT (0xFFFF-20) +#define TB_KEY_ARROW_RIGHT (0xFFFF-21) +#define TB_KEY_MOUSE_LEFT (0xFFFF-22) +#define TB_KEY_MOUSE_RIGHT (0xFFFF-23) +#define TB_KEY_MOUSE_MIDDLE (0xFFFF-24) +#define TB_KEY_MOUSE_RELEASE (0xFFFF-25) +#define TB_KEY_MOUSE_WHEEL_UP (0xFFFF-26) +#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27) + +// These are all ASCII code points below SPACE character and a BACKSPACE key. +#define TB_KEY_CTRL_TILDE 0x00 +#define TB_KEY_CTRL_2 0x00 // clash with 'CTRL_TILDE' +#define TB_KEY_CTRL_A 0x01 +#define TB_KEY_CTRL_B 0x02 +#define TB_KEY_CTRL_C 0x03 +#define TB_KEY_CTRL_D 0x04 +#define TB_KEY_CTRL_E 0x05 +#define TB_KEY_CTRL_F 0x06 +#define TB_KEY_CTRL_G 0x07 +#define TB_KEY_BACKSPACE 0x08 +#define TB_KEY_CTRL_H 0x08 // clash with 'CTRL_BACKSPACE' +#define TB_KEY_TAB 0x09 +#define TB_KEY_CTRL_I 0x09 // clash with 'TAB' +#define TB_KEY_CTRL_J 0x0A +#define TB_KEY_CTRL_K 0x0B +#define TB_KEY_CTRL_L 0x0C +#define TB_KEY_ENTER 0x0D +#define TB_KEY_CTRL_M 0x0D // clash with 'ENTER' +#define TB_KEY_CTRL_N 0x0E +#define TB_KEY_CTRL_O 0x0F +#define TB_KEY_CTRL_P 0x10 +#define TB_KEY_CTRL_Q 0x11 +#define TB_KEY_CTRL_R 0x12 +#define TB_KEY_CTRL_S 0x13 +#define TB_KEY_CTRL_T 0x14 +#define TB_KEY_CTRL_U 0x15 +#define TB_KEY_CTRL_V 0x16 +#define TB_KEY_CTRL_W 0x17 +#define TB_KEY_CTRL_X 0x18 +#define TB_KEY_CTRL_Y 0x19 +#define TB_KEY_CTRL_Z 0x1A +#define TB_KEY_ESC 0x1B +#define TB_KEY_CTRL_LSQ_BRACKET 0x1B // clash with 'ESC' +#define TB_KEY_CTRL_3 0x1B // clash with 'ESC' +#define TB_KEY_CTRL_4 0x1C +#define TB_KEY_CTRL_BACKSLASH 0x1C // clash with 'CTRL_4' +#define TB_KEY_CTRL_5 0x1D +#define TB_KEY_CTRL_RSQ_BRACKET 0x1D // clash with 'CTRL_5' +#define TB_KEY_CTRL_6 0x1E +#define TB_KEY_CTRL_7 0x1F +#define TB_KEY_CTRL_SLASH 0x1F // clash with 'CTRL_7' +#define TB_KEY_CTRL_UNDERSCORE 0x1F // clash with 'CTRL_7' +#define TB_KEY_SPACE 0x20 +#define TB_KEY_BACKSPACE2 0x7F +#define TB_KEY_CTRL_8 0x7F // clash with 'BACKSPACE2' + +// These are non-existing ones. +// #define TB_KEY_CTRL_1 clash with '1' +// #define TB_KEY_CTRL_9 clash with '9' +// #define TB_KEY_CTRL_0 clash with '0' + +// Alt modifier constant, see tb_event.mod field and tb_select_input_mode function. +// Mouse-motion modifier +#define TB_MOD_ALT 0x01 +#define TB_MOD_MOTION 0x02 + +// Colors (see struct tb_cell's fg and bg fields). +#define TB_DEFAULT 0x00 +#define TB_BLACK 0x01 +#define TB_RED 0x02 +#define TB_GREEN 0x03 +#define TB_YELLOW 0x04 +#define TB_BLUE 0x05 +#define TB_MAGENTA 0x06 +#define TB_CYAN 0x07 +#define TB_WHITE 0x08 + +// Attributes, it is possible to use multiple attributes by combining them +// using bitwise OR ('|'). Although, colors cannot be combined. But you can +// combine attributes and a single color. See also struct tb_cell's fg and bg +// fields. +#define TB_BOLD 0x01000000 +#define TB_UNDERLINE 0x02000000 +#define TB_REVERSE 0x04000000 + +// A cell, single conceptual entity on the terminal screen. The terminal screen +// is basically a 2d array of cells. It has the following fields: +// - 'ch' is a unicode character +// - 'fg' foreground color and attributes +// - 'bg' background color and attributes +struct tb_cell +{ + uint32_t ch; + uint32_t fg; + uint32_t bg; +}; + +#define TB_EVENT_KEY 1 +#define TB_EVENT_RESIZE 2 +#define TB_EVENT_MOUSE 3 + +// An event, single interaction from the user. The 'mod' and 'ch' fields are +// valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type' +// is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is +// TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY +// or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only +// one of them can be non-zero at a time. +struct tb_event +{ + uint8_t type; + uint8_t mod; // modifiers to either 'key' or 'ch' below + uint16_t key; // one of the TB_KEY_* constants + uint32_t ch; // unicode character + int32_t w; + int32_t h; + int32_t x; + int32_t y; +}; + +// Error codes returned by tb_init(). All of them are self-explanatory, except +// the pipe trap error. Termbox uses unix pipes in order to deliver a message +// from a signal handler (SIGWINCH) to the main event reading loop. Honestly in +// most cases you should just check the returned code as < 0. +#define TB_EUNSUPPORTED_TERMINAL -1 +#define TB_EFAILED_TO_OPEN_TTY -2 +#define TB_EPIPE_TRAP_ERROR -3 + +// Initializes the termbox library. This function should be called before any +// other functions. Function tb_init is same as tb_init_file("/dev/tty"). After successful initialization, the library must be +// finalized using the tb_shutdown() function. +SO_IMPORT int tb_init(void); +SO_IMPORT int tb_init_file(const char* name); +SO_IMPORT void tb_shutdown(void); + +// Returns the size of the internal back buffer (which is the same as +// terminal's window size in characters). The internal buffer can be resized +// after tb_clear() or tb_present() function calls. Both dimensions have an +// unspecified negative value when called before tb_init() or after +// tb_shutdown(). +SO_IMPORT int tb_width(void); +SO_IMPORT int tb_height(void); + +// Clears the internal back buffer using TB_DEFAULT color or the +// color/attributes set by tb_set_clear_attributes() function. +SO_IMPORT void tb_clear(void); +SO_IMPORT void tb_set_clear_attributes(uint32_t fg, uint32_t bg); + +// Synchronizes the internal back buffer with the terminal. +SO_IMPORT void tb_present(void); + +#define TB_HIDE_CURSOR -1 + +// Sets the position of the cursor. Upper-left character is (0, 0). If you pass +// TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor +// is hidden by default. +SO_IMPORT void tb_set_cursor(int cx, int cy); + +// Changes cell's parameters in the internal back buffer at the specified +// position. +SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell* cell); +SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint32_t fg, + uint32_t bg); + +// Copies the buffer from 'cells' at the specified position, assuming the +// buffer is a two-dimensional array of size ('w' x 'h'), represented as a +// one-dimensional buffer containing lines of cells starting from the top. +// (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own) +SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell* cells); + +// Returns a pointer to internal cell back buffer. You can get its dimensions +// using tb_width() and tb_height() functions. The pointer stays valid as long +// as no tb_clear() and tb_present() calls are made. The buffer is +// one-dimensional buffer containing lines of cells starting from the top. +SO_IMPORT struct tb_cell* tb_cell_buffer(void); + +#define TB_INPUT_CURRENT 0 // 000 +#define TB_INPUT_ESC 1 // 001 +#define TB_INPUT_ALT 2 // 010 +#define TB_INPUT_MOUSE 4 // 100 + +// Sets the termbox input mode. Termbox has two input modes: +// 1. Esc input mode. +// When ESC sequence is in the buffer and it doesn't match any known +// ESC sequence => ESC means TB_KEY_ESC. +// 2. Alt input mode. +// When ESC sequence is in the buffer and it doesn't match any known +// sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event. +// +// You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the +// modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes +// were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some +// reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it +// will behave as if only TB_INPUT_ESC was selected. +// +// If 'mode' is TB_INPUT_CURRENT, it returns the current input mode. +// +// Default termbox input mode is TB_INPUT_ESC. +SO_IMPORT int tb_select_input_mode(int mode); + +#define TB_OUTPUT_CURRENT 0 +#define TB_OUTPUT_NORMAL 1 +#define TB_OUTPUT_256 2 +#define TB_OUTPUT_216 3 +#define TB_OUTPUT_GRAYSCALE 4 +#define TB_OUTPUT_TRUECOLOR 5 + +// Sets the termbox output mode. Termbox has three output options: +// 1. TB_OUTPUT_NORMAL => [1..8] +// This mode provides 8 different colors: +// black, red, green, yellow, blue, magenta, cyan, white +// Shortcut: TB_BLACK, TB_RED, ... +// Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE +// +// Example usage: +// tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED); +// +// 2. TB_OUTPUT_256 => [0..256] +// In this mode you can leverage the 256 terminal mode: +// 0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL +// 0x08 - 0x0f: TB_* | TB_BOLD +// 0x10 - 0xe7: 216 different colors +// 0xe8 - 0xff: 24 different shades of grey +// +// Example usage: +// tb_change_cell(x, y, '@', 184, 240); +// tb_change_cell(x, y, '@', 0xb8, 0xf0); +// +// 3. TB_OUTPUT_216 => [0..216] +// This mode supports the 3rd range of the 256 mode only. +// But you don't need to provide an offset. +// +// 4. TB_OUTPUT_GRAYSCALE => [0..23] +// This mode supports the 4th range of the 256 mode only. +// But you dont need to provide an offset. +// +// 5. TB_OUTPUT_TRUECOLOR => [0x000000..0xFFFFFF] +// This mode supports 24-bit true color. Format is 0xRRGGBB. +// +// Execute build/src/demo/output to see its impact on your terminal. +// +// If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode. +// +// Default termbox output mode is TB_OUTPUT_NORMAL. +SO_IMPORT int tb_select_output_mode(int mode); + +// Wait for an event up to 'timeout' milliseconds and fill the 'event' +// structure with it, when the event is available. Returns the type of the +// event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case +// there were no event during 'timeout' period. +SO_IMPORT int tb_peek_event(struct tb_event* event, int timeout); + +// Wait for an event forever and fill the 'event' structure with it, when the +// event is available. Returns the type of the event (one of TB_EVENT_ +// constants) or -1 if there was an error. +SO_IMPORT int tb_poll_event(struct tb_event* event); + +// Utility utf8 functions. +#define TB_EOF -1 +SO_IMPORT int utf8_char_length(char c); +SO_IMPORT int utf8_char_to_unicode(uint32_t* out, const char* c); +SO_IMPORT int utf8_unicode_to_char(char* out, uint32_t c); + +// c++ +#ifdef __cplusplus +} +#endif + +#endif diff --git a/framework/3rd/termbox_next/src/utf8.c b/framework/3rd/termbox_next/src/utf8.c new file mode 100755 index 0000000..43efd7f --- /dev/null +++ b/framework/3rd/termbox_next/src/utf8.c @@ -0,0 +1,106 @@ +#include "termbox.h" + +static const unsigned char utf8_length[256] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 +}; + +static const unsigned char utf8_mask[6] = +{ + 0x7F, + 0x1F, + 0x0F, + 0x07, + 0x03, + 0x01 +}; + +int utf8_char_length(char c) +{ + return utf8_length[(unsigned char)c]; +} + +int utf8_char_to_unicode(uint32_t* out, const char* c) +{ + if (*c == 0) + { + return TB_EOF; + } + + int i; + unsigned char len = utf8_char_length(*c); + unsigned char mask = utf8_mask[len - 1]; + uint32_t result = c[0] & mask; + + for (i = 1; i < len; ++i) + { + result <<= 6; + result |= c[i] & 0x3f; + } + + *out = result; + return (int)len; +} + +int utf8_unicode_to_char(char* out, uint32_t c) +{ + int len = 0; + int first; + int i; + + if (c < 0x80) + { + first = 0; + len = 1; + } + else if (c < 0x800) + { + first = 0xc0; + len = 2; + } + else if (c < 0x10000) + { + first = 0xe0; + len = 3; + } + else if (c < 0x200000) + { + first = 0xf0; + len = 4; + } + else if (c < 0x4000000) + { + first = 0xf8; + len = 5; + } + else + { + first = 0xfc; + len = 6; + } + + for (i = len - 1; i > 0; --i) + { + out[i] = (c & 0x3f) | 0x80; + c >>= 6; + } + + out[0] = c | first; + + return len; +} diff --git a/framework/3rd/termbox_next/tools/astylerc b/framework/3rd/termbox_next/tools/astylerc new file mode 100755 index 0000000..a296bc3 --- /dev/null +++ b/framework/3rd/termbox_next/tools/astylerc @@ -0,0 +1,27 @@ +--style=break +--indent=force-tab=4 +--indent-classes +--indent-switches +--indent-namespaces +--indent-after-parens +--indent-continuation=1 +--indent-preproc-block +--indent-preproc-define +--indent-preproc-cond +--indent-col1-comments +--min-conditional-indent=0 +--max-continuation-indent=40 +--break-blocks +--pad-oper +--pad-comma +--pad-header +--unpad-paren +--align-pointer=type +--align-reference=type +--break-one-line-headers +--add-braces +--attach-return-type +--attach-return-type-decl +--remove-comment-prefix +--max-code-length=80 +--mode=c diff --git a/framework/3rd/termbox_next/tools/collect_terminfo.py b/framework/3rd/termbox_next/tools/collect_terminfo.py new file mode 100755 index 0000000..596c3c4 --- /dev/null +++ b/framework/3rd/termbox_next/tools/collect_terminfo.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python + +import sys, os, subprocess + +def escaped(s): + return s.replace("\033", "\\033") + +def tput(term, name): + try: + return subprocess.check_output(['tput', '-T%s' % term, name]).decode() + except subprocess.CalledProcessError as e: + return e.output.decode() + + +def w(s): + if s == None: + return + sys.stdout.write(s) + +terminals = { + 'xterm' : 'xterm', + 'rxvt-256color' : 'rxvt_256color', + 'rxvt-unicode' : 'rxvt_unicode', + 'linux' : 'linux', + 'Eterm' : 'eterm', + 'screen' : 'screen' +} + +keys = [ + "F1", "kf1", + "F2", "kf2", + "F3", "kf3", + "F4", "kf4", + "F5", "kf5", + "F6", "kf6", + "F7", "kf7", + "F8", "kf8", + "F9", "kf9", + "F10", "kf10", + "F11", "kf11", + "F12", "kf12", + "INSERT", "kich1", + "DELETE", "kdch1", + "HOME", "khome", + "END", "kend", + "PGUP", "kpp", + "PGDN", "knp", + "KEY_UP", "kcuu1", + "KEY_DOWN", "kcud1", + "KEY_LEFT", "kcub1", + "KEY_RIGHT", "kcuf1" +] + +funcs = [ + "T_ENTER_CA", "smcup", + "T_EXIT_CA", "rmcup", + "T_SHOW_CURSOR", "cnorm", + "T_HIDE_CURSOR", "civis", + "T_CLEAR_SCREEN", "clear", + "T_SGR0", "sgr0", + "T_UNDERLINE", "smul", + "T_BOLD", "bold", + "T_BLINK", "blink", + "T_REVERSE", "rev", + "T_ENTER_KEYPAD", "smkx", + "T_EXIT_KEYPAD", "rmkx" +] + +def iter_pairs(iterable): + iterable = iter(iterable) + while True: + yield (next(iterable), next(iterable)) + +def do_term(term, nick): + w("// %s\n" % term) + w("static const char *%s_keys[] = {\n\t" % nick) + for k, v in iter_pairs(keys): + w('"') + w(escaped(tput(term, v))) + w('",') + w(" 0\n};\n") + w("static const char *%s_funcs[] = {\n\t" % nick) + for k,v in iter_pairs(funcs): + w('"') + if v == "sgr": + w("\\033[3%d;4%dm") + elif v == "cup": + w("\\033[%d;%dH") + else: + w(escaped(tput(term, v))) + w('", ') + w("\n};\n\n") + +def do_terms(d): + w("static struct term {\n") + w("\tconst char *name;\n") + w("\tconst char **keys;\n") + w("\tconst char **funcs;\n") + w("} terms[] = {\n") + for k, v in d.items(): + w('\t{"%s", %s_keys, %s_funcs},\n' % (k, v, v)) + w("\t{0, 0, 0},\n") + w("};\n") + +for k,v in terminals.items(): + do_term(k, v) + +do_terms(terminals) diff --git a/framework/lualib-src/Makefile b/framework/lualib-src/Makefile index 4227a2f..7a53071 100644 --- a/framework/lualib-src/Makefile +++ b/framework/lualib-src/Makefile @@ -20,6 +20,8 @@ SNAPSHOT_SO = $(LUA_CLIB_PATH)/snapshot.so SHIFTTIMER_SO = $(LUA_CLIB_PATH)/shiftimer.so CLUA_SO = $(LUA_CLIB_PATH)/clua.so AOI_SO = $(LUA_CLIB_PATH)/aoi.so +ECS_SO = $(LUA_CLIB_PATH)/ecs.so + ##################################################### all: $(LFS_SO) \ $(CJSON_SO) \ @@ -28,6 +30,7 @@ all: $(LFS_SO) \ $(SNAPSHOT_SO) \ $(SHIFTTIMER_SO) \ $(AOI_SO) \ + $(ECS_SO) \ $(CLUA_SO) ##################################################### @@ -55,6 +58,9 @@ $(AOI_SO): $(CLUA_SO): cd lua-clua && $(MAKE) PLAT=$(PLAT) +$(ECS_SO): + cd lua-ecs && $(MAKE) PLAT=$(PLAT) + ##################################################### cleanall: diff --git a/framework/lualib-src/lua-ecs/Makefile b/framework/lualib-src/lua-ecs/Makefile new file mode 100755 index 0000000..9a4e8f0 --- /dev/null +++ b/framework/lualib-src/lua-ecs/Makefile @@ -0,0 +1,31 @@ +SKYNET_ROOT ?= ../../skynet +include $(SKYNET_ROOT)/platform.mk + +PLAT ?= none + +TARGET = ../../luaclib/ecs.so + +ifeq ($(PLAT), macosx) + CFLAGS = -g -O2 -dynamiclib -Wl,-undefined,dynamic_lookup +else +ifeq ($(PLAT), linux) + CFLAGS = -g -O2 -shared -fPIC +endif +endif + +LUA_LIB ?= $(SKYNET_ROOT)/3rd/lua/ +LUA_INC ?= $(SKYNET_ROOT)/3rd/lua/ +SKYNET_SRC ?= $(SKYNET_ROOT)/skynet-src + +SRC = . + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(foreach dir, $(SRC), $(wildcard $(dir)/*.c)) + $(CC) $(CFLAGS) $(SHARED) -I$(LUA_INC) -I$(SKYNET_SRC) $^ -o $@ + +clean: + rm -f *.o $(TARGET) + diff --git a/framework/lualib-src/lua-ecs/luaecs.c b/framework/lualib-src/lua-ecs/luaecs.c new file mode 100755 index 0000000..90c76c9 --- /dev/null +++ b/framework/lualib-src/lua-ecs/luaecs.c @@ -0,0 +1,910 @@ +#define LUA_LIB + +#include +#include + +#include +#include +#include + +#include "luaecs.h" + +#define MAX_COMPONENT 256 +#define ENTITY_REMOVED 0 +#define DEFAULT_SIZE 128 +#define DUMMY_PTR (void *)(uintptr_t)(~0) + +struct component_pool +{ + int cap; + int n; + int count; + int stride; + int last_lookup; + unsigned int *id; + void *buffer; +}; + +struct entity_world +{ + unsigned int max_id; + unsigned int wrap_begin; + int wrap; + struct component_pool c[MAX_COMPONENT]; +}; + +static void +init_component_pool(struct entity_world *w, int index, int stride, int opt_size) +{ + struct component_pool *c = &w->c[index]; + c->cap = opt_size; + c->n = 0; + c->count = 0; + c->stride = stride; + c->id = NULL; + if (stride > 0) + { + c->buffer = NULL; + } + else + { + c->buffer = DUMMY_PTR; + } +} + +static void +entity_new_type(lua_State *L, struct entity_world *w, int cid, int stride, int opt_size) +{ + if (opt_size <= 0) + { + opt_size = DEFAULT_SIZE; + } + if (cid < 0 || cid >= MAX_COMPONENT || w->c[cid].cap != 0) + { + luaL_error(L, "Can't new type %d", cid); + } + init_component_pool(w, cid, stride, opt_size); +} + +static inline struct entity_world * +getW(lua_State *L) +{ + return (struct entity_world *)luaL_checkudata(L, 1, "ENTITY_WORLD"); +} + +static int +lnew_type(lua_State *L) +{ + struct entity_world *w = getW(L); + int cid = luaL_checkinteger(L, 2); + int stride = luaL_checkinteger(L, 3); + int size = luaL_optinteger(L, 4, 0); + entity_new_type(L, w, cid, stride, size); + return 0; +} + +static int +lcount_memory(lua_State *L) +{ + struct entity_world *w = getW(L); + size_t sz = sizeof(*w); + int i; + size_t msz = sz; + for (i = 0; i < MAX_COMPONENT; i++) + { + struct component_pool *c = &w->c[i]; + if (c->id) + { + sz += c->cap * sizeof(unsigned int); + } + if (c->buffer) + { + sz += c->cap * c->stride; + } + + msz += c->n * (sizeof(unsigned int) + c->stride); + } + lua_pushinteger(L, sz); + lua_pushinteger(L, msz); + return 2; +} + +static void +shrink_component_pool(lua_State *L, struct component_pool *c, int id) +{ + if (c->id == NULL) + return; + if (c->n == 0) + { + c->id = NULL; + if (c->stride > 0) + c->buffer = NULL; + lua_pushnil(L); + lua_setiuservalue(L, 1, id * 2 + 1); + lua_pushnil(L); + lua_setiuservalue(L, 1, id * 2 + 2); + } + if (c->n < c->cap) + { + c->cap = c->n; + c->id = (unsigned int *)lua_newuserdatauv(L, c->n * sizeof(unsigned int), 0); + lua_setiuservalue(L, 1, id * 2 + 1); + if (c->stride > 0) + { + c->buffer = lua_newuserdatauv(L, c->n * c->stride, 0); + lua_setiuservalue(L, 1, id * 2 + 2); + } + } +} + +static int +lcollect_memory(lua_State *L) +{ + struct entity_world *w = getW(L); + int i; + for (i = 0; i < MAX_COMPONENT; i++) + { + shrink_component_pool(L, &w->c[i], i); + } + return 0; +} + +static void +add_component_(lua_State *L, struct entity_world *w, int cid, unsigned int eid, const void *buffer) +{ + struct component_pool *pool = &w->c[cid]; + int cap = pool->cap; + int index = pool->n; + int stride = pool->stride; + if (pool->n == 0) + { + if (pool->id == NULL) + { + pool->id = (unsigned int *)lua_newuserdatauv(L, cap * sizeof(unsigned int), 0); + lua_setiuservalue(L, 1, cid * 2 + 1); + } + if (pool->buffer == NULL) + { + pool->buffer = lua_newuserdatauv(L, cap * stride, 0); + lua_setiuservalue(L, 1, cid * 2 + 2); + } + } + else + { + if (pool->n >= pool->cap) + { + // expand pool + int newcap = cap * 3 / 2; + unsigned int *newid = (unsigned int *)lua_newuserdatauv(L, newcap * sizeof(unsigned int), 0); + lua_setiuservalue(L, 1, cid * 2 + 1); + memcpy(newid, pool->id, cap * sizeof(unsigned int)); + pool->id = newid; + if (stride > 0) + { + void *newbuffer = lua_newuserdatauv(L, newcap * stride, 0); + lua_setiuservalue(L, 1, cid * 2 + 2); + memcpy(newbuffer, pool->buffer, cap * stride); + pool->buffer = newbuffer; + } + pool->cap = newcap; + } + } + ++pool->n; + pool->id[index] = eid; + memcpy((char *)pool->buffer + index * stride, buffer, stride); +} + +static inline int +check_cid(lua_State *L, struct entity_world *w, int index) +{ + int cid = luaL_checkinteger(L, index); + struct component_pool *c = &w->c[cid]; + if (cid < 0 || cid >= MAX_COMPONENT || c->cap == 0) + { + luaL_error(L, "Invalid type %d", cid); + } + return cid; +} + +static int +ladd_component(lua_State *L) +{ + struct entity_world *w = getW(L); + unsigned int eid = luaL_checkinteger(L, 2); + int cid = check_cid(L, w, 3); + size_t sz; + const char *buffer = lua_tolstring(L, 4, &sz); + int stride = w->c[cid].stride; + if ((buffer == NULL && stride > 0) || (sz != stride)) + { + return luaL_error(L, "Invalid data (size=%d/%d) for type %d", (int)sz, stride, cid); + } + add_component_(L, w, cid, eid, buffer); + return 0; +} + +static int +lnew_entity(lua_State *L) +{ + struct entity_world *w = getW(L); + unsigned int eid = ++w->max_id; + if (eid == 0) + { + assert(w->wrap == 0); + w->wrap = 1; + } + lua_pushinteger(L, eid); + return 1; +} + +static void +remove_entity_(struct entity_world *w, int cid, int index, void *L) +{ + struct component_pool *c = &w->c[cid]; + if (index < 0 || index >= c->count) + luaL_error((lua_State *)L, "Invalid index %d/%d", index, c->count); + unsigned int eid = c->id[index]; + add_component_((lua_State *)L, w, ENTITY_REMOVED, eid, NULL); +} + +static int +lremove_entity(lua_State *L) +{ + struct entity_world *w = getW(L); + int cid = check_cid(L, w, 2); + int index = luaL_checkinteger(L, 3); + remove_entity_(w, cid, index, (void *)L); + return 0; +} + +static int +id_comp(const void *a, const void *b) +{ + const unsigned int ai = *(const unsigned int *)a; + const unsigned int bi = *(const unsigned int *)b; + + if (ai < bi) + return -1; + else if (ai > bi) + return 1; + else + return 0; +} + +static int +lsort_removed(lua_State *L) +{ + struct entity_world *w = getW(L); + struct component_pool *pool = &w->c[ENTITY_REMOVED]; + if (pool->n == 0) + return 0; + qsort(pool->id, pool->n, sizeof(unsigned int), id_comp); + pool->count = pool->n; + lua_pushinteger(L, pool->n); + return 1; +} + +static int +binary_search(unsigned int *a, int from, int to, unsigned int v) +{ + while (from < to) + { + int mid = (from + to) / 2; + int aa = a[mid]; + if (aa == v) + return mid; + else if (aa < v) + { + from = mid + 1; + } + else + { + to = mid; + } + } + return -1; +} + +#define GUESS_RANGE 64 + +static inline int +lookup_component(struct component_pool *pool, unsigned int eid, int guess_index) +{ + int n = pool->count; + if (n == 0) + return -1; + if (guess_index + GUESS_RANGE >= n) + return binary_search(pool->id, 0, pool->count, eid); + unsigned int *a = pool->id; + int higher = a[guess_index + GUESS_RANGE]; + if (eid > higher) + { + return binary_search(a, guess_index + GUESS_RANGE + 1, pool->count, eid); + } + int lower = a[guess_index]; + if (eid < lower) + { + return binary_search(a, 0, guess_index, eid); + } + return binary_search(a, guess_index, guess_index + GUESS_RANGE, eid); +} + +struct rearrange_context +{ + struct entity_world *w; + unsigned int ptr[MAX_COMPONENT - 1]; +}; + +static int +find_min(struct rearrange_context *ctx) +{ + unsigned int m = ~0; + int i; + int r = -1; + struct entity_world *w = ctx->w; + for (i = 1; i < MAX_COMPONENT; i++) + { + int index = ctx->ptr[i - 1]; + if (index < w->c[i].count) + { + if (w->c[i].id[index] <= m) + { + m = w->c[i].id[index]; + r = i; + } + } + } + return r; +} + +static void +rearrange(struct entity_world *w) +{ + struct rearrange_context ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.w = w; + int cid; + unsigned int new_id = 1; + unsigned int last_id = 0; + while ((cid = find_min(&ctx)) >= 0) + { + int index = ctx.ptr[cid - 1]; + unsigned int current_id = w->c[cid].id[index]; + // printf("arrange %d -> %d\n", new_id, w->c[cid].id[index]); + w->c[cid].id[index] = new_id; + if (current_id != last_id) + { + ++new_id; + last_id = current_id; + } + ++ctx.ptr[cid - 1]; + } + int i, j; + for (i = 1; i < MAX_COMPONENT; i++) + { + struct component_pool *pool = &w->c[i]; + for (j = pool->count; j < pool->n; j++) + { + // printf("arrange new %d -> %d\n", pool->id[j], new_id + pool->id[j] - w->wrap_begin -1); + pool->id[j] = new_id + pool->id[j] - w->wrap_begin - 1; + } + } + w->max_id = new_id + w->max_id - w->wrap_begin - 1; +} + +static inline void +move_item(struct component_pool *pool, int from, int to) +{ + if (from != to) + { + pool->id[to] = pool->id[from]; + memcpy((char *)pool->buffer + to * pool->stride, (char *)pool->buffer + from * pool->stride, pool->stride); + } +} + +static void +remove_all(struct component_pool *pool, struct component_pool *removed) +{ + int index = 0; + int i; + unsigned int *id = removed->id; + unsigned int last_id = 0; + int count = 0; + for (i = 0; i < removed->n; i++) + { + if (id[i] != last_id) + { + int r = lookup_component(pool, id[i], index); + if (r >= 0) + { + index = r; + assert(pool->id[r] == id[i]); + pool->id[r] = 0; + ++count; + } + } + } + if (count > 0) + { + index = 0; + for (i = 0; i < pool->n; i++) + { + if (pool->id[i] != 0) + { + move_item(pool, i, index); + ++index; + } + } + pool->n -= count; + pool->count -= count; + } +} + +static int +lupdate(lua_State *L) +{ + struct entity_world *w = getW(L); + struct component_pool *removed = &w->c[ENTITY_REMOVED]; + int i; + if (removed->n > 0) + { + // mark removed + assert(ENTITY_REMOVED == 0); + for (i = 1; i < MAX_COMPONENT; i++) + { + struct component_pool *pool = &w->c[i]; + if (pool->n > 0) + remove_all(pool, removed); + } + removed->n = 0; + removed->count = 0; + } + + if (w->wrap) + { + rearrange(w); + w->wrap = 0; + } + w->wrap_begin = w->max_id; + // add componets + for (i = 1; i < MAX_COMPONENT; i++) + { + struct component_pool *c = &w->c[i]; + c->count = c->n; + } + return 0; +} + +static void * +entity_iter_(struct entity_world *w, int cid, int index) +{ + struct component_pool *c = &w->c[cid]; + assert(index >= 0); + if (index >= c->count) + return NULL; + return (char *)c->buffer + c->stride * index; +} + +static void +entity_clear_type_(struct entity_world *w, int cid) +{ + struct component_pool *c = &w->c[cid]; + c->n = 0; + c->count = 0; +} + +static int +lclear_type(lua_State *L) +{ + struct entity_world *w = getW(L); + int cid = check_cid(L, w, 2); + entity_clear_type_(w, cid); + return 0; +} + +static void * +entity_sibling_(struct entity_world *w, int cid, int index, int slibling_id) +{ + struct component_pool *c = &w->c[cid]; + if (index < 0 || index >= c->count) + return NULL; + unsigned int eid = c->id[index]; + c = &w->c[slibling_id]; + int result_index = lookup_component(c, eid, c->last_lookup); + if (result_index >= 0) + { + c->last_lookup = result_index; + return (char *)c->buffer + c->stride * result_index; + } + return NULL; +} + +static void +entity_add_sibling_(struct entity_world *w, int cid, int index, int slibling_id, const void *buffer, void *L) +{ + struct component_pool *c = &w->c[cid]; + assert(index >= 0 && index < c->count); + unsigned int eid = c->id[index]; + // todo: pcall add_component_ + add_component_((lua_State *)L, w, slibling_id, eid, buffer); + c->count = c->n; +} + +static int +lcontext(lua_State *L) +{ + struct entity_world *w = getW(L); + luaL_checktype(L, 2, LUA_TTABLE); + lua_len(L, 2); + int n = lua_tointeger(L, -1); + lua_pop(L, 1); + if (n <= 0) + { + return luaL_error(L, "Invalid length %d of table", n); + } + size_t sz = sizeof(struct ecs_context) + sizeof(int) * n; + struct ecs_context *ctx = (struct ecs_context *)lua_newuserdatauv(L, sz, 1); + ctx->L = (void *)lua_newthread(L); + lua_setiuservalue(L, -2, 1); + ctx->max_id = n; + ctx->world = w; + static struct ecs_capi c_api = { + entity_iter_, + entity_clear_type_, + entity_sibling_, + entity_add_sibling_, + }; + ctx->api = &c_api; + ctx->cid[0] = ENTITY_REMOVED; + int i; + for (i = 1; i <= n; i++) + { + if (lua_geti(L, 2, i) != LUA_TNUMBER) + { + return luaL_error(L, "Invalid id at index %d", i); + } + ctx->cid[i] = lua_tointeger(L, -1); + lua_pop(L, 1); + int cid = ctx->cid[i]; + if (cid == ENTITY_REMOVED || cid < 0 || cid >= MAX_COMPONENT) + return luaL_error(L, "Invalid id (%d) at index %d", cid, i); + } + return 1; +} + +static int +lnew_world(lua_State *L) +{ + size_t sz = sizeof(struct entity_world); + struct entity_world *w = (struct entity_world *)lua_newuserdatauv(L, sz, MAX_COMPONENT * 2); + memset(w, 0, sz); + // removed set + entity_new_type(L, w, ENTITY_REMOVED, 0, 0); + luaL_getmetatable(L, "ENTITY_WORLD"); + lua_setmetatable(L, -2); + return 1; +} + +#define TYPE_INT 0 +#define TYPE_FLOAT 1 +#define TYPE_BOOL 2 + +struct field +{ + const char *key; + int offset; + int type; +}; + +struct simple_iter +{ + int id; + int field_n; + struct entity_world *world; + struct field f[1]; +}; + +static void +get_field(lua_State *L, int index, int i, struct field *f) +{ + if (lua_geti(L, index, i) != LUA_TTABLE) + { + luaL_error(L, "Invalid field %d", i); + } + + if (lua_geti(L, -1, 1) != LUA_TNUMBER) + { + luaL_error(L, "Invalid field %d [1] type", i); + } + f->type = lua_tointeger(L, -1); + if (f->type != TYPE_INT && + f->type != TYPE_FLOAT && + f->type != TYPE_BOOL) + { + luaL_error(L, "Invalid field %d [1] type(%d)", i, f->type); + } + lua_pop(L, 1); + + if (lua_geti(L, -1, 2) != LUA_TSTRING) + { + luaL_error(L, "Invalid field %d [2] key", i); + } + f->key = lua_tostring(L, -1); + lua_pop(L, 1); + + if (lua_geti(L, -1, 3) != LUA_TNUMBER) + { + luaL_error(L, "Invalid field %d [3] offset", i); + } + f->offset = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_pop(L, 1); +} + +static void +write_component(lua_State *L, struct simple_iter *iter, int index, char *buffer) +{ + int i; + for (i = 0; i < iter->field_n; i++) + { + int luat = lua_getfield(L, index, iter->f[i].key); + char *ptr = buffer + iter->f[i].offset; + switch (iter->f[i].type) + { + case TYPE_INT: + if (!lua_isinteger(L, -1)) + luaL_error(L, "Invalid .%s type %s (int)", iter->f[i].key, lua_typename(L, luat)); + *(int *)ptr = lua_tointeger(L, -1); + break; + case TYPE_FLOAT: + if (luat != LUA_TNUMBER) + luaL_error(L, "Invalid .%s type %s (float)", iter->f[i].key, lua_typename(L, luat)); + *(float *)ptr = lua_tonumber(L, -1); + break; + case TYPE_BOOL: + if (luat != LUA_TBOOLEAN) + luaL_error(L, "Invalid .%s type %s (bool)", iter->f[i].key, lua_typename(L, luat)); + *(unsigned char *)ptr = lua_toboolean(L, -1); + } + lua_pop(L, 1); + } +} + +static void +read_component(lua_State *L, struct simple_iter *iter, int index, const char *buffer) +{ + int i; + for (i = 0; i < iter->field_n; i++) + { + const char *ptr = buffer + iter->f[i].offset; + switch (iter->f[i].type) + { + case TYPE_INT: + lua_pushinteger(L, *(const int *)ptr); + break; + case TYPE_FLOAT: + lua_pushnumber(L, *(const float *)ptr); + break; + case TYPE_BOOL: + lua_pushboolean(L, *ptr); + break; + default: + // never here + lua_pushnil(L); + break; + } + lua_setfield(L, index, iter->f[i].key); + } +} + +static int +leach_simple(lua_State *L) +{ + struct simple_iter *iter = lua_touserdata(L, 1); + if (lua_rawgeti(L, 2, 1) != LUA_TNUMBER) + { + return luaL_error(L, "Invalid simple iterator"); + } + int i = lua_tointeger(L, -1); + lua_pop(L, 1); + if (i > 0) + { + void *write_buffer = entity_iter_(iter->world, iter->id, i - 1); + if (write_buffer == NULL) + return luaL_error(L, "Can't write to index %d", i); + write_component(L, iter, 2, (char *)write_buffer); + } + void *read_buffer = entity_iter_(iter->world, iter->id, i++); + if (read_buffer == NULL) + { + return 0; + } + lua_pushinteger(L, i); + lua_rawseti(L, 2, 1); + read_component(L, iter, 2, (const char *)read_buffer); + lua_settop(L, 2); + return 1; +} + +static int +lpairs_simple(lua_State *L) +{ + struct simple_iter *iter = lua_touserdata(L, 1); + lua_pushcfunction(L, leach_simple); + lua_pushvalue(L, 1); + lua_createtable(L, 1, iter->field_n); + lua_pushinteger(L, 0); + lua_rawseti(L, -2, 1); + return 3; +} + +static int +lsimpleiter(lua_State *L) +{ + struct entity_world *w = getW(L); + luaL_checktype(L, 2, LUA_TTABLE); + lua_len(L, 2); + if (lua_type(L, -1) != LUA_TNUMBER) + { + return luaL_error(L, "Invalid fields"); + } + int n = lua_tointeger(L, -1); + if (n <= 0) + { + return luaL_error(L, "Invalid fields number %d", n); + } + lua_pop(L, 1); + size_t sz = sizeof(struct simple_iter) + (n - 1) * sizeof(struct field); + struct simple_iter *iter = (struct simple_iter *)lua_newuserdatauv(L, sz, 1); + lua_pushvalue(L, 1); + lua_setiuservalue(L, -2, 1); + iter->world = w; + iter->field_n = n; + if (lua_getfield(L, 2, "id") != LUA_TNUMBER) + { + return luaL_error(L, "Invalid id"); + } + iter->id = lua_tointeger(L, -1); + lua_pop(L, 1); + if (iter->id < 0 || iter->id >= MAX_COMPONENT || iter->id == ENTITY_REMOVED || w->c[iter->id].cap == 0) + { + return luaL_error(L, "Invalid id %d", iter->id); + } + int i; + for (i = 0; i < n; i++) + { + get_field(L, 2, i + 1, &iter->f[i]); + } + if (luaL_newmetatable(L, "ENTITY_SIMPLEITER")) + { + lua_pushcfunction(L, lpairs_simple); + lua_setfield(L, -2, "__call"); + } + lua_setmetatable(L, -2); + return 1; +} + +LUAMOD_API int +luaopen_ecs_core(lua_State *L) +{ + luaL_checkversion(L); + luaL_Reg l[] = { + {"world", lnew_world}, + {"_MAXTYPE", NULL}, + {"_METHODS", NULL}, + {"_TYPEINT", NULL}, + {"_TYPEFLOAT", NULL}, + {"_TYPEBOOL", NULL}, + {NULL, NULL}, + }; + luaL_newlib(L, l); + lua_pushinteger(L, MAX_COMPONENT - 1); + lua_setfield(L, -2, "_MAXTYPE"); + if (luaL_newmetatable(L, "ENTITY_WORLD")) + { + luaL_Reg l[] = { + {"__index", NULL}, + {"memory", lcount_memory}, + {"collect", lcollect_memory}, + {"_newtype", lnew_type}, + {"_newentity", lnew_entity}, + {"_addcomponent", ladd_component}, + {"remove", lremove_entity}, + {"sort_removed", lsort_removed}, + {"update", lupdate}, + {"clear", lclear_type}, + {"_context", lcontext}, + {"_simpleiter", lsimpleiter}, + {NULL, NULL}, + }; + luaL_setfuncs(L, l, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + } + else + { + return luaL_error(L, "ENTITY_WORLD exist"); + } + lua_setfield(L, -2, "_METHODS"); + lua_pushinteger(L, TYPE_INT); + lua_setfield(L, -2, "_TYPEINT"); + lua_pushinteger(L, TYPE_FLOAT); + lua_setfield(L, -2, "_TYPEFLOAT"); + lua_pushinteger(L, TYPE_BOOL); + lua_setfield(L, -2, "_TYPEBOOL"); + return 1; +} + +#ifdef TEST_LUAECS + +#include + +#define COMPONENT_VECTOR2 1 +#define TAG_MARK 2 +#define COMPONENT_ID 3 + +struct vector2 +{ + float x; + float y; +}; + +struct id +{ + int v; +}; + +static int +ltest(lua_State *L) +{ + struct ecs_context *ctx = lua_touserdata(L, 1); + struct vector2 *v; + int i; + for (i = 0; (v = (struct vector2 *)entity_iter(ctx, COMPONENT_VECTOR2, i)); i++) + { + printf("vector2 %d: x=%f y=%f\n", i, v->x, v->y); + struct id *id = (struct id *)entity_sibling(ctx, COMPONENT_VECTOR2, i, COMPONENT_ID); + if (id) + { + printf("\tid = %d\n", id->v); + } + void *mark = entity_sibling(ctx, COMPONENT_VECTOR2, i, TAG_MARK); + if (mark) + { + printf("\tMARK\n"); + } + } + + return 0; +} + +static int +lsum(lua_State *L) +{ + struct ecs_context *ctx = lua_touserdata(L, 1); + struct vector2 *v; + int i; + float s = 0; + for (i = 0; (v = (struct vector2 *)entity_iter(ctx, COMPONENT_VECTOR2, i)); i++) + { + s += v->x + v->y; + } + lua_pushnumber(L, s); + return 1; +} + +LUAMOD_API int +luaopen_ecs_ctest(lua_State *L) +{ + luaL_checkversion(L); + luaL_Reg l[] = { + {"test", ltest}, + {"sum", lsum}, + {NULL, NULL}, + }; + luaL_newlib(L, l); + return 1; +} + +#endif diff --git a/framework/lualib-src/lua-ecs/luaecs.h b/framework/lualib-src/lua-ecs/luaecs.h new file mode 100755 index 0000000..99bc5bf --- /dev/null +++ b/framework/lualib-src/lua-ecs/luaecs.h @@ -0,0 +1,69 @@ +#ifndef lua_ecs_cdata_h +#define lua_ecs_cdata_h + +#include + +struct entity_world; + +struct ecs_capi +{ + void *(*iter)(struct entity_world *w, int cid, int index); + void (*clear_type)(struct entity_world *w, int cid); + void *(*sibling)(struct entity_world *w, int cid, int index, int slibling_id); + void (*add_sibling)(struct entity_world *w, int cid, int index, int slibling_id, const void *buffer, void *L); + void (*remove)(struct entity_world *w, int cid, int index, void *L); +}; + +struct ecs_context +{ + struct ecs_capi *api; + struct entity_world *world; + void *L; // for memory allocator + int max_id; + int cid[1]; +}; + +static inline void +check_id_(struct ecs_context *ctx, int cid) +{ + assert(cid >= 0 && cid <= ctx->max_id); +} + +static inline void * +entity_iter(struct ecs_context *ctx, int cid, int index) +{ + check_id_(ctx, cid); + return ctx->api->iter(ctx->world, ctx->cid[cid], index); +} + +static inline void +entity_clear_type(struct ecs_context *ctx, int cid) +{ + check_id_(ctx, cid); + ctx->api->clear_type(ctx->world, ctx->cid[cid]); +} + +static inline void * +entity_sibling(struct ecs_context *ctx, int cid, int index, int slibling_id) +{ + check_id_(ctx, cid); + check_id_(ctx, slibling_id); + return ctx->api->sibling(ctx->world, ctx->cid[cid], index, ctx->cid[slibling_id]); +} + +static inline void +entity_add_sibling(struct ecs_context *ctx, int cid, int index, int slibling_id, const void *buffer) +{ + check_id_(ctx, cid); + check_id_(ctx, slibling_id); + ctx->api->add_sibling(ctx->world, ctx->cid[cid], index, ctx->cid[slibling_id], buffer, ctx->L); +} + +static inline void +entity_remove(struct ecs_context *ctx, int cid, int index) +{ + check_id_(ctx, cid); + ctx->api->remove(ctx->world, ctx->cid[cid], index, ctx->L); +} + +#endif diff --git a/framework/lualib-src/lua-rc4/.gitignore b/framework/lualib-src/lua-rc4/.gitignore new file mode 100644 index 0000000..9d22eb4 --- /dev/null +++ b/framework/lualib-src/lua-rc4/.gitignore @@ -0,0 +1,2 @@ +*.o +*.so diff --git a/framework/lualib-src/lua-rc4/Makefile b/framework/lualib-src/lua-rc4/Makefile new file mode 100755 index 0000000..f670b43 --- /dev/null +++ b/framework/lualib-src/lua-rc4/Makefile @@ -0,0 +1,26 @@ +PROJECT = rc4 +SRC = . +INC = -I. -I/usr/local/include + +CC = gcc +CC_FLAGS = -O2 -fPIC $(INC) -Wall -Wextra -c + +SRC_C = $(foreach dir, $(SRC), $(wildcard $(dir)/*.c)) +OBJ_C = $(patsubst %.c, %.o, $(SRC_C)) +OBJ = $(OBJ_C) + +.PHONY : all +all: $(PROJECT).so + +$(PROJECT).so: $(OBJ) + ld -shared $(OBJ) -o $(PROJECT).so +ifdef OUTPUT + cp -f $(PROJECT).so $(OUTPUT) +endif + +$(OBJ_C) : %.o : %.c + $(CC) $(CC_FLAGS) -o $@ $< + +.PHONY : clean +clean: + rm -f $(PROJECT).so $(OBJ) diff --git a/framework/lualib-src/lua-rc4/luabinding.c b/framework/lualib-src/lua-rc4/luabinding.c new file mode 100755 index 0000000..9552c15 --- /dev/null +++ b/framework/lualib-src/lua-rc4/luabinding.c @@ -0,0 +1,89 @@ +#include +#include + +#include "lua.h" +#include "lauxlib.h" + +#include "rc4.h" + +#include + +#define RC4_METATABLE "rc4_metatable" +#define RC4_BUFSIZE (4096) /* after wrap in lua string, is 4096 */ + +static int +lrc4(lua_State * L) { + size_t len; + const char * key = luaL_checklstring(L, 1, &len); + +#if LUA_VERSION_NUM == 504 + struct rc4_state * rc4 = (struct rc4_state *)lua_newuserdatauv(L, sizeof(*rc4), 0); +#else + struct rc4_state * rc4 = (struct rc4_state *)lua_newuserdata(L, sizeof(*rc4)); +#endif + + lua_pushvalue(L, 1); + lua_setuservalue(L, -2); + + luaL_getmetatable(L, RC4_METATABLE); + lua_setmetatable(L, -2); + + librc4_init(rc4, (uint8_t*)key, (int)len); + + return 1; +} + +static int +lreset(lua_State* L) { + size_t len; + struct rc4_state * rc4 = (struct rc4_state *)luaL_checkudata(L, 1, RC4_METATABLE); + lua_getuservalue(L, 1); + const char* key = luaL_checklstring(L, -1, &len); + librc4_init(rc4, (uint8_t*)key, (int)len); + return 0; +} + +static int +lcrypt(lua_State * L) { + struct rc4_state * rc4 = (struct rc4_state *)luaL_checkudata(L, 1, RC4_METATABLE); + + size_t len; + const char * data = luaL_checklstring(L, 2, &len); + + uint8_t *buffer = (uint8_t *)malloc(len); + if(buffer) { + librc4_crypt(rc4, (const uint8_t*)data, buffer, (int)len); + lua_pushlstring(L, (const char*)buffer, len); + free(buffer); + return 1; + } + + return 0; +} + +int +luaopen_rc4_c(lua_State *L) { + luaL_checkversion(L); + + if (luaL_newmetatable(L, RC4_METATABLE)) { + luaL_Reg rc4_mt[] = { + { "crypt", lcrypt }, + { "reset", lreset }, + { NULL, NULL }, + }; + luaL_newlib(L, rc4_mt); + lua_setfield(L, -2, "__index"); + } + lua_pop(L, 1); + + luaL_Reg l[] = { + { "rc4", lrc4 }, + { NULL, NULL }, + }; + luaL_newlib(L, l); + + lua_pushinteger(L, 2); + lua_setfield(L, -2, "VERSION"); + + return 1; +} diff --git a/framework/lualib-src/lua-rc4/rc4.c b/framework/lualib-src/lua-rc4/rc4.c new file mode 100755 index 0000000..cd6f48d --- /dev/null +++ b/framework/lualib-src/lua-rc4/rc4.c @@ -0,0 +1,99 @@ +/* + * rc4.c + * + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/crypto/rc4/rc4.c,v 1.2.2.1 2000/04/18 04:48:31 archie Exp $ + */ + +#include +#include "rc4.h" + +static __inline void +swap_bytes(uint8_t *a, uint8_t *b) +{ + uint8_t temp; + + temp = *a; + *a = *b; + *b = temp; +} + +/* + * Initialize an RC4 state buffer using the supplied key, + * which can have arbitrary length. + */ +void +librc4_init(struct rc4_state *const state, const uint8_t *key, int keylen) +{ + uint8_t j; + int i, k; + + /* Initialize state with identity permutation */ + for (i = 0; i < 256; i++) + state->perm[i] = (uint8_t)i; + state->index1 = 0; + state->index2 = 0; + + /* Randomize the permutation using key data */ + for (j = i = k = 0; i < 256; i++) { + j += state->perm[i] + key[k]; + swap_bytes(&state->perm[i], &state->perm[j]); + if (++k >= keylen) + k = 0; + } +} + +/* + * Encrypt some data using the supplied RC4 state buffer. + * The input and output buffers may be the same buffer. + * Since RC4 is a stream cypher, this function is used + * for both encryption and decryption. + */ +void +librc4_crypt(struct rc4_state *const state, const uint8_t *inbuf, uint8_t *outbuf, int buflen) +{ + int i; + uint8_t j; + for (i = 0; i < buflen; i++) { + /* Update modification indicies */ + state->index1++; + state->index2 += state->perm[state->index1]; + /* Modify permutation */ + swap_bytes(&state->perm[state->index1], &state->perm[state->index2]); + /* Encrypt/decrypt next byte */ + j = state->perm[state->index1] + state->perm[state->index2]; + outbuf[i] = inbuf[i] ^ state->perm[j]; + } +} + diff --git a/framework/lualib-src/lua-rc4/rc4.h b/framework/lualib-src/lua-rc4/rc4.h new file mode 100755 index 0000000..0fdb0ee --- /dev/null +++ b/framework/lualib-src/lua-rc4/rc4.h @@ -0,0 +1,51 @@ +/* + * rc4.h + * + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * $FreeBSD: src/sys/crypto/rc4/rc4.h,v 1.2.2.1 2000/04/18 04:48:32 archie Exp $ + */ + +#ifndef _SYS_CRYPTO_RC4_RC4_H_ +#define _SYS_CRYPTO_RC4_RC4_H_ + +struct rc4_state { + uint8_t perm[256]; + uint8_t index1; + uint8_t index2; +}; + +void librc4_init(struct rc4_state *state, const uint8_t *key, int keylen); +void librc4_crypt(struct rc4_state *state, const uint8_t *inbuf, uint8_t *outbuf, int buflen); + +#endif diff --git a/framework/lualib-src/lua-rc4/test.lua b/framework/lualib-src/lua-rc4/test.lua new file mode 100755 index 0000000..bf1a4a8 --- /dev/null +++ b/framework/lualib-src/lua-rc4/test.lua @@ -0,0 +1,53 @@ +local rc4 = require "rc4.c" + +local function gen_key(t) + local len = #t + for i = 1, len do + local b = t[i] + local c = string.char(b) + t[i] = c + end + local key = table.concat(t) + return rc4.rc4(key) +end + + +local decode_key = gen_key { + 215,100,200,204,233,50,85,196,71,141, + 122,160,93,131,243,234,162,183,36,155, + 4,62,35,205,40,102,33,27,255,55, + 131,214,156,75,163,134,126,249,74,197, + 134,197,102,228,72,90,206,235,17,243, + 134,22,49,169,227,89,16,5,117,16, + 60,248,230,217,68,138,96,194,131,170, + 136,10,112,238,238,184,72,189,163,90, + 176,42,112,225,212,84,58,228,89,175, + 244,150,168,219,112,236,101,208,175,233, + 123,55,243,235,37,225,164,110,158,71, + 201,78,114,57,48,70,142,106,43,232, + 26,32,126,194,252,239,175,98,191,94, + 75,59,149,62,39,187,32,203,42,190, + 19,243,13,133,45,61,204,187,168,247, + 163,194,23,34,133,20,17,52,118,209, + 146,193,13,40,255,52,227,32,255,13, + 222,18,1,236,152,46,41,100,233,209, + 91,141,148,115,175,25,135,193,77,254, + 147,224,191,161,9,191,213,236,223,212, + 250,190,231,251,170,127,41,212,227,19, + 166,63,161,58,179,81,84,59,18,162, + 57,166,130,248,71,139,184,28,120,151, + 241,115,86,217,111,0,88,153,213,59, + 172,123,123,78,182,46,159,10,105,178, + 172,163,88,47,155,160, +} + + +local function decode_data(data) + local o = decode_key:crypt(data) + decode_key:reset() + return o +end + +for i = 1, 10 do + print(decode_data("asdf")) +end diff --git a/framework/lualib-src/lua-termfx/Makefile b/framework/lualib-src/lua-termfx/Makefile new file mode 100755 index 0000000..a919216 --- /dev/null +++ b/framework/lualib-src/lua-termfx/Makefile @@ -0,0 +1,86 @@ +# simple Makefile for termfx. Works for Linux, MacOS X, probably other unixen +# +# Gunnar Zötl , 2014-2015. +# Released under the terms of the MIT license. See file LICENSE for details. + +TERMBOX = ../../3rd//termbox_next + +# try some automatic discovery. If that does not work for you, just set +# the following values manually. +OS = $(shell uname -s) +LUAVERSION = $(shell lua -e "print(string.match(_VERSION, '%d+%.%d+'))") +LUA_BINDIR = $(shell dirname `which lua`) +LUAROOT = $(shell dirname $(LUA_BINDIR)) + +OBJS = termfx.o termfx_color.o tbutils.o + +TARGET = termfx.so + +CC = gcc +CFLAGS = -fPIC -Wall +LUA_INCDIR = $(LUAROOT)/include +LUA_LIBDIR = $(LUAROOT)/lib + +# OS specialities +ifeq ($(OS),Darwin) +LIBFLAG = -bundle -undefined dynamic_lookup -all_load +else +LIBFLAG = -shared +endif + +ifdef DEBUG +CCFLAGS=-g $(CFLAGS) +CLDFLAGS=-g -lefence $(LIBFLAG) +else +CCFLAGS=$(CFLAGS) +CLDFLAGS=$(LIBFLAG) +endif + +# install target locations +INST_DIR = /usr/local +INST_LIBDIR = $(INST_DIR)/lib/lua/$(LUAVERSION) +INST_LUADIR = $(INST_DIR)/share/lua/$(LUAVERSION) + +all: $(TARGET) + +$(TARGET): $(OBJS) libtermbox.a + $(CC) $(CLDFLAGS) -o $@ -L$(LUA_LIBDIR) $(OBJS) -L. -ltermbox + +%.o: %.c termbox.h termfx.h + $(CC) $(CCFLAGS) -I$(LUA_INCDIR) -c $< -o $@ + +# $(TERMBOX): +# git clone https://github.com/nullgemm/termbox_next.git + +termbox.h: $(TERMBOX)/src/termbox.h + cp $(TERMBOX)/src/$@ . + +libtermbox.a: $(TERMBOX)/bin/termbox.a + cp $< $@ + +$(TERMBOX)/bin/termbox.a: $(TERMBOX) + cd $(TERMBOX) && FLAGS="-fPIC" make + +$(TERMBOX)/src/termbox.h: $(TERMBOX) + touch $@ + +install: + mkdir -p $(INST_LIBDIR) + cp $(TARGET) $(INST_LIBDIR) + +clean: + find . -name "*~" -exec rm {} \; + find . -name .DS_Store -exec rm {} \; + find . -name "._*" -exec rm {} \; + rm -f *.a *.o *.so core termbox.h + rm -f screenshot.html samples/screenshot.html + cd $(TERMBOX) && make clean + +distclean: clean + rm -rf $(TERMBOX) + +check: + cppcheck *.c + +dist: $(TERMBOX) clean + cd $(TERMBOX) && rm -rf .git .gitignore diff --git a/framework/lualib-src/lua-termfx/mini_utf8.h b/framework/lualib-src/lua-termfx/mini_utf8.h new file mode 100755 index 0000000..3c7e6c7 --- /dev/null +++ b/framework/lualib-src/lua-termfx/mini_utf8.h @@ -0,0 +1,272 @@ +/* mini_utf8.h + * + * Gunnar Zötl 2014 + * + * a tiny library to deal with utf8 encoded strings. Tries to fault + * invalid unicode codepoints and invalid utf8 sequences. + * + * Stuff starting with _mini_utf8_* is reserved and private. Don't name your + * identifiers like that, and don't use stuff named like that. + * + * Needed #includes: + * ----------------- + * - + * + * Functions: + * ---------- + * + * int mini_utf8_check_encoding(const char* str) + * test all characters in a string for valid utf8 encoding. Returns + * 0 if the string is valid utf8, 1 if it is pure ASCII, or -1, if + * the string is not valid utf8. We do a somewhat relaxed test in + * that all chars in the range [0x01-0x1F] are considered valid. + * + * int mini_utf8_decode(const char **str) + * returns the next valid utf8 character from *str, updating *str + * to point behind that char. If *str points to a 0 byte, 0 is + * returned and *str is not updated. If *str does not point to a + * valid utf8 encoded char, -1 is returned and *str is not updated. + * + * int mini_utf8_encode(int cp, const char* str, int len) + * encodes the codepoint cp into an utf8 byte sequence and stores + * that into str, where len bytes are available. If that went without + * errors, the length of the encoded sequence is returned. If cp is + * not a valid code point, -1 is returned, for all other problems, + * 0 is returned. If cp is 0, it is stored as a single byte 0, even + * if that is not really valid utf8. Also, all chars in the range + * [0x01-0x1F] are considered valid. + * + * int mini_utf8_strlen(const char *str) + * returns the number of utf8 codepoints in the string str, or -1 if + * the string contains invalid utf8 sequences. + * + * int mini_utf8_byteoffset(const char *str, int cpno) + * returns the number of bytes from the start of the string to the + * start of codepoint number cpno. Returns >=0 for the offset, or + * -1 if the string had less than cpno codepoints, or contained an + * invalid utf8 sequence. + * + * Example: + * -------- + * + #include + #include + #include "mini_utf8.h" + + int main(int argc, char **argv) + { + int size = 0x11FFFF; + int l = size * 4 + 1, i = 0, ok = 1, cp = 0; + int *ibuf = calloc(size, sizeof(int)); + char *cbuf = calloc(l, sizeof(char)); + char *str = cbuf; + + while (cp < size) { + cp = cp + 1; + int n = mini_utf8_encode(cp, str, l); + if (n > 0) { + l -= n; + str += n; + ibuf[i++] = cp; + } + } + *str = 0; + size = i; + + str = cbuf; + for (i = 0; ok && (i < size); ++i) { + cp = mini_utf8_decode((const char**)&str); + ok = (cp == ibuf[i]); + } + + ok = ok && (mini_utf8_strlen(cbuf) == size); + + printf("Roundtrip test %s.\n", ok ? "succeeded" : "failed"); + + ok = mini_utf8_check_encoding(cbuf); + + printf("utf8 check %s.\n", ok >= 0 ? "succeeded" : "failed"); + + return ok < 0; + } + * + * License: + * -------- + * + * Copyright (c) 2014 Gunnar Zötl + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _mini_utf8 +#define _mini_utf8 + +#define _mini_utf8_in_range(c, s, e) ((s) <= (c) && (c) <= (e)) + +/* The patterns for the encoding check are taken from + * http://www.w3.org/International/questions/qa-forms-utf-8 + */ +static inline int mini_utf8_check_encoding(const char *str) +{ + const unsigned char *s = (const unsigned char*) str; + int isu = 1; + int isa = 1; + + while (*s && isu) { + if (*s <= 0x7F) { + s += 1; + continue; /* [\x09\x0A\x0D\x20-\x7E] # ASCII (somewhat relaxed) */ + } + isa = 0; /* if we get here, the file is not pure ASCII */ + if (_mini_utf8_in_range(*s, 0xC2, 0xDF) && _mini_utf8_in_range(s[1], 0x80, 0xBF)) { + s += 2; /* [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte */ + } else if (*s == 0xE0 && _mini_utf8_in_range(s[1], 0xA0, 0xBF) && _mini_utf8_in_range(s[2], 0x80, 0xBF)) { + s += 3; /* \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs */ + } else if ((*s <= 0xEC || *s == 0xEE || *s == 0xEF) && _mini_utf8_in_range(s[1], 0x80, 0xBF) && _mini_utf8_in_range(s[2], 0x80, 0xBF)) { + s += 3; /* [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte */ + } else if (*s == 0xED && _mini_utf8_in_range(s[1], 0x80, 0x9F) && _mini_utf8_in_range(s[2], 0x80, 0xBF)) { + s += 3; /* \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates */ + } else if (*s == 0xF0 && _mini_utf8_in_range(s[1], 0x90, 0xBF) && _mini_utf8_in_range(s[2], 0x80, 0xBF) && _mini_utf8_in_range(s[3], 0x80, 0xBF)) { + s += 4; /* \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 */ + } else if (*s <= 0xF3 && _mini_utf8_in_range(s[1], 0x80, 0xBF) && _mini_utf8_in_range(s[2], 0x80, 0xBF) && _mini_utf8_in_range(s[3], 0x80, 0xBF)) { + s += 4; /* [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 */ + } else if (*s == 0xF4 && _mini_utf8_in_range(s[1], 0x80, 0x8F) && _mini_utf8_in_range(s[2], 0x80, 0xBF) && _mini_utf8_in_range(s[3], 0x80, 0xBF)) { + s += 4; /* \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 */ + } else + isu = 0; + } + + if (isa && isu) + return 1; + else if (isu) + return 0; + return -1; +} + +/* bits start end bytes encoding + * 7 U+0000 U+007F 1 0xxxxxxx + * 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx + * 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx + * 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + * + * validity checking derived from above patterns +*/ +static inline int mini_utf8_decode(const char **str) +{ + const unsigned char *s = (const unsigned char*) *str; + int ret = -1; + if (!*s) return 0; + + if (*s <= 0x7F) { + ret = s[0]; /* ASCII */ + *str = (char*) s+1; + return ret; + } else if (*s < 0xC2) { + return -1; + } else if (*s<= 0xDF) { + if ((s[1] & 0xC0) != 0x80) return -1; + ret = ((s[0] & 0x1F) << 6) | (s[1] & 0x3F); + *str = (char*) s+2; + return ret; + } else if (*s <= 0xEF) { + if ((s[1] & 0xC0) != 0x80) return -1; + if (*s == 0xE0 && s[1] < 0xA0) return -1; + if (*s == 0xED && s[1] > 0x9F) return -1; + if ((s[2] & 0xC0) != 0x80) return -1; + ret = ((s[0] & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); + *str = (char*) s+3; + return ret; + } else if (*s <= 0xF4) { + if ((s[1] & 0xC0) != 0x80) return -1; + if (*s == 0xF0 && s[1] < 0x90) return -1; + if (*s == 0xF4 && s[1] > 0x8F) return -1; + if ((s[2] & 0xC0) != 0x80) return -1; + if ((s[3] & 0xC0) != 0x80) return -1; + ret = ((s[0] & 0x0F) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); + *str = (char*) s+4; + return ret; + } + + return ret; +} + +/* only utf16 surrogate pairs (0xD800-0xDFFF) are invalid unicode + * codepoints + */ +static inline int mini_utf8_encode(int cp, char *str, int len) +{ + unsigned char *s = (unsigned char*) str; + if (cp <= 0x7F) { + if (len < 1) return 0; + *s = (cp & 0x7F); + return 1; + } else if (cp <= 0x7FF) { + if (len < 2) return 0; + *s++ = (cp >> 6) | 0xC0; + *s = (cp & 0x3F) | 0x80; + return 2; + } else if (cp <= 0xFFFF) { + if (0xD800 <= cp && cp <= 0xDFFF) return -1; + if (len < 3) return 0; + *s++ = (cp >> 12) | 0xE0; + *s++ = ((cp >> 6) & 0x3F) | 0x80; + *s = (cp & 0x3F) | 0x80; + return 3; + } else if (cp <= 0x10FFFF) { + if (len < 4) return 0; + *s++ =(cp >> 18) | 0xF0; + *s++ =((cp >> 12) & 0x3F) | 0x80; + *s++ =((cp >> 6) & 0x3F) | 0x80; + *s =(cp & 0x3F) | 0x80; + return 4; + } + return -1; +} + +static inline int mini_utf8_strlen(const char *str) +{ + const char *s = str; + int len = 0; + int ok = mini_utf8_decode(&s); + while (ok > 0) { + ++len; + ok = mini_utf8_decode(&s); + } + if (ok == 0) + return len; + return -1; +} + +static inline int mini_utf8_byteoffset(const char *str, int cpno) +{ + const char *s = str; + int cnt = 0; + int ok = 1; + for (cnt = 0; (cnt < cpno) && (ok > 0); ++cnt) { + ok = mini_utf8_decode(&s); + } + if (ok > 0) + return (int)(s - str); + return -1; +} + +#endif /* _mini_utf8 */ diff --git a/framework/lualib-src/lua-termfx/tbutils.c b/framework/lualib-src/lua-termfx/tbutils.c new file mode 100755 index 0000000..33a9140 --- /dev/null +++ b/framework/lualib-src/lua-termfx/tbutils.c @@ -0,0 +1,218 @@ +/* termbox utils + * + * Utility functions for use with termbox + * + * Gunnar Zötl , 2015 + * Released under the terms of the MIT license. See file LICENSE for details. + */ + +#include +#include + +#include "termbox.h" +#include "tbutils.h" + +void tbu_blitbuffer(struct tb_cell *to, int tw, int th, int x, int y, const struct tb_cell *from, int w, int h) +{ + if (x + w < 0 || x >= tw || w <= 0) + return; + if (y + h < 0 || y >= th || h <= 0) + return; + int xo = 0, yo = 0, ww = w, hh = h; + if (x < 0) { + xo = -x; + ww -= xo; + x = 0; + } + if (y < 0) { + yo = -y; + hh -= yo; + y = 0; + } + if (ww > tw - x) { + ww = tw - x; + } + if (hh > th - y) { + hh = th - y; + } + int sy; + struct tb_cell *dst = &CELL(to, tw, x, y); + const struct tb_cell *src = from + yo * w + xo; + size_t size = sizeof(struct tb_cell) * ww; + + for (sy = 0; sy < hh; ++sy) { + memcpy(dst, src, size); + dst += tw; + src += w; + } +} + +void tbu_blit(int x, int y, const struct tb_cell *from, int w, int h) +{ + int bbw = tb_width(); + int bbh = tb_height(); + struct tb_cell *bb = tb_cell_buffer(); + + tbu_blitbuffer(bb, bbw, bbh, x, y, from, w, h); +} + +void tbu_fillbufferregion(struct tb_cell *buf, int bw, int bh, int x, int y, int w, int h, const struct tb_cell *fill) +{ + if (x + w < 0 || x >= bw || w <= 0) + return; + if (y + h < 0 || y >= bh || h <= 0) + return; + + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + if (x + w > bw) { + w = bw - x; + } + if (y + h > bh) { + h = bh - y; + } + int sx, sy; + struct tb_cell *dst = &CELL(buf, bw, x, y); + for (sy = 0; sy < h; ++sy) { + for (sx = 0; sx < w; ++sx) { + dst[sx] = *fill; + } + dst += bw; + } +} + +void tbu_fillregion(int x, int y, int w, int h, const struct tb_cell *fill) +{ + int bbw = tb_width(); + int bbh = tb_height(); + struct tb_cell *bb = tb_cell_buffer(); + + tbu_fillbufferregion(bb, bbw, bbh, x, y, w, h, fill); +} + +void tbu_copybufferregion(struct tb_cell *buf, int bw, int bh, int tx, int ty, int x, int y, int w, int h) +{ + if (w < 1 || h < 1) + return; + if (x >= bw || x + w < 0 || y >= bh || y + h < 0) + return; + if (tx >= bw || tx + w < 0 || ty >= bh || ty + h < 0) + return; + + if (x < 0) { + int dx = -x; + x = 0; + tx += dx; + w -= dx; + } + if (x + w > bw) { + w = bw - x; + } + if (tx < 0) { + int dx = -tx; + tx = 0; + x += dx; + w -= dx; + } + if (tx + w > bw) { + w = bw - tx; + } + + if (y < 0) { + int dy = -y; + y = 0; + ty += dy; + h -= dy; + } + if (y + h > bh) { + h = bh - y; + } + if (ty < 0) { + int dy = -ty; + ty = 0; + y += dy; + h -= dy; + } + if (ty + h > bh) { + h = bh - ty; + } + + int ys = 1; + + if (ty > y) { + y = y + h - 1; + ty = ty + h - 1; + ys = -1; + } + + int ry; + int from = x, to = tx; + if (y > 0) { + from += y * bw; + } + if (ty > 0) { + to += ty * bw; + } + + for (ry = 0; ry < h; ++ry) { + int cfy = from + (ry * ys * bw); + int cty = to + (ry * ys * bw); + memmove(&buf[cty], &buf[cfy], w * sizeof(struct tb_cell)); + } +} + +void tbu_copyregion(int tx, int ty, int x, int y, int w, int h) +{ + int bbw = tb_width(); + int bbh = tb_height(); + struct tb_cell *bb = tb_cell_buffer(); + + tbu_copybufferregion(bb, bbw, bbh, tx, ty, x, y, w, h); +} + +void tbu_scrollbufferregion(struct tb_cell *buf, int bw, int bh, int x, int y, int w, int h, int sx, int sy, const struct tb_cell *fill) +{ + int fx = x, tx = x; + int fy = y, ty = y; + + if (sx < 0) { + sx = -1; + fx = x + 1; + } else if (sx > 0) { + sx = 1; + tx = x + 1; + } + + if (sy < 0) { + sy = -1; + fy = y + 1; + } else if (sy > 0) { + sy = 1; + ty = y + 1; + } + + tbu_copybufferregion(buf, bw, bh, tx, ty, fx, fy, w - abs(sx), h - abs(sy)); + if (sx != 0) { + int fillx = sx > 0 ? x : x + w - 1; + tbu_fillbufferregion(buf, bw, bh, fillx, y, 1, h, fill); + } + if (sy != 0) { + int filly = sy > 0 ? y : y + h - 1; + tbu_fillbufferregion(buf, bw, bh, x, filly, w, 1, fill); + } +} + +void tbu_scrollregion(int x, int y, int w, int h, int sx, int sy, const struct tb_cell *fill) +{ + int bbw = tb_width(); + int bbh = tb_height(); + struct tb_cell *bb = tb_cell_buffer(); + + tbu_scrollbufferregion(bb, bbw, bbh, x, y, w, h, sx, sy, fill); +} diff --git a/framework/lualib-src/lua-termfx/tbutils.h b/framework/lualib-src/lua-termfx/tbutils.h new file mode 100755 index 0000000..2ea6dd7 --- /dev/null +++ b/framework/lualib-src/lua-termfx/tbutils.h @@ -0,0 +1,102 @@ +/* termbox utils + * + * Utility functions for use with termbox + * + * Gunnar Zötl , 2015 + * Released under the terms of the MIT license. See file LICENSE for details. + */ + +#ifndef tbutils_h +#define tbutils_h + +#define CELL(buf, w, x, y) (buf)[(y) * (w) + (x)] + +/* blit one buffer into another buffer + * + * Arguments: + * to target buffer to blit into + * tw, th target buffer dimensions + * x, y target coordinates + * from source buffer, must be different from "to" + * w, h dimensions of source buffer + */ +void tbu_blitbuffer(struct tb_cell *to, int tw, int th, int x, int y, const struct tb_cell *from, int w, int h); + +/* blit buffer into terminal back buffer + * + * Arguments: + * x, y target coordinates + * from source buffer + * w, h dimensions of source buffer + */ +void tbu_blit(int x, int y, const struct tb_cell *from, int w, int h); + +/* fill a region of a buffer + * + * Arguments: + * buf target buffer + * bw, bh target buffer dimensions + * x, y target coordinates + * w, h dimensions of rect to fill + * fill cell describing what to fill with + */ +void tbu_fillbufferregion(struct tb_cell *buf, int bw, int bh, int x, int y, int w, int h, const struct tb_cell *fill); + +/* fill a region of the terminal back buffer + * + * Arguments: + * x, y target coordinates + * w, h dimensions of rect to fill + * fill cell describing what to fill with + */ +void tbu_fillregion(int x, int y, int w, int h, const struct tb_cell *fill); + +/* copy a region of a buffer to another place + * + * Arguments: + * buf target buffer + * bw, bh target buffer dimensions + * tx, ty target coordinates + * x, y source coordinates + * w, h dimensions of rect to copy + */ +void tbu_copybufferregion(struct tb_cell *buf, int bw, int bh, int tx, int ty, int x, int y, int w, int h); + +/* copy a region of the terminal back buffer to another place + * + * Arguments: + * tx, ty target coordinates + * x, y source coordinates + * w, h dimensions of rect to copy + */ +void tbu_copyregion(int tx, int ty, int x, int y, int w, int h); + +/* scroll a region within a buffer + * + * Arguments: + * buf target buffer + * bw, bh target buffer dimensions + * x, y coordinates of scrolled region + * w, h dimensions of scrolled region + * sx, sy directions to scroll in x and y + * fill what to fill the cleared space with + * + * Note: the amount by which is scrolled is always 1. + * sy and sx only give the directions: -1 left/up, 0 none, 1 right/down. + */ +void tbu_scrollbufferregion(struct tb_cell *buf, int bw, int bh, int x, int y, int w, int h, int sx, int sy, const struct tb_cell *fill); + +/* scroll a region within the terminal back buffer + * + * Arguments: + * x, y coordinates of scrolled region + * w, h dimensions of scrolled region + * sx, sy directions to scroll in x and y + * fill what to fill the cleared space with + * + * Note: the amount by which is scrolled is always 1. + * sy and sx only give the directions: -1 left/up, 0 none, 1 right/down. + */ +void tbu_scrollregion(int x, int y, int w, int h, int sx, int sy, const struct tb_cell *fill); + +#endif /* tbutils_h */ diff --git a/framework/lualib-src/lua-termfx/termfx.c b/framework/lualib-src/lua-termfx/termfx.c new file mode 100755 index 0000000..e1820da --- /dev/null +++ b/framework/lualib-src/lua-termfx/termfx.c @@ -0,0 +1,1797 @@ +/* termfx.c + * + * provide simple terminal interface for lua + * + * Gunnar Zötl , 2014-2015 + * Released under the terms of the MIT license. See file LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include + +#include "lua.h" +#include "lauxlib.h" + +#include "mini_utf8.h" + +#include "termbox.h" +#include "tbutils.h" +#include "termfx.h" + +/* userdata for a termbox cell */ +typedef struct tb_cell TfxCell; + +/* userdata for a termfx offscreen buffer */ +typedef struct { + int w, h; + uint16_t fg, bg; + struct tb_cell *buf; +} TfxBuffer; + +static const int top_left_coord = 1; +static uint16_t default_fg, default_bg; +static int initialized = 0; + +static uint64_t mstimer_tm0 = 0; + +/* helper: try to get a char from the stack at index. If the value there + * is a number, return it. If the value is a string of length 1, return + * the value of the first char. If it is a string of length >1, try to + * read it as an utf8 encoded char. + */ +static uint32_t _tfx_getchar(lua_State *L, int index) +{ + uint32_t res = 0; + int t = lua_type(L, index); + if (t == LUA_TNUMBER) + res = (uint32_t) lua_tointeger(L, index); + else if (t == LUA_TSTRING) { + const char* str = lua_tostring(L, index); + int l = strlen(str); + if (l == 1) + res = *str; + else if (l > 1) { + res = mini_utf8_decode(&str); + if (*str != 0) + return luaL_argerror(L, index, "invalid char"); + } + } else { + return luaL_argerror(L, index, "number or string"); + } + return res; +} + +/* helper: as above, but if there's nil or nothing on the stack at the + * index, return the default value. + */ +static uint32_t _tfx_optchar(lua_State *L, int index, uint32_t dfl) +{ + if (!lua_isnoneornil(L, index)) + return _tfx_getchar(L, index); + return dfl; +} + +/* helper: check whether the value of the given index on the lua stack + * is a userdata of the given type. Return 1 for yes, 0 for no. + */ +static int _tfx_isUserdataOfType(lua_State *L, int index, const char* type) +{ + if (lua_isuserdata(L, index)) { + if (lua_getmetatable(L, index)) { + luaL_getmetatable(L, type); + if (lua_rawequal(L, -1, -2)) { + lua_pop(L, 2); + return 1; + } + lua_pop(L, 2); + } + } + return 0; +} + +/* helper: return a millisecond timer, which is 0 at the load time of + * the library, so that the result will fit into an uint32_t for some time + * (a little more than 49 days) + */ +static uint32_t _tfx_getMsTimer(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t tm = tv.tv_sec * 1000 + tv.tv_usec / 1000 - mstimer_tm0; + if (tm > INT_MAX) { + mstimer_tm0 += INT_MAX; + tm -= INT_MAX; + } + return (uint32_t) tm; +} + +/*** TfxCell Userdata handling ***/ + +/* tfx_isCell + * + * If the value at the given acceptable index is a full userdata of the + * type TFXCELL, then return 1, else return 0. + * + * Arguments: + * L Lua State + * index stack index where the userdata is expected + */ +static int tfx_isCell(lua_State *L, int index) +{ + return _tfx_isUserdataOfType(L, index, TFXCELL); +} + +/* tfx_toCell + * + * If the value at the given acceptable index is a full userdata, returns + * its block address. Otherwise, returns NULL. + * + * Arguments: + * L Lua State + * index stack index where the userdata is expected + */ +static TfxCell* tfx_toCell(lua_State *L, int index) +{ + TfxCell *tfxcell = (TfxCell*) lua_touserdata(L, index); + return tfxcell; +} + +/* tfx_checkCell + * + * Checks whether the function argument narg is a userdata of the type + * TFXCELL. If so, returns its block address, else throw an error. + * + * Arguments: + * L Lua State + * index stack index where the userdata is expected + */ +static TfxCell* tfx_checkCell(lua_State *L, int index) +{ + TfxCell *tfxcell = (TfxCell*) luaL_checkudata(L, index, TFXCELL); + return tfxcell; +} + +/* tfx_pushCell + * + * create a new, empty TfxCell userdata and push it to the stack. + * + * Arguments: + * L Lua state + */ +static TfxCell* tfx_pushCell(lua_State *L) +{ + TfxCell *tfxcell = (TfxCell*) lua_newuserdata(L, sizeof(TfxCell)); + luaL_getmetatable(L, TFXCELL); + lua_setmetatable(L, -2); + return tfxcell; +} + +/* tfx__toStringCell + * + * __tostring metamethod for the TfxCell userdata. + * Returns a string representation of the TfxCell + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxCell userdata + * + * Lua Returns: + * +1 the string representation of the TfxCell userdata + */ +static int tfx__tostringCell(lua_State *L) +{ + TfxCell *tfxcell = (TfxCell*) lua_touserdata(L, 1); + lua_pushfstring(L, "%s (%p)", TFXCELL, tfxcell); + return 1; +} + +/* tfx__indexCell + * + * __index metamethod for the TfxCell userdata. + * Returns a the value accessible at the index which is on top of the + * stack, or nil of there is no such value + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxCell userdata + * 2 index to access + * + * Lua Returns: + * +1 value accessible through index + */ +static int tfx__indexCell(lua_State *L) +{ + TfxCell *tfxcell = tfx_checkCell(L, 1); + const char* what = luaL_checkstring(L, 2); + + if (!strcmp(what, "ch")) + lua_pushinteger(L, tfxcell->ch); + else if (!strcmp(what, "fg")) + lua_pushinteger(L, tfxcell->fg); + else if (!strcmp(what, "bg")) + lua_pushinteger(L, tfxcell->bg); + else + lua_pushnil(L); + return 1; +} + +/* tfx__newindexCell + * + * __newindex metamethod for the TfxCell userdata. + * Sets a the value accessible at the index which is on the stack. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxCell userdata + * 2 index to set + * 3 value to set + * + * Lua Returns: + * - + */ +static int tfx__newindexCell(lua_State *L) +{ + TfxCell *tfxcell = tfx_checkCell(L, 1); + const char* what = luaL_checkstring(L, 2); + uint32_t val = (uint32_t) luaL_checkinteger(L, 3); + + if (!strcmp(what, "ch")) + tfxcell->ch = val < ' ' ? ' ' : val; + else if (!strcmp(what, "fg")) + tfxcell->fg = val; + else if (!strcmp(what, "bg")) + tfxcell->bg = val; + return 0; +} + +/* tfx_newCell + * + * create a new TfxCell object, initialize it, put it into a userdata and + * return it to the user. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * +1 (opt) char to store, defaults to ' ' + * +2 (opt) forgeround color, defaults to tfx default foreground color + * +3 (opt) background color, defaults to tfx default background color + * + * Lua Returns: + * +1 the TfxCell userdata + */ +static int tfx_newCell(lua_State *L) +{ + maxargs(L, 3); + uint32_t ch = _tfx_optchar(L, 1, ' '); + uint16_t fg = (uint16_t) luaL_optinteger(L, 2, default_fg) & 0xFFFF; + uint16_t bg = (uint16_t) luaL_optinteger(L, 3, default_bg) & 0xFFFF; + TfxCell *tfxcell = tfx_pushCell(L); + tfxcell->ch = ch < ' ' ? ' ' : ch; + tfxcell->fg = fg; + tfxcell->bg = bg; + return 1; +} + +/* metamethods for the TfxCell userdata + */ +static const luaL_Reg tfx_CellMeta[] = { + {"__tostring", tfx__tostringCell}, + {"__index", tfx__indexCell}, + {"__newindex", tfx__newindexCell}, + {NULL, NULL} +}; + +/*** TfxBuffer Userdata handling ***/ + +/* tfx_isBuffer + * + * If the value at the given acceptable index is a full userdata of the + * type TFXBUFFER, then return 1, else return 0. + * + * Arguments: + * L Lua State + * index stack index where the userdata is expected + */ +static int tfx_isBuffer(lua_State *L, int index) +{ + return _tfx_isUserdataOfType(L, index, TFXBUFFER); +} + +/* tfx_checkBuffer + * + * Checks whether the function argument narg is a userdata of the type + * TFXBUFFER. If so, returns its block address, else throw an error. + * + * Arguments: + * L Lua State + * index stack index where the userdata is expected + */ +static TfxBuffer* tfx_checkBuffer(lua_State *L, int index) +{ + TfxBuffer *tfxbuf = (TfxBuffer*) luaL_checkudata(L, index, TFXBUFFER); + return tfxbuf; +} + +/* tfx_pushBuffer + * + * create a new, empty TfxBuffer userdata and push it to the stack. + * + * Arguments: + * L Lua state + */ +static TfxBuffer* tfx_pushBuffer(lua_State *L) +{ + TfxBuffer *tfxbuf = (TfxBuffer*) lua_newuserdata(L, sizeof(TfxBuffer)); + luaL_getmetatable(L, TFXBUFFER); + lua_setmetatable(L, -2); + return tfxbuf; +} + +/*** Housekeeping metamethods ***/ + +/* tfx__gcBuffer + * + * __gc metamethod for the TfxBuffer userdata. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + */ +static int tfx__gcBuffer(lua_State *L) +{ + TfxBuffer *tfxbuf = (TfxBuffer*) lua_touserdata(L, 1); + if (tfxbuf->buf) free(tfxbuf->buf); + return 0; +} + +/* tfx__tostringBuffer + * + * __tostring metamethod for the TfxBuffer userdata. + * Returns a string representation of the TfxBuffer + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * + * Lua Returns: + * +1 the string representation of the TfxCell userdata + */ +static int tfx__tostringBuffer(lua_State *L) +{ + TfxBuffer *tfxbuf = (TfxBuffer*) lua_touserdata(L, 1); + lua_pushfstring(L, "%s (%p)", TFXBUFFER, tfxbuf); + return 1; +} + +/* metamethods for the TfxBuffer userdata + */ +static const luaL_Reg tfx_BufferMeta[] = { + {"__gc", tfx__gcBuffer}, + {"__tostring", tfx__tostringBuffer}, + {NULL, NULL} +}; + +/* tfx_newBuffer + * + * create a new TfxBuffer object, initialize it, put it into a userdata + * and return it to the user. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * +1 the TfxBuffer userdata + */ +static int tfx_newBuffer(lua_State *L) +{ + maxargs(L, 2); + int w = (int) luaL_checkinteger(L, 1); + int h = (int) luaL_checkinteger(L, 2); + if (w < 1 || h < 1) + return luaL_error(L, "buffer dimensions must be >=1"); + TfxBuffer *tfxbuf = tfx_pushBuffer(L); + tfxbuf->buf = calloc(w * h, sizeof(struct tb_cell)); + tfxbuf->w = w; + tfxbuf->h = h; + tfxbuf->fg = default_fg; + tfxbuf->bg = default_bg; + return 1; +} + +/* tfx_bufAttributes + * + * sets or gets default foreground and background attributes on a buffer. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * 2 (opt) foreground attributes + * 3 (opt) background attributes + * + * Lua Returns: + * +1 buffer default foreground attribute + * +2 buffer default background attribute + */ +static int tfx_bufAttributes(lua_State *L) +{ + maxargs(L, 3); + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + if (lua_gettop(L) > 1) { + tfxbuf->fg = (uint16_t) luaL_optinteger(L, 2, tfxbuf->fg) & 0xFFFF; + tfxbuf->bg = (uint16_t) luaL_optinteger(L, 3, tfxbuf->bg) & 0xFFFF; + } + lua_pushinteger(L, tfxbuf->fg); + lua_pushinteger(L, tfxbuf->bg); + return 2; +} + +/* tfx_bufClear + * + * clears a buffer to the default foreground and background attributes. + * If the optional foreground and background attribute arguments are + * given, these will be set using tfx_bufSetAttributes before clearing. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * 2 (opt) foreground attributes, defaults to buffer default foreground + * 3 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * - + */ +static int tfx_bufClear(lua_State *L) +{ + if (lua_gettop(L) > 1) + tfx_bufAttributes(L); + + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int c; + for (c = 0; c < tfxbuf->w * tfxbuf->h; ++c) { + TfxCell *cell = &tfxbuf->buf[c]; + cell->fg = tfxbuf->fg; + cell->bg = tfxbuf->bg; + cell->ch = ' '; + } + return 0; +} + +/* _tfx_bufChangeCell + * + * analog to tb_changecell() for buffers + */ +static int _tfx_bufChangeCell(TfxBuffer *tfxbuf, int x, int y, TfxCell *c) +{ + if (x > tfxbuf->w || y > tfxbuf->h || x < 0 || y < 0) + return 0; + + TfxCell *cell = &tfxbuf->buf[y * tfxbuf->w + x]; + *cell = *c; + return 1; +} + +/* tfx_bufSetcell + * + * sets a cell in a buffer. Either uses the default values for foreground + * and background, or the values passed on the lua stack. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * 2 x coordinate of cell + * 3 y coordinate of cell + * either + * 4 TfxCell + * or + * 4 (opt) char, defaults to ' ' + * 5 (opt) foreground attributes, defaults to buffer default foreground + * 6 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * - + */ +static int tfx_bufSetcell(lua_State *L) +{ + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int x = (int) luaL_checkinteger(L, 2) - top_left_coord; + int y = (int) luaL_checkinteger(L, 3) - top_left_coord; + + uint32_t ch = ' '; + uint16_t fg = TB_WHITE, bg = TB_BLACK; + if (tfx_isCell(L, 4)) { + maxargs(L, 4); + TfxCell *tfxcell = tfx_toCell(L, 4); + ch = tfxcell->ch; + fg = tfxcell->fg; + bg = tfxcell->bg; + } else { + maxargs(L, 6); + ch = _tfx_optchar(L, 4, ' '); + fg = (uint16_t) luaL_optinteger(L, 5, tfxbuf->fg) & 0xFFFF; + bg = (uint16_t) luaL_optinteger(L, 6, tfxbuf->bg) & 0xFFFF; + } + TfxCell cell; + cell.ch = ch < '?' ? ' ' : ch; + cell.fg = fg; + cell.bg = bg; + + _tfx_bufChangeCell(tfxbuf, x, y, &cell); + return 0; +} + +/* tfx_bufGetCell + * + * gets data from a cell in a buffer. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * 2 x coordinate of cell + * 3 y coordinate of cell + * + * Lua Returns: + * a TfxCell with the data from the read cell + */ +int tfx_bufGetCell(lua_State *L) +{ + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int x = (int) luaL_checkinteger(L, 2) - top_left_coord; + int y = (int) luaL_checkinteger(L, 3) - top_left_coord; + + if (x < 0 || x >= tfxbuf->w || y < 0 || y >= tfxbuf->h) { + lua_pushnil(L); + return 1; + } + TfxCell *tfxcell = tfx_pushCell(L); + *tfxcell = CELL(tfxbuf->buf, tfxbuf->w, x, y); + + return 1; +} + +/* tfx_bufBlit + * + * blits the contents of a buffer to another buffer. Just a modified + * copy of my improved tb_blit() routine. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 destination TfxBuffer + * 2 x coordinate of cell + * 3 y coordinate of cell + * 4 source TfxBuffer + * + * Lua Returns: + * - + */ +static int tfx_bufBlit(lua_State *L) +{ + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int x = (int) luaL_checkinteger(L, 2) - top_left_coord; + int y = (int) luaL_checkinteger(L, 3) - top_left_coord; + TfxBuffer *other = tfx_checkBuffer(L, 4); + + tbu_blitbuffer(tfxbuf->buf, tfxbuf->w, tfxbuf->h, x, y, other->buf, other->w, other->h); + return 0; +} + +/* tfx_bufRect + * + * draws a rectangle. Either uses the default values for foreground and + * background, or the values passed on the lua stack. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * 2 x coordinate of rect + * 3 y coordinate of rect + * 4 width of rect + * 5 height of rect + * either + * 6 TfxCell + * or + * 6 (opt) char, defaults to ' ' + * 7 (opt) foreground attributes, defaults to buffer default foreground + * 8 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * false if the rectangle was entirely outside of the terminal, true if not + */ +static int tfx_bufRect(lua_State *L) +{ + TfxCell cel, *celp = &cel; + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int x = (int) luaL_checkinteger(L, 2) - top_left_coord; + int y = (int) luaL_checkinteger(L, 3) - top_left_coord; + int w = (int) luaL_checkinteger(L, 4); + int h = (int) luaL_checkinteger(L, 5); + if (tfx_isCell(L, 6)) { + maxargs(L, 6); + celp = tfx_toCell(L, 6); + } else { + maxargs(L, 8); + cel.ch = _tfx_optchar(L, 6, 0); + cel.fg = (uint16_t) luaL_optinteger(L, 7, default_fg) & 0xFFFF; + cel.bg = (uint16_t) luaL_optinteger(L, 8, default_bg) & 0xFFFF; + } + + tbu_fillbufferregion(tfxbuf->buf, tfxbuf->w, tfxbuf->h, x, y, w, h, celp); + return 0; +} + +/* tfx_bufCopyRegion + * + * copies a rectangular region of a buffer to another place + * + * Arguments: + * L Lua State + * + * Lua Stack + * 1 TfxBuffer userdata + * 2 target x coordinate for copy + * 3 target y coordinate for copy + * 4 source x coordinate + * 5 source y coordinate + * 6 width of rectangle to copy + * 7 height of rectangle to copy + * + * Lua Returns: + * - + */ +static int tfx_bufCopyRegion(lua_State *L) +{ + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int tx = (int) luaL_checkinteger(L, 2) - top_left_coord; + int ty = (int) luaL_checkinteger(L, 3) - top_left_coord; + int x = (int) luaL_checkinteger(L, 4) - top_left_coord; + int y = (int) luaL_checkinteger(L, 5) - top_left_coord; + int w = (int) luaL_checkinteger(L, 6); + int h = (int) luaL_checkinteger(L, 7); + + tbu_copybufferregion(tfxbuf->buf, tfxbuf->w, tfxbuf->h, tx, ty, x, y, w, h); + return 0; +} + +/* tfx_bufScrollRegion + * + * scrolls a rectangular region of a buffer + * + * Arguments: + * L Lua State + * + * Lua Stack + * 1 TfxBuffer userdata + * 2 x coordinate of rectangle to scroll + * 3 y coordinate of rectangle to scroll + * 4 width of rectangle to scroll + * 5 height of rectangle to scroll + * 6 (opt) x scroll direction (-1, 0, 1), defaults to 0 + * 7 (opt) y scroll direction (-1, 0, 1), defaults to 0 + * either + * 8 TfxCell + * or + * 8 (opt) char, defaults to ' ' + * 9 (opt) foreground attributes, defaults to buffer default foreground + * 10 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * - + * + * Note: + * arguments 6 and 7 (scroll directions) are ints, but only their sign is + * used. Scrolling is always by at most 1 cell. + */ +int tfx_bufScrollRegion(lua_State *L) +{ + if (!initialized) return 0; + + TfxCell cel, *celp = &cel; + + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + int x = (int) luaL_checkinteger(L, 2) - top_left_coord; + int y = (int) luaL_checkinteger(L, 3) - top_left_coord; + int w = (int) luaL_checkinteger(L, 4); + int h = (int) luaL_checkinteger(L, 5); + int sx = (int) luaL_optinteger(L, 6, 0); + int sy = (int) luaL_optinteger(L, 7, 0); + + if (tfx_isCell(L, 8)) { + maxargs(L, 8); + celp = tfx_toCell(L, 8); + } else { + maxargs(L, 10); + cel.ch = _tfx_optchar(L, 8, 0); + cel.fg = (uint16_t) luaL_optinteger(L, 9, default_fg) & 0xFFFF; + cel.bg = (uint16_t) luaL_optinteger(L, 10, default_bg) & 0xFFFF; + } + + tbu_scrollbufferregion(tfxbuf->buf, tfxbuf->w, tfxbuf->h, x, y, w, h, sx, sy, celp); + return 0; +} + +/* tfx_bufWidth + * + * returns the width of the buffer + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * + * Lua Returns: + * +1 width of buffer + */ +static int tfx_bufWidth(lua_State *L) +{ + maxargs(L, 1); + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + lua_pushinteger(L, tfxbuf->w); + return 1; +} + +/* tfx_bufHeight + * + * returns the height of the buffer + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * + * Lua Returns: + * +1 height of buffer + */ +static int tfx_bufHeight(lua_State *L) +{ + maxargs(L, 1); + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + lua_pushinteger(L, tfxbuf->h); + return 1; +} + +/* tfx_bufSize + * + * returns width and height of the buffer + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * + * Lua Returns: + * +1 width of buffer + * +2 height of buffer + */ +static int tfx_bufSize(lua_State *L) +{ + maxargs(L, 1); + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); + lua_pushinteger(L, tfxbuf->w); + lua_pushinteger(L, tfxbuf->h); + return 2; +} + +/*** termfx stuff ***/ + +/* tfx_init + * + * initialize TermFX + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 (opt) boolean value, if true, then all coordinates will be + * 0-based, otherwise they will be 1-based. + * + * Lua Returns: + * - + */ +static int tfx_init(lua_State *L) +{ + maxargs(L, 0); + default_fg = TB_WHITE; + default_bg = TB_BLACK; + + int status = tb_init(); + if (status < 0) { + switch(status) { + case TB_EUNSUPPORTED_TERMINAL: return luaL_error(L, "unsupported terminal"); + case TB_EFAILED_TO_OPEN_TTY: return luaL_error(L, "failed to open tty"); + case TB_EPIPE_TRAP_ERROR: return luaL_error(L, "sigpipe trap"); + default: return luaL_error(L, "unknown error"); + } + } else + initialized = 1; + + return 0; +} + +/* helper: shutdown termfx. Also used for atexit. + */ +static void _tfx_doShutdown(void) +{ + if (initialized) + tb_shutdown(); + initialized = 0; +} + +/* tfx_shutdown + * + * shutdown TermFX + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * - + */ +static int tfx_shutdown(lua_State *L) +{ + maxargs(L, 0); + _tfx_doShutdown(); + return 0; +} + +/* tfx_width + * + * returns width of terminal + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * +1 width of terminal, or nil if the terminal is not initialized. + */ +static int tfx_width(lua_State *L) +{ + maxargs(L, 0); + int w = tb_width(); + if (w >= 0) + lua_pushinteger(L, w); + else + lua_pushnil(L); + return 1; +} + +/* tfx_height + * + * returns height of terminal + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * +1 height of terminal, or nil if the terminal is not initialized. + */ +static int tfx_height(lua_State *L) +{ + maxargs(L, 0); + int h = tb_height(); + if (h >= 0) + lua_pushinteger(L, h); + else + lua_pushnil(L); + return 1; +} + +/* tfx_size + * + * returns width and height of terminal + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * +1 width of terminal, or nil if the terminal is not initialized. + * +2 height of terminal, or nil if the terminal is not initialized. + */ +static int tfx_size(lua_State *L) +{ + maxargs(L, 0); + int w = tb_width(), h = tb_height(); + if (w >= 0) { + lua_pushinteger(L, w); + lua_pushinteger(L, h); + } else { + lua_pushnil(L); + lua_pushnil(L); + } + return 2; +} + +/* tfx_attributes + * + * sets and gets default foreground and background attributes for terminal + * operations + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 (opt) foreground attribute + * 2 (opt) background attribute + * + * Lua Returns: + * +1 default foreground attribute, or nothing if terminal is not initialized + * +2 default background attribute, or nothing if terminal is not initialized + */ +static int tfx_attributes(lua_State *L) +{ + maxargs(L, 2); + if (!initialized) return 0; + if (lua_gettop(L) > 0) { + default_fg = (uint16_t) luaL_optinteger(L, 1, default_fg) & 0xFFFF; + default_bg = (uint16_t) luaL_optinteger(L, 2, default_bg) & 0xFFFF; + tb_set_clear_attributes(default_fg, default_bg); + } + lua_pushinteger(L, default_fg); + lua_pushinteger(L, default_bg); + return 2; +} + +/* tfx_clear + * + * clears the terminal to the default foreground and background attributes. + * If the optional foreground and background attribute arguments are + * given, these will be set using tfx_bufSetAttributes before clearing. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 TfxBuffer userdata + * 2 (opt) foreground attributes, defaults to buffer default foreground + * 3 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * - + */ +static int tfx_clear(lua_State *L) +{ + if (lua_gettop(L) != 0) + tfx_attributes(L); + if (initialized) tb_clear(); + return 0; +} + +/* tfx_present + * + * update the terminal with the data from the back buffer + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * - + */ +static int tfx_present(lua_State *L) +{ + maxargs(L, 0); + if (initialized) tb_present(); + return 0; +} + +/* tfx_setCursor + * + * set cursor position + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 x coordinate + * 2 y coordinate + * + * Lua Returns: + * - + */ +static int tfx_setCursor(lua_State *L) +{ + maxargs(L, 2); + int x = (int) luaL_checkinteger(L, 1) - top_left_coord; + int y = (int) luaL_checkinteger(L, 2) - top_left_coord; + if (initialized) tb_set_cursor(x, y); + return 0; +} + +/* tfx_hideCursor + * + * hide cursor + * + * Arguments: + * L Lua State + * + * Lua Stack: + * - + * + * Lua Returns: + * - + */ +static int tfx_hideCursor(lua_State *L) +{ + maxargs(L, 0); + if (initialized) tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR); + return 0; +} + +/* tfx_setcell + * + * sets a cell on the terminal. Either uses the default values for foreground + * and background, or the values passed on the lua stack. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 x coordinate of cell + * 2 y coordinate of cell + * either + * 3 TfxCell + * or + * 3 (opt) char, defaults to ' ' + * 4 (opt) foreground attributes, defaults to buffer default foreground + * 5 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * - + */ +static int tfx_setCell(lua_State *L) +{ + int x = (int) luaL_checkinteger(L, 1) - top_left_coord; + int y = (int) luaL_checkinteger(L, 2) - top_left_coord; + if (tfx_isCell(L, 3)) { + maxargs(L, 3); + const TfxCell *tfxcell = tfx_checkCell(L, 3); + if (initialized) tb_put_cell(x, y, tfxcell); + } else { + maxargs(L, 5); + uint32_t ch = _tfx_optchar(L, 3, ' '); + uint16_t fg = (uint16_t) luaL_optinteger(L, 4, default_fg) & 0xFFFF; + uint16_t bg = (uint16_t) luaL_optinteger(L, 5, default_bg) & 0xFFFF; + if (ch < ' ') ch = ' '; + if (initialized) tb_change_cell(x, y, ch, fg, bg); + } + return 0; +} + +/* tfx_getCell + * + * gets data from a cell on the terminal + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 x coordinate of cell + * 2 y coordinate of cell + * + * Lua Returns: + * a TfxCell with the data from the read cell + */ +int tfx_getCell(lua_State *L) +{ + int x = (int) luaL_checkinteger(L, 1) - top_left_coord; + int y = (int) luaL_checkinteger(L, 2) - top_left_coord; + + if (x < 0 || x >= tb_width() || y < 0 || y >= tb_height()) { + lua_pushnil(L); + return 1; + } + TfxCell *tfxcell = tfx_pushCell(L); + *tfxcell = CELL(tb_cell_buffer(), tb_width(), x, y); + + return 1; +} + +/* tfx_blit + * + * blits the contents of a buffer to the terminal. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 x coordinate of cell + * 2 y coordinate of cell + * 3 TfxBuffer + * + * Lua Returns: + * - + */ +static int tfx_blit(lua_State *L) +{ + maxargs(L, 3); + int x = (int) luaL_checkinteger(L, 1) - top_left_coord; + int y = (int) luaL_checkinteger(L, 2) - top_left_coord; + TfxBuffer *tfxbuf = tfx_checkBuffer(L, 3); + if (initialized) tbu_blit(x, y, tfxbuf->buf, tfxbuf->w, tfxbuf->h); + return 0; +} + +/* tfx_rect + * + * draws a rectangle. Either uses the default values for foreground and + * background, or the values passed on the lua stack. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 x coordinate of rect + * 2 y coordinate of rect + * 3 width of rect + * 4 height of rect + * either + * 5 TfxCell + * or + * 5 (opt) char, defaults to ' ' + * 6 (opt) foreground attributes, defaults to buffer default foreground + * 7 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * false if the rectangle was entirely outside of the terminal, true if not + */ +static int tfx_rect(lua_State *L) +{ + if (!initialized) return 0; + + TfxCell cel, *celp = &cel; + + int x = (int) luaL_checkinteger(L, 1) - top_left_coord; + int y = (int) luaL_checkinteger(L, 2) - top_left_coord; + int w = (int) luaL_checkinteger(L, 3); + int h = (int) luaL_checkinteger(L, 4); + if (tfx_isCell(L, 5)) { + maxargs(L, 5); + celp = tfx_toCell(L, 5); + } else { + maxargs(L, 7); + cel.ch = _tfx_optchar(L, 5, 0); + cel.fg = (uint16_t) luaL_optinteger(L, 6, default_fg) & 0xFFFF; + cel.bg = (uint16_t) luaL_optinteger(L, 7, default_bg) & 0xFFFF; + } + + tbu_fillregion(x, y, w, h, celp); + return 0; +} + +/* tfx_copyRegion + * + * copies a rectangular region to another place + * + * Arguments: + * L Lua State + * + * Lua Stack + * 1 target x coordinate for copy + * 2 target y coordinate for copy + * 3 source x coordinate + * 4 source y coordinate + * 5 width of rectangle to copy + * 6 height of rectangle to copy + * + * Lua Returns: + * - + */ +int tfx_copyRegion(lua_State *L) +{ + if (!initialized) return 0; + + int tx = (int) luaL_checkinteger(L, 1) - top_left_coord; + int ty = (int) luaL_checkinteger(L, 2) - top_left_coord; + int x = (int) luaL_checkinteger(L, 3) - top_left_coord; + int y = (int) luaL_checkinteger(L, 4) - top_left_coord; + int w = (int) luaL_checkinteger(L, 5); + int h = (int) luaL_checkinteger(L, 6); + + tbu_copyregion(tx, ty, x, y, w, h); + return 0; +} + +/* tfx_scrollRegion + * + * scrolls a rectangular region + * + * Arguments: + * L Lua State + * + * Lua Stack + * 1 x coordinate of rectangle to scroll + * 2 y coordinate of rectangle to scroll + * 3 width of rectangle to scroll + * 4 height of rectangle to scroll + * 5 (opt) x scroll direction (-1, 0, 1), defaults to 0 + * 6 (opt) y scroll direction (-1, 0, 1), defaults to 0 + * either + * 7 TfxCell + * or + * 7 (opt) char, defaults to ' ' + * 8 (opt) foreground attributes, defaults to buffer default foreground + * 9 (opt) background attributes, defaults to buffer default background + * + * Lua Returns: + * - + * + * Note: + * arguments 5 and 6 (scroll directions) are ints, but only their sign is + * used. Scrolling is always by at most 1 cell. + */ +int tfx_scrollRegion(lua_State *L) +{ + if (!initialized) return 0; + + TfxCell cel, *celp = &cel; + + int x = (int) luaL_checkinteger(L, 1) - top_left_coord; + int y = (int) luaL_checkinteger(L, 2) - top_left_coord; + int w = (int) luaL_checkinteger(L, 3); + int h = (int) luaL_checkinteger(L, 4); + int sx = (int) luaL_optinteger(L, 5, 0); + int sy = (int) luaL_optinteger(L, 6, 0); + + if (tfx_isCell(L, 7)) { + maxargs(L, 7); + celp = tfx_toCell(L, 7); + } else { + maxargs(L, 9); + cel.ch = _tfx_optchar(L, 7, 0); + cel.fg = (uint16_t) luaL_optinteger(L, 8, default_fg) & 0xFFFF; + cel.bg = (uint16_t) luaL_optinteger(L, 9, default_bg) & 0xFFFF; + } + + tbu_scrollregion(x, y, w, h, sx, sy, celp); + return 0; +} + +/* tfx_printAt + * + * prints some text to the top left position at x, y. Is used as both the + * function termfx.printat and the TfxBuffer printat method, depending on + * the first argument. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * (1 TfxBuffer) + * +1 x coordinate of text + * +2 y coordinate of text + * +3 text (string or table of chars) + * +4 (opt) maximum width to print + * + * Lua Returns: + * - + */ +static int tfx_printAt(lua_State *L) +{ + if (!initialized) return 0; + + TfxBuffer *tfxbuf = NULL; + int fw, start = 1; + uint16_t dfg = default_fg, dbg = default_bg; + if (tfx_isBuffer(L, 1)) { + tfxbuf = tfx_checkBuffer(L, 1); + fw = tfxbuf->w; + dfg = tfxbuf->fg; + dbg = tfxbuf->bg; + start = 2; + } else { + fw = tb_width(); + } + + int x = (int) luaL_checkinteger(L, start) - top_left_coord; + int y = (int) luaL_checkinteger(L, start + 1) - top_left_coord; + int pw = -1; + if (lua_gettop(L) == start + 3) + pw = (int) luaL_checkinteger(L, start + 3); + + if (lua_type(L, start + 2) == LUA_TTABLE) { + int i = 1; + int w = 0; + if (pw < 0) { + pw = lua_rawlen(L, start + 2); + } + lua_rawgeti(L, start + 2, i); + while (!lua_isnil(L, -1) && w < pw && x + w < fw) { + TfxCell *c = tfx_toCell(L, -1); + if (c) { + if (tfxbuf) + _tfx_bufChangeCell(tfxbuf, x + w, y, c); + else + tb_put_cell(x + w, y, c); + } + ++w; + ++i; + lua_rawgeti(L, start + 2, i); + } + } else if (!lua_isnil(L, start + 2)) { + const char* str = luaL_checkstring(L, start + 2); + int isutf8 = mini_utf8_check_encoding(str) == 0; + int w = 0; + if (pw < 0) { + pw = isutf8 ? mini_utf8_strlen(str) : strlen(str); + } + struct tb_cell c; + c.fg = dfg; + c.bg = dbg; + c.ch = isutf8 ? mini_utf8_decode(&str) : *str++; + if (c.ch < ' ' && c.ch > 0) c.ch = ' '; + + for (w = 0; c.ch && (w < pw) && (x + w < fw); ++w) { + if (tfxbuf) + _tfx_bufChangeCell(tfxbuf, x + w, y, &c); + else + tb_put_cell(x + w, y, &c); + c.ch = isutf8 ? mini_utf8_decode(&str) : *str++; + if (c.ch < ' ' && c.ch > 0) c.ch = ' '; + } + } + return 0; +} + +/* tfx_inputMode + * + * set or query input mode + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 input mode, or nil to query + * + * Lua Returns: + * +1 current input mode + */ +static int tfx_inputMode(lua_State *L) +{ + maxargs(L, 1); + int mode = TB_INPUT_CURRENT; + if (!lua_isnoneornil(L, 1)) + mode = (int) luaL_checkinteger(L, 1); + int res = tb_select_input_mode(mode); + lua_pushinteger(L, res); + return 1; +} + +/* tfx_outputMode + * + * set or query output mode + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 output mode, or nil to query + * + * Lua Returns: + * +1 current output mode + */ +static int tfx_outputMode(lua_State *L) +{ + maxargs(L, 1); + int mode = TB_OUTPUT_CURRENT; + if (!lua_isnoneornil(L, 1)) + mode = (int) luaL_checkinteger(L, 1); + int res = tb_select_output_mode(mode); + lua_pushinteger(L, res); + return 1; +} + +/* tfx_pollEvent + * + * polls next event. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 (opt) timeout in ms, waits forever for the next event if not set + * + * Lua Returns: + * +1 a table with the event data + */ +static int tfx_pollEvent(lua_State *L) +{ + maxargs(L, 1); + struct tb_event evt; + int eno = 0; + uint32_t started = _tfx_getMsTimer(); + if (lua_gettop(L) == 1 && !lua_isnil(L, 1)) { + uint32_t timeout = (int) luaL_checkinteger(L, 1); + eno = initialized ? tb_peek_event(&evt, timeout) : 0; + } else + eno = initialized ? tb_poll_event(&evt) : 0; + + if (eno > 0) { + lua_newtable(L); + + lua_pushliteral(L, "type"); + switch (evt.type) { + case TB_EVENT_KEY: lua_pushliteral(L, "key"); break; + case TB_EVENT_RESIZE: lua_pushliteral(L, "resize"); break; +#ifdef TB_EVENT_MOUSE + case TB_EVENT_MOUSE: lua_pushliteral(L, "mouse"); break; +#endif + default: lua_pushliteral(L, "unknown"); + } + lua_rawset(L, -3); + + lua_pushliteral(L, "elapsed"); + lua_pushinteger(L, _tfx_getMsTimer() - started); + lua_rawset(L, -3); + + if (evt.type == TB_EVENT_KEY) { + lua_pushliteral(L, "mod"); + switch (evt.mod) { + case TB_MOD_ALT: lua_pushliteral(L, "ALT"); break; + default: lua_pushnil(L); + } + lua_rawset(L, -3); + + lua_pushliteral(L, "key"); + if (evt.ch == 0) { + lua_pushinteger(L, evt.key); + } else { + lua_pushnil(L); + } + lua_rawset(L, -3); + + lua_pushliteral(L, "ch"); + if (evt.ch != 0) { + lua_pushinteger(L, evt.ch); + } else { + lua_pushnil(L); + } + lua_rawset(L, -3); + + lua_pushliteral(L, "char"); + char c[10]; + memset(c, 0, 10); + if (evt.ch < 0x7F) + c[0] = evt.ch; + else + mini_utf8_encode(evt.ch, c, 10); + lua_pushstring(L, c); + lua_rawset(L, -3); + + } else if (evt.type == TB_EVENT_RESIZE) { + lua_pushliteral(L, "w"); + lua_pushinteger(L, evt.w); + lua_rawset(L, -3); + + lua_pushliteral(L, "h"); + lua_pushinteger(L, evt.h); + lua_rawset(L, -3); +#ifdef TB_EVENT_MOUSE + } else if (evt.type == TB_EVENT_MOUSE) { + lua_pushliteral(L, "x"); + lua_pushinteger(L, evt.x + top_left_coord); + lua_rawset(L, -3); + + lua_pushliteral(L, "y"); + lua_pushinteger(L, evt.y + top_left_coord); + lua_rawset(L, -3); + + lua_pushliteral(L, "key"); + lua_pushinteger(L, evt.key); + lua_rawset(L, -3); +#endif + } + } else if (eno == 0) { + lua_pushnil(L); + } else { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + return 1; +} + +/* methods for the TfxBuffer userdata + */ +static const struct luaL_Reg ltfxbuf_methods [] = { + {"attributes", tfx_bufAttributes}, + {"clear", tfx_bufClear}, + {"setcell", tfx_bufSetcell}, + {"getcell", tfx_bufGetCell}, + {"blit", tfx_bufBlit}, + {"rect", tfx_bufRect}, + {"copyregion", tfx_bufCopyRegion}, + {"scrollregion", tfx_bufScrollRegion}, + {"printat", tfx_printAt}, + {"width", tfx_bufWidth}, + {"height", tfx_bufHeight}, + {"size", tfx_bufSize}, + + {NULL, NULL} +}; + +/* TermFX function list + */ +static const struct luaL_Reg ltermfx [] ={ + {"init", tfx_init}, + {"shutdown", tfx_shutdown}, + {"width", tfx_width}, + {"height", tfx_height}, + {"size", tfx_size}, + {"clear", tfx_clear}, + {"attributes", tfx_attributes}, + {"present", tfx_present}, + {"setcursor", tfx_setCursor}, + {"hidecursor", tfx_hideCursor}, + {"newcell", tfx_newCell}, + {"setcell", tfx_setCell}, + {"getcell", tfx_getCell}, + {"newbuffer", tfx_newBuffer}, + {"blit", tfx_blit}, + {"rect", tfx_rect}, + {"copyregion", tfx_copyRegion}, + {"scrollregion", tfx_scrollRegion}, + {"printat", tfx_printAt}, + {"inputmode", tfx_inputMode}, + {"outputmode", tfx_outputMode}, + {"pollevent", tfx_pollEvent}, + + {NULL, NULL} +}; + + +/* helper: __index metamethod for the table containing the color values + * for the predefined color names. As the values for these names are not + * constant across the output modes, some tweaking is needed. + */ +static int tfx__indexColor(lua_State *L) +{ + const char* what = luaL_checkstring(L, 2); + int omode = tb_select_output_mode(TB_OUTPUT_CURRENT); + int col = -1; + + if (!strcmp("DEFAULT", what)) col = TB_DEFAULT; + else if (!strcmp("BLACK", what)) col = TB_BLACK; + else if (!strcmp("RED", what)) col = TB_RED; + else if (!strcmp("GREEN", what)) col = TB_GREEN; + else if (!strcmp("YELLOW", what)) col = TB_YELLOW; + else if (!strcmp("BLUE", what)) col = TB_BLUE; + else if (!strcmp("MAGENTA", what)) col = TB_MAGENTA; + else if (!strcmp("CYAN", what)) col = TB_CYAN; + else if (!strcmp("WHITE", what)) col = TB_WHITE; + + switch (omode) { + case TB_OUTPUT_256: + col -= 1; + break; + case TB_OUTPUT_GRAYSCALE: + col = (col - 1) * 3; + if (col > 6) col += 1; + if (col > 15) col += 1; + break; + case TB_OUTPUT_216: + switch (col) { + case TB_BLACK: col = 0; break; + case TB_RED: col = 72; break; + case TB_GREEN: col = 12; break; + case TB_YELLOW: col = 114; break; + case TB_BLUE: col = 2; break; + case TB_MAGENTA: col = 74; break; + case TB_CYAN: col = 14; break; + case TB_WHITE: col = 129; break; + default:; + } + break; + default:; + } + + if (col < 0) + lua_pushnil(L); + else + lua_pushinteger(L, col); + + return 1; +} + +/* add constants */ +#define ADDCONST(n, v) \ + lua_pushliteral(L, n); \ + lua_pushinteger(L, v); \ + lua_rawset(L, -3) + +static void tfx_addconstants(lua_State *L) +{ + lua_pushliteral(L, "key"); + lua_newtable(L); + ADDCONST("F1", TB_KEY_F1); + ADDCONST("F2", TB_KEY_F2); + ADDCONST("F3", TB_KEY_F3); + ADDCONST("F4", TB_KEY_F4); + ADDCONST("F5", TB_KEY_F5); + ADDCONST("F6", TB_KEY_F6); + ADDCONST("F7", TB_KEY_F7); + ADDCONST("F8", TB_KEY_F8); + ADDCONST("F9", TB_KEY_F9); + ADDCONST("F10", TB_KEY_F10); + ADDCONST("F11", TB_KEY_F11); + ADDCONST("F12", TB_KEY_F12); + ADDCONST("INSERT", TB_KEY_INSERT); + ADDCONST("DELETE", TB_KEY_DELETE); + ADDCONST("HOME", TB_KEY_HOME); + ADDCONST("END", TB_KEY_END); + ADDCONST("PGUP", TB_KEY_PGUP); + ADDCONST("PGDN", TB_KEY_PGDN); + ADDCONST("ARROW_UP", TB_KEY_ARROW_UP); + ADDCONST("ARROW_DOWN", TB_KEY_ARROW_DOWN); + ADDCONST("ARROW_LEFT", TB_KEY_ARROW_LEFT); + ADDCONST("ARROW_RIGHT", TB_KEY_ARROW_RIGHT); + +#ifdef TB_INPUT_MOUSE + ADDCONST("MOUSE_LEFT", TB_KEY_MOUSE_LEFT); + ADDCONST("MOUSE_RIGHT", TB_KEY_MOUSE_RIGHT); + ADDCONST("MOUSE_MIDDLE", TB_KEY_MOUSE_MIDDLE); + ADDCONST("MOUSE_RELEASE", TB_KEY_MOUSE_RELEASE); + ADDCONST("MOUSE_WHEEL_UP", TB_KEY_MOUSE_WHEEL_UP); + ADDCONST("MOUSE_WHEEL_DOWN", TB_KEY_MOUSE_WHEEL_DOWN); +#endif + + ADDCONST("CTRL_TILDE", TB_KEY_CTRL_TILDE); + ADDCONST("CTRL_2", TB_KEY_CTRL_2); + ADDCONST("CTRL_A", TB_KEY_CTRL_A); + ADDCONST("CTRL_B", TB_KEY_CTRL_B); + ADDCONST("CTRL_C", TB_KEY_CTRL_C); + ADDCONST("CTRL_D", TB_KEY_CTRL_D); + ADDCONST("CTRL_E", TB_KEY_CTRL_E); + ADDCONST("CTRL_F", TB_KEY_CTRL_F); + ADDCONST("CTRL_G", TB_KEY_CTRL_G); + ADDCONST("BACKSPACE", TB_KEY_BACKSPACE); + ADDCONST("CTRL_H", TB_KEY_CTRL_H); + ADDCONST("TAB", TB_KEY_TAB); + ADDCONST("CTRL_I", TB_KEY_CTRL_I); + ADDCONST("CTRL_J", TB_KEY_CTRL_J); + ADDCONST("CTRL_K", TB_KEY_CTRL_K); + ADDCONST("CTRL_L", TB_KEY_CTRL_L); + ADDCONST("ENTER", TB_KEY_ENTER); + ADDCONST("CTRL_M", TB_KEY_CTRL_M); + ADDCONST("CTRL_N", TB_KEY_CTRL_N); + ADDCONST("CTRL_O", TB_KEY_CTRL_O); + ADDCONST("CTRL_P", TB_KEY_CTRL_P); + ADDCONST("CTRL_Q", TB_KEY_CTRL_Q); + ADDCONST("CTRL_R", TB_KEY_CTRL_R); + ADDCONST("CTRL_S", TB_KEY_CTRL_S); + ADDCONST("CTRL_T", TB_KEY_CTRL_T); + ADDCONST("CTRL_U", TB_KEY_CTRL_U); + ADDCONST("CTRL_V", TB_KEY_CTRL_V); + ADDCONST("CTRL_W", TB_KEY_CTRL_W); + ADDCONST("CTRL_X", TB_KEY_CTRL_X); + ADDCONST("CTRL_Y", TB_KEY_CTRL_Y); + ADDCONST("CTRL_Z", TB_KEY_CTRL_Z); + ADDCONST("ESC", TB_KEY_ESC); + ADDCONST("CTRL_LSQ_BRACKET", TB_KEY_CTRL_LSQ_BRACKET); + ADDCONST("CTRL_3", TB_KEY_CTRL_3); + ADDCONST("CTRL_4", TB_KEY_CTRL_4); + ADDCONST("CTRL_BACKSLASH", TB_KEY_CTRL_BACKSLASH); + ADDCONST("CTRL_5", TB_KEY_CTRL_5); + ADDCONST("CTRL_RSQ_BRACKET", TB_KEY_CTRL_RSQ_BRACKET); + ADDCONST("CTRL_6", TB_KEY_CTRL_6); + ADDCONST("CTRL_7", TB_KEY_CTRL_7); + ADDCONST("CTRL_SLASH", TB_KEY_CTRL_SLASH); + ADDCONST("CTRL_UNDERSCORE", TB_KEY_CTRL_UNDERSCORE); + ADDCONST("SPACE", TB_KEY_SPACE); + ADDCONST("BACKSPACE2", TB_KEY_BACKSPACE2); + ADDCONST("CTRL_8", TB_KEY_CTRL_8); + lua_rawset(L, -3); + + lua_pushliteral(L, "color"); + lua_newtable(L); + lua_newtable(L); + lua_pushliteral(L, "__index"); + lua_pushcfunction(L, tfx__indexColor); + lua_rawset(L, -3); + lua_setmetatable(L, -2); + lua_rawset(L, -3); + + lua_pushliteral(L, "format"); + lua_newtable(L); + ADDCONST("BOLD", TB_BOLD); + ADDCONST("UNDERLINE", TB_UNDERLINE); + ADDCONST("REVERSE", TB_REVERSE); + lua_rawset(L, -3); + + lua_pushliteral(L, "input"); + lua_newtable(L); + ADDCONST("ESC", TB_INPUT_ESC); + ADDCONST("ALT", TB_INPUT_ALT); +#ifdef TB_INPUT_MOUSE + ADDCONST("MOUSE", TB_INPUT_MOUSE); +#endif + lua_rawset(L, -3); + + lua_pushliteral(L, "output"); + lua_newtable(L); + ADDCONST("NORMAL", TB_OUTPUT_NORMAL); + ADDCONST("COL256", TB_OUTPUT_256); + ADDCONST("COL216", TB_OUTPUT_216); + ADDCONST("GRAYSCALE", TB_OUTPUT_GRAYSCALE); + ADDCONST("GREYSCALE", TB_OUTPUT_GRAYSCALE); + lua_rawset(L, -3); +} + +/* luaopen_ltermbox + * + * open and initialize this library + */ +int luaopen_termfx(lua_State *L) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + mstimer_tm0 = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + luaL_newlib(L, ltermfx); + tfx_color_init(L); + tfx_addconstants(L); + + lua_pushliteral(L, "_VERSION"); + lua_pushliteral(L, _VERSION); + lua_rawset(L, -3); + + luaL_newmetatable(L, TFXCELL); + luaL_setfuncs(L, tfx_CellMeta, 0); + lua_pop(L, 1); + + luaL_newmetatable(L, TFXBUFFER); + luaL_setfuncs(L, tfx_BufferMeta, 0); + lua_pushliteral(L, "__index"); + luaL_newlib(L, ltfxbuf_methods); + lua_rawset(L, -3); + lua_pop(L, 1); + + /* shutdown termbox or suffer the consequences */ + atexit(_tfx_doShutdown); + + return 1; +} diff --git a/framework/lualib-src/lua-termfx/termfx.h b/framework/lualib-src/lua-termfx/termfx.h new file mode 100755 index 0000000..6e18307 --- /dev/null +++ b/framework/lualib-src/lua-termfx/termfx.h @@ -0,0 +1,25 @@ +/* termfx.h + * + * provide simple terminal interface for lua + * + * Gunnar Zötl , 2014-2015 + * Released under the terms of the MIT license. See file LICENSE for details. + */ + +#define _VERSION "0.7.1" + +#define TFXCELL "TfxCell" +#define TFXBUFFER "TfxBuffer" +#define TOSTRING_BUFSIZ 64 + +#if LUA_VERSION_NUM == 501 +#define luaL_newlib(L,funcs) lua_newtable(L); luaL_register(L, NULL, funcs) +#define luaL_setfuncs(L,funcs,x) luaL_register(L, NULL, funcs) +#define lua_rawlen(L, i) lua_objlen(L, i) +#endif + +#define maxargs(L, n) if (lua_gettop(L) > (n)) { return luaL_error(L, "invalid number of arguments."); } + +/* from termfx_color.c */ +extern void tfx_color_init(lua_State *L); + diff --git a/framework/lualib-src/lua-termfx/termfx_color.c b/framework/lualib-src/lua-termfx/termfx_color.c new file mode 100755 index 0000000..ac65080 --- /dev/null +++ b/framework/lualib-src/lua-termfx/termfx_color.c @@ -0,0 +1,219 @@ +/* termfx_color.c + * + * provide simple terminal interface for lua + * + * Gunnar Zötl , 2014-2015 + * Released under the terms of the MIT license. See file LICENSE for details. + */ + +#include "lua.h" +#include "lauxlib.h" + +#include "termbox.h" +#include "termfx.h" + +static const char* xterm_color_data[256] = { + "#000000", "#800000", "#008000", "#808000", "#000080", "#800080", "#008080", "#c0c0c0", + "#808080", "#ff0000", "#00ff00", "#ffff00", "#0000ff", "#ff00ff", "#00ffff", "#ffffff", + + "#000000", "#00005f", "#000087", "#0000af", "#0000d7", "#0000ff", + "#005f00", "#005f5f", "#005f87", "#005faf", "#005fd7", "#005fff", + "#008700", "#00875f", "#008787", "#0087af", "#0087d7", "#0087ff", + "#00af00", "#00af5f", "#00af87", "#00afaf", "#00afd7", "#00afff", + "#00d700", "#00d75f", "#00d787", "#00d7af", "#00d7d7", "#00d7ff", + "#00ff00", "#00ff5f", "#00ff87", "#00ffaf", "#00ffd7", "#00ffff", + "#5f0000", "#5f005f", "#5f0087", "#5f00af", "#5f00d7", "#5f00ff", + "#5f5f00", "#5f5f5f", "#5f5f87", "#5f5faf", "#5f5fd7", "#5f5fff", + "#5f8700", "#5f875f", "#5f8787", "#5f87af", "#5f87d7", "#5f87ff", + "#5faf00", "#5faf5f", "#5faf87", "#5fafaf", "#5fafd7", "#5fafff", + "#5fd700", "#5fd75f", "#5fd787", "#5fd7af", "#5fd7d7", "#5fd7ff", + "#5fff00", "#5fff5f", "#5fff87", "#5fffaf", "#5fffd7", "#5fffff", + "#870000", "#87005f", "#870087", "#8700af", "#8700d7", "#8700ff", + "#875f00", "#875f5f", "#875f87", "#875faf", "#875fd7", "#875fff", + "#878700", "#87875f", "#878787", "#8787af", "#8787d7", "#8787ff", + "#87af00", "#87af5f", "#87af87", "#87afaf", "#87afd7", "#87afff", + "#87d700", "#87d75f", "#87d787", "#87d7af", "#87d7d7", "#87d7ff", + "#87ff00", "#87ff5f", "#87ff87", "#87ffaf", "#87ffd7", "#87ffff", + "#af0000", "#af005f", "#af0087", "#af00af", "#af00d7", "#af00ff", + "#af5f00", "#af5f5f", "#af5f87", "#af5faf", "#af5fd7", "#af5fff", + "#af8700", "#af875f", "#af8787", "#af87af", "#af87d7", "#af87ff", + "#afaf00", "#afaf5f", "#afaf87", "#afafaf", "#afafd7", "#afafff", + "#afd700", "#afd75f", "#afd787", "#afd7af", "#afd7d7", "#afd7ff", + "#afff00", "#afff5f", "#afff87", "#afffaf", "#afffd7", "#afffff", + "#d70000", "#d7005f", "#d70087", "#d700af", "#d700d7", "#d700ff", + "#d75f00", "#d75f5f", "#d75f87", "#d75faf", "#d75fd7", "#d75fff", + "#d78700", "#d7875f", "#d78787", "#d787af", "#d787d7", "#d787ff", + "#d7af00", "#d7af5f", "#d7af87", "#d7afaf", "#d7afd7", "#d7afff", + "#d7d700", "#d7d75f", "#d7d787", "#d7d7af", "#d7d7d7", "#d7d7ff", + "#d7ff00", "#d7ff5f", "#d7ff87", "#d7ffaf", "#d7ffd7", "#d7ffff", + "#ff0000", "#ff005f", "#ff0087", "#ff00af", "#ff00d7", "#ff00ff", + "#ff5f00", "#ff5f5f", "#ff5f87", "#ff5faf", "#ff5fd7", "#ff5fff", + "#ff8700", "#ff875f", "#ff8787", "#ff87af", "#ff87d7", "#ff87ff", + "#ffaf00", "#ffaf5f", "#ffaf87", "#ffafaf", "#ffafd7", "#ffafff", + "#ffd700", "#ffd75f", "#ffd787", "#ffd7af", "#ffd7d7", "#ffd7ff", + "#ffff00", "#ffff5f", "#ffff87", "#ffffaf", "#ffffd7", "#ffffff", + + "#080808", "#121212", "#1c1c1c", "#262626", "#303030", "#3a3a3a", + "#444444", "#4e4e4e", "#585858", "#626262", "#6c6c6c", "#767676", + "#808080", "#8a8a8a", "#949494", "#9e9e9e", "#a8a8a8", "#b2b2b2", + "#bcbcbc", "#c6c6c6", "#d0d0d0", "#dadada", "#e4e4e4", "#eeeeee" +}; + +/* tfx_rgb2color + * + * maps r, g, b values in the range 0..5 to an xterm color number. Works + * only in COL216 and COL256 modes. As can be seen by the table above, + * the 216 colors are arranged such that the color for any rgb triplet + * can be found at 16 + (r * 6 + g) * 6 + b, for 0 <= r, g, b <= 5 + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 red value + * 2 green value + * 3 blue value + * + * Lua Returns: + * +1 the color number, or nil on error. + */ +static int tfx_rgb2color(lua_State *L) +{ + maxargs(L, 3); + unsigned int r = luaL_checkinteger(L, 1); + unsigned int g = luaL_checkinteger(L, 2); + unsigned int b = luaL_checkinteger(L, 3); + + if (r > 5 || g > 5 || b > 5) { + lua_pushnil(L); + return 1; + } + + int omode = tb_select_output_mode(TB_OUTPUT_CURRENT); + if (omode == TB_OUTPUT_256 || omode == TB_OUTPUT_216) { + int col = (r * 6 + g) * 6 + b; + if (omode == TB_OUTPUT_256) col += 16; + lua_pushinteger(L, col); + } else { + lua_pushnil(L); + } + + return 1; +} + +/* tfx_rgb2color + * + * maps a grey value in the range 0..25 to an xterm color number. Works + * only in GREYSCALE and COL256 modes. The greys are in one consecutive + * block, starting from 232, except for #000000 and #ffffff, which are + * only available in COL256 mode. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 grey value + * + * Lua Returns: + * +1 the color number, or nil on error. + */ +static int tfx_grey2color(lua_State *L) +{ + maxargs(L, 1); + unsigned int v = luaL_checkinteger(L, 1); + if (v > 25) { + lua_pushnil(L); + return 1; + } + + int omode = tb_select_output_mode(TB_OUTPUT_CURRENT); + if (omode == TB_OUTPUT_256 || omode == TB_OUTPUT_GRAYSCALE) { + int col; + if (omode == TB_OUTPUT_GRAYSCALE) { + if (v < 1) + v = 1; + else if (v > 24) + v = 24; + } + if (v == 0) + col = 16; + else if (v == 25) + col = 231; + else + col = 231 + v; + if (omode == TB_OUTPUT_GRAYSCALE) + col -= 232; + lua_pushinteger(L, col); + } else { + lua_pushnil(L); + } + return 1; +} + +/* tfx_colorinfo + * + * finds the color string from a xterm color number from the above table, + * and also returns its r, g, b values. + * + * Arguments: + * L Lua State + * + * Lua Stack: + * 1 color number + * + * Lua Returns: + * +1 color string "#XXXXXX", or nil on error. + * +2 r value or nothing + * +3 g value or nothing + * +4 b value or nothing + */ +static int tfx_colorinfo(lua_State *L) +{ + int omode = tb_select_output_mode(TB_OUTPUT_CURRENT); + unsigned int col = luaL_checkinteger(L, 1); + if ((omode == TB_OUTPUT_NORMAL && col >= 16) || col > 255) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + maxargs(L, 1); + if (omode == TB_OUTPUT_NORMAL) { + if (col > 0 && col <= 8) { + col -= 1; + } else { + col = 8; + } + } else if (omode == TB_OUTPUT_216) { + col += 16; + } else if (omode == TB_OUTPUT_GRAYSCALE) { + col += 232; + } + if (col < 256) { + unsigned int r, g, b; + lua_pushstring(L, xterm_color_data[col]); + sscanf(xterm_color_data[col], "#%02x%02x%02x", &r, &g, &b); + lua_pushinteger(L, r); + lua_pushinteger(L, g); + lua_pushinteger(L, b); + return 4; + } + lua_pushnil(L); + return 1; +} + +/* TermFX color handling function list + */ +static const struct luaL_Reg ltermfx_color [] ={ + {"rgb2color", tfx_rgb2color}, + {"grey2color", tfx_grey2color}, + {"colorinfo", tfx_colorinfo}, + + {NULL, NULL} +}; + +/* export color functions into termfx function table + */ +void tfx_color_init(lua_State *L) +{ + luaL_setfuncs(L, ltermfx_color, 0); +} diff --git a/framework/lualib-src/luasocket/.gitignore b/framework/lualib-src/luasocket/.gitignore new file mode 100644 index 0000000..9ed661c --- /dev/null +++ b/framework/lualib-src/luasocket/.gitignore @@ -0,0 +1,15 @@ +*.o +*.so +*.so.* +*.obj +*.lib +*.dll* +*.user +*.sdf +Debug +Release +*.manifest +*.swp +*.suo +x64 + diff --git a/framework/lualib-src/luasocket/README b/framework/lualib-src/luasocket/README new file mode 100644 index 0000000..cd8ee59 --- /dev/null +++ b/framework/lualib-src/luasocket/README @@ -0,0 +1,11 @@ +This is the LuaSocket 3.0-rc1. It has been tested on Windows 7, Mac OS X, +and Linux. + +Please use the project page at GitHub + + https://github.com/diegonehab/luasocket + +to file bug reports or propose changes. + +Have fun, +Diego Nehab. diff --git a/framework/lualib-src/luasocket/luasocket.sln b/framework/lualib-src/luasocket/luasocket.sln new file mode 100644 index 0000000..e4ba1fd --- /dev/null +++ b/framework/lualib-src/luasocket/luasocket.sln @@ -0,0 +1,35 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "socket", "socket.vcxproj", "{66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mime", "mime.vcxproj", "{128E8BD0-174A-48F0-8771-92B1E8D18713}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Debug|Win32.ActiveCfg = Debug|Win32 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Debug|Win32.Build.0 = Debug|Win32 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Debug|x64.ActiveCfg = Debug|x64 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Debug|x64.Build.0 = Debug|x64 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Release|Win32.ActiveCfg = Release|Win32 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Release|Win32.Build.0 = Release|Win32 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Release|x64.ActiveCfg = Release|x64 + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A}.Release|x64.Build.0 = Release|x64 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Debug|Win32.ActiveCfg = Debug|Win32 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Debug|Win32.Build.0 = Debug|Win32 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Debug|x64.ActiveCfg = Debug|x64 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Debug|x64.Build.0 = Debug|x64 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Release|Win32.ActiveCfg = Release|Win32 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Release|Win32.Build.0 = Release|Win32 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Release|x64.ActiveCfg = Release|x64 + {128E8BD0-174A-48F0-8771-92B1E8D18713}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/framework/lualib-src/luasocket/macosx.cmd b/framework/lualib-src/luasocket/macosx.cmd new file mode 100644 index 0000000..dd5d8cf --- /dev/null +++ b/framework/lualib-src/luasocket/macosx.cmd @@ -0,0 +1 @@ +make DEBUG=DEBUG PLAT=macosx LUAINC_macosx_base=/Users/$USER/build/macosx/include LUAPREFIX_macosx=/Users/$USER/build/macosx install-both diff --git a/framework/lualib-src/luasocket/makefile b/framework/lualib-src/luasocket/makefile new file mode 100755 index 0000000..f766a25 --- /dev/null +++ b/framework/lualib-src/luasocket/makefile @@ -0,0 +1,49 @@ +# luasocket makefile +# +# see src/makefile for description of how to customize the build +# +# Targets: +# install install system independent support +# install-unix also install unix-only support +# install-both install for lua51 lua52 lua53 +# install-both-unix also install unix-only +# print print the build settings + +PLAT?= linux +PLATS= macosx linux win32 win64 mingw freebsd solaris + +all: $(PLAT) + +$(PLATS) none install install-unix local clean: + $(MAKE) -C src $@ + +print: + $(MAKE) -C src $@ + +test: + lua test/hello.lua + +install-both: + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.1 + @cd src; $(MAKE) install LUAV=5.1 + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.2 + @cd src; $(MAKE) install LUAV=5.2 + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.3 + @cd src; $(MAKE) install LUAV=5.3 + +install-both-unix: + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.1 + @cd src; $(MAKE) install-unix LUAV=5.1 + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.2 + @cd src; $(MAKE) install-unix LUAV=5.2 + $(MAKE) clean + @cd src; $(MAKE) $(PLAT) LUAV=5.3 + @cd src; $(MAKE) install-unix LUAV=5.3 + +.PHONY: test + diff --git a/framework/lualib-src/luasocket/makefile.dist b/framework/lualib-src/luasocket/makefile.dist new file mode 100644 index 0000000..45a8866 --- /dev/null +++ b/framework/lualib-src/luasocket/makefile.dist @@ -0,0 +1,139 @@ +#-------------------------------------------------------------------------- +# Distribution makefile +#-------------------------------------------------------------------------- +DIST = luasocket-3.0-rc1 + +TEST = \ + test/README \ + test/hello.lua \ + test/testclnt.lua \ + test/testsrvr.lua \ + test/testsupport.lua + +SAMPLES = \ + samples/README \ + samples/cddb.lua \ + samples/daytimeclnt.lua \ + samples/echoclnt.lua \ + samples/echosrvr.lua \ + samples/mclisten.lua \ + samples/mcsend.lua \ + samples/listener.lua \ + samples/lpr.lua \ + samples/talker.lua \ + samples/tinyirc.lua + +ETC = \ + etc/README \ + etc/b64.lua \ + etc/check-links.lua \ + etc/check-memory.lua \ + etc/dict.lua \ + etc/dispatch.lua \ + etc/eol.lua \ + etc/forward.lua \ + etc/get.lua \ + etc/lp.lua \ + etc/qp.lua \ + etc/tftp.lua + +SRC = \ + src/makefile \ + src/auxiliar.c \ + src/auxiliar.h \ + src/buffer.c \ + src/buffer.h \ + src/except.c \ + src/except.h \ + src/inet.c \ + src/inet.h \ + src/io.c \ + src/io.h \ + src/luasocket.c \ + src/luasocket.h \ + src/mime.c \ + src/mime.h \ + src/options.c \ + src/options.h \ + src/select.c \ + src/select.h \ + src/socket.h \ + src/tcp.c \ + src/tcp.h \ + src/timeout.c \ + src/timeout.h \ + src/udp.c \ + src/udp.h \ + src/unix.c \ + src/serial.c \ + src/unix.h \ + src/usocket.c \ + src/usocket.h \ + src/wsocket.c \ + src/wsocket.h \ + src/ftp.lua \ + src/http.lua \ + src/ltn12.lua \ + src/mime.lua \ + src/smtp.lua \ + src/socket.lua \ + src/headers.lua \ + src/tp.lua \ + src/url.lua + +MAKE = \ + makefile \ + luasocket.sln \ + luasocket-scm-0.rockspec \ + Lua51.props \ + Lua52.props \ + socket.vcxproj.filters \ + mime.vcxproj.filters \ + socket.vcxproj \ + mime.vcxproj + +DOC = \ + doc/dns.html \ + doc/ftp.html \ + doc/index.html \ + doc/http.html \ + doc/installation.html \ + doc/introduction.html \ + doc/ltn12.html \ + doc/luasocket.png \ + doc/mime.html \ + doc/reference.css \ + doc/reference.html \ + doc/smtp.html \ + doc/socket.html \ + doc/tcp.html \ + doc/udp.html \ + doc/url.html + +dist: + mkdir -p $(DIST) + cp -vf NEW $(DIST) + cp -vf LICENSE $(DIST) + cp -vf README $(DIST) + cp -vf $(MAKE) $(DIST) + + mkdir -p $(DIST)/etc + cp -vf $(ETC) $(DIST)/etc + + mkdir -p $(DIST)/src + cp -vf $(SRC) $(DIST)/src + + mkdir -p $(DIST)/doc + cp -vf $(DOC) $(DIST)/doc + + mkdir -p $(DIST)/samples + cp -vf $(SAMPLES) $(DIST)/samples + + mkdir -p $(DIST)/test + cp -vf $(TEST) $(DIST)/test + + tar -zcvf $(DIST).tar.gz $(DIST) + zip -r $(DIST).zip $(DIST) + +clean: + \rm -rf $(DIST) $(DIST).tar.gz $(DIST).zip diff --git a/framework/lualib-src/luasocket/mime.vcxproj b/framework/lualib-src/luasocket/mime.vcxproj new file mode 100755 index 0000000..1827722 --- /dev/null +++ b/framework/lualib-src/luasocket/mime.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + {128E8BD0-174A-48F0-8771-92B1E8D18713} + Win32Proj + + + + DynamicLibrary + v141 + MultiByte + + + DynamicLibrary + v141 + MultiByte + + + DynamicLibrary + v141 + MultiByte + + + DynamicLibrary + v141 + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.50727.1 + + + $(Configuration)\mime\ + $(Configuration)\ + true + core + + + true + core + $(Platform)\$(Configuration)\mime\ + + + $(Configuration)\mime\ + $(Configuration)\ + false + core + + + false + $(Platform)\$(Configuration)\mime\ + core + + + + Disabled + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + $(OutDir)mime.pdb + Windows + false + + $(OutDir)$(TargetName).lib + MachineX86 + false + + + + + Disabled + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + $(OutDir)mime.pdb + Windows + false + + + $(OutDir)$(TargetName).lib + + + + + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level4 + + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + Windows + true + true + false + + $(OutDir)$(TargetName).lib + MachineX86 + + + + + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;MIME_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level4 + + + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + Windows + true + true + false + + + $(OutDir)$(TargetName).lib + + + + + + \ No newline at end of file diff --git a/framework/lualib-src/luasocket/mingw.cmd b/framework/lualib-src/luasocket/mingw.cmd new file mode 100644 index 0000000..bf2b7ed --- /dev/null +++ b/framework/lualib-src/luasocket/mingw.cmd @@ -0,0 +1 @@ +make PLAT=mingw LUAINC_mingw_base=/home/diego/build/mingw/include LUALIB_mingw_base=/home/diego/build/mingw/bin LUAPREFIX_mingw=/home/diego/build/mingw/bin DEBUG=DEBUG install-both diff --git a/framework/lualib-src/luasocket/socket.vcxproj b/framework/lualib-src/luasocket/socket.vcxproj new file mode 100755 index 0000000..4e8c749 --- /dev/null +++ b/framework/lualib-src/luasocket/socket.vcxproj @@ -0,0 +1,215 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + {66E3CE14-884D-4AEA-9F20-15A0BEAF8C5A} + Win32Proj + + + + DynamicLibrary + v141 + MultiByte + + + DynamicLibrary + v141 + MultiByte + + + DynamicLibrary + v141 + MultiByte + + + DynamicLibrary + v141 + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.50727.1 + + + $(Configuration)\socket\ + $(Configuration)\ + true + core + + + true + core + $(Platform)\$(Configuration)\socket\ + + + $(Configuration)\socket\ + $(Configuration)\ + false + core + + + false + $(Platform)\$(Configuration)\socket\ + core + + + + Disabled + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;LUASOCKET_DEBUG;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + $(OutDir)mime.pdb + Windows + false + + $(OutDir)$(TargetName).lib + MachineX86 + false + + + + + Disabled + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;LUASOCKET_DEBUG;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + $(OutDir)mime.pdb + Windows + false + + + $(OutDir)$(TargetName).lib + + + + + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level4 + + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + Windows + true + true + false + + $(OutDir)$(TargetName).lib + MachineX86 + + + + + $(LUAINC);%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;LUASOCKET_API=__declspec(dllexport);_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level4 + + + $(IntDir)$(TargetName)$(PlatformToolsetVersion).pdb + + + $(LUALIBNAME);ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName).dll + $(LUALIB);%(AdditionalLibraryDirectories) + true + Windows + true + true + false + + + $(OutDir)$(TargetName).lib + + + + + + \ No newline at end of file diff --git a/framework/lualib-src/luasocket/src/auxiliar.c b/framework/lualib-src/luasocket/src/auxiliar.c new file mode 100644 index 0000000..93a66a0 --- /dev/null +++ b/framework/lualib-src/luasocket/src/auxiliar.c @@ -0,0 +1,154 @@ +/*=========================================================================*\ +* Auxiliar routines for class hierarchy manipulation +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "auxiliar.h" +#include +#include + +/*-------------------------------------------------------------------------*\ +* Initializes the module +\*-------------------------------------------------------------------------*/ +int auxiliar_open(lua_State *L) { + (void) L; + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Creates a new class with given methods +* Methods whose names start with __ are passed directly to the metatable. +\*-------------------------------------------------------------------------*/ +void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func) { + luaL_newmetatable(L, classname); /* mt */ + /* create __index table to place methods */ + lua_pushstring(L, "__index"); /* mt,"__index" */ + lua_newtable(L); /* mt,"__index",it */ + /* put class name into class metatable */ + lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ + lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ + lua_rawset(L, -3); /* mt,"__index",it */ + /* pass all methods that start with _ to the metatable, and all others + * to the index table */ + for (; func->name; func++) { /* mt,"__index",it */ + lua_pushstring(L, func->name); + lua_pushcfunction(L, func->func); + lua_rawset(L, func->name[0] == '_' ? -5: -3); + } + lua_rawset(L, -3); /* mt */ + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Prints the value of a class in a nice way +\*-------------------------------------------------------------------------*/ +int auxiliar_tostring(lua_State *L) { + char buf[32]; + if (!lua_getmetatable(L, 1)) goto error; + lua_pushstring(L, "__index"); + lua_gettable(L, -2); + if (!lua_istable(L, -1)) goto error; + lua_pushstring(L, "class"); + lua_gettable(L, -2); + if (!lua_isstring(L, -1)) goto error; + sprintf(buf, "%p", lua_touserdata(L, 1)); + lua_pushfstring(L, "%s: %s", lua_tostring(L, -1), buf); + return 1; +error: + lua_pushstring(L, "invalid object passed to 'auxiliar.c:__tostring'"); + lua_error(L); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Insert class into group +\*-------------------------------------------------------------------------*/ +void auxiliar_add2group(lua_State *L, const char *classname, const char *groupname) { + luaL_getmetatable(L, classname); + lua_pushstring(L, groupname); + lua_pushboolean(L, 1); + lua_rawset(L, -3); + lua_pop(L, 1); +} + +/*-------------------------------------------------------------------------*\ +* Make sure argument is a boolean +\*-------------------------------------------------------------------------*/ +int auxiliar_checkboolean(lua_State *L, int objidx) { + if (!lua_isboolean(L, objidx)) + auxiliar_typeerror(L, objidx, lua_typename(L, LUA_TBOOLEAN)); + return lua_toboolean(L, objidx); +} + +/*-------------------------------------------------------------------------*\ +* Return userdata pointer if object belongs to a given class, abort with +* error otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx) { + void *data = auxiliar_getclassudata(L, classname, objidx); + if (!data) { + char msg[45]; + sprintf(msg, "%.35s expected", classname); + luaL_argerror(L, objidx, msg); + } + return data; +} + +/*-------------------------------------------------------------------------*\ +* Return userdata pointer if object belongs to a given group, abort with +* error otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx) { + void *data = auxiliar_getgroupudata(L, groupname, objidx); + if (!data) { + char msg[45]; + sprintf(msg, "%.35s expected", groupname); + luaL_argerror(L, objidx, msg); + } + return data; +} + +/*-------------------------------------------------------------------------*\ +* Set object class +\*-------------------------------------------------------------------------*/ +void auxiliar_setclass(lua_State *L, const char *classname, int objidx) { + luaL_getmetatable(L, classname); + if (objidx < 0) objidx--; + lua_setmetatable(L, objidx); +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata pointer if object belongs to a given group. Return NULL +* otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx) { + if (!lua_getmetatable(L, objidx)) + return NULL; + lua_pushstring(L, groupname); + lua_rawget(L, -2); + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + return NULL; + } else { + lua_pop(L, 2); + return lua_touserdata(L, objidx); + } +} + +/*-------------------------------------------------------------------------*\ +* Get a userdata pointer if object belongs to a given class. Return NULL +* otherwise +\*-------------------------------------------------------------------------*/ +void *auxiliar_getclassudata(lua_State *L, const char *classname, int objidx) { + return luaL_testudata(L, objidx, classname); +} + +/*-------------------------------------------------------------------------*\ +* Throws error when argument does not have correct type. +* Used to be part of lauxlib in Lua 5.1, was dropped from 5.2. +\*-------------------------------------------------------------------------*/ +int auxiliar_typeerror (lua_State *L, int narg, const char *tname) { + const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, + luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} diff --git a/framework/lualib-src/luasocket/src/auxiliar.h b/framework/lualib-src/luasocket/src/auxiliar.h new file mode 100644 index 0000000..e8c3ead --- /dev/null +++ b/framework/lualib-src/luasocket/src/auxiliar.h @@ -0,0 +1,54 @@ +#ifndef AUXILIAR_H +#define AUXILIAR_H +/*=========================================================================*\ +* Auxiliar routines for class hierarchy manipulation +* LuaSocket toolkit (but completely independent of other LuaSocket modules) +* +* A LuaSocket class is a name associated with Lua metatables. A LuaSocket +* group is a name associated with a class. A class can belong to any number +* of groups. This module provides the functionality to: +* +* - create new classes +* - add classes to groups +* - set the class of objects +* - check if an object belongs to a given class or group +* - get the userdata associated to objects +* - print objects in a pretty way +* +* LuaSocket class names follow the convention {}. Modules +* can define any number of classes and groups. The module tcp.c, for +* example, defines the classes tcp{master}, tcp{client} and tcp{server} and +* the groups tcp{client,server} and tcp{any}. Module functions can then +* perform type-checking on their arguments by either class or group. +* +* LuaSocket metatables define the __index metamethod as being a table. This +* table has one field for each method supported by the class, and a field +* "class" with the class name. +* +* The mapping from class name to the corresponding metatable and the +* reverse mapping are done using lauxlib. +\*=========================================================================*/ + +#include "luasocket.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int auxiliar_open(lua_State *L); +void auxiliar_newclass(lua_State *L, const char *classname, luaL_Reg *func); +int auxiliar_tostring(lua_State *L); +void auxiliar_add2group(lua_State *L, const char *classname, const char *group); +int auxiliar_checkboolean(lua_State *L, int objidx); +void *auxiliar_checkclass(lua_State *L, const char *classname, int objidx); +void *auxiliar_checkgroup(lua_State *L, const char *groupname, int objidx); +void auxiliar_setclass(lua_State *L, const char *classname, int objidx); +void *auxiliar_getgroupudata(lua_State *L, const char *groupname, int objidx); +void *auxiliar_getclassudata(lua_State *L, const char *groupname, int objidx); +int auxiliar_typeerror(lua_State *L, int narg, const char *tname); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* AUXILIAR_H */ diff --git a/framework/lualib-src/luasocket/src/buffer.c b/framework/lualib-src/luasocket/src/buffer.c new file mode 100644 index 0000000..ac5c531 --- /dev/null +++ b/framework/lualib-src/luasocket/src/buffer.c @@ -0,0 +1,270 @@ +/*=========================================================================*\ +* Input/Output interface for Lua programs +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "buffer.h" + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); +static int recvline(p_buffer buf, luaL_Buffer *b); +static int recvall(p_buffer buf, luaL_Buffer *b); +static int buffer_get(p_buffer buf, const char **data, size_t *count); +static void buffer_skip(p_buffer buf, size_t count); +static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); + +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int buffer_open(lua_State *L) { + (void) L; + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Initializes C structure +\*-------------------------------------------------------------------------*/ +void buffer_init(p_buffer buf, p_io io, p_timeout tm) { + buf->first = buf->last = 0; + buf->io = io; + buf->tm = tm; + buf->received = buf->sent = 0; + buf->birthday = timeout_gettime(); +} + +/*-------------------------------------------------------------------------*\ +* object:getstats() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_getstats(lua_State *L, p_buffer buf) { + lua_pushnumber(L, (lua_Number) buf->received); + lua_pushnumber(L, (lua_Number) buf->sent); + lua_pushnumber(L, timeout_gettime() - buf->birthday); + return 3; +} + +/*-------------------------------------------------------------------------*\ +* object:setstats() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_setstats(lua_State *L, p_buffer buf) { + buf->received = (long) luaL_optnumber(L, 2, (lua_Number) buf->received); + buf->sent = (long) luaL_optnumber(L, 3, (lua_Number) buf->sent); + if (lua_isnumber(L, 4)) buf->birthday = timeout_gettime() - lua_tonumber(L, 4); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* object:send() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_send(lua_State *L, p_buffer buf) { + int top = lua_gettop(L); + int err = IO_DONE; + size_t size = 0, sent = 0; + const char *data = luaL_checklstring(L, 2, &size); + long start = (long) luaL_optnumber(L, 3, 1); + long end = (long) luaL_optnumber(L, 4, -1); + timeout_markstart(buf->tm); + if (start < 0) start = (long) (size+start+1); + if (end < 0) end = (long) (size+end+1); + if (start < 1) start = (long) 1; + if (end > (long) size) end = (long) size; + if (start <= end) err = sendraw(buf, data+start-1, end-start+1, &sent); + /* check if there was an error */ + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, buf->io->error(buf->io->ctx, err)); + lua_pushnumber(L, (lua_Number) (sent+start-1)); + } else { + lua_pushnumber(L, (lua_Number) (sent+start-1)); + lua_pushnil(L); + lua_pushnil(L); + } +#ifdef LUASOCKET_DEBUG + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, timeout_gettime() - timeout_getstart(buf->tm)); +#endif + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* object:receive() interface +\*-------------------------------------------------------------------------*/ +int buffer_meth_receive(lua_State *L, p_buffer buf) { + int err = IO_DONE, top = lua_gettop(L); + luaL_Buffer b; + size_t size; + const char *part = luaL_optlstring(L, 3, "", &size); + timeout_markstart(buf->tm); + /* initialize buffer with optional extra prefix + * (useful for concatenating previous partial results) */ + luaL_buffinit(L, &b); + luaL_addlstring(&b, part, size); + /* receive new patterns */ + if (!lua_isnumber(L, 2)) { + const char *p= luaL_optstring(L, 2, "*l"); + if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b); + else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b); + else luaL_argcheck(L, 0, 2, "invalid receive pattern"); + /* get a fixed number of bytes (minus what was already partially + * received) */ + } else { + double n = lua_tonumber(L, 2); + size_t wanted = (size_t) n; + luaL_argcheck(L, n >= 0, 2, "invalid receive pattern"); + if (size == 0 || wanted > size) + err = recvraw(buf, wanted-size, &b); + } + /* check if there was an error */ + if (err != IO_DONE) { + /* we can't push anyting in the stack before pushing the + * contents of the buffer. this is the reason for the complication */ + luaL_pushresult(&b); + lua_pushstring(L, buf->io->error(buf->io->ctx, err)); + lua_pushvalue(L, -2); + lua_pushnil(L); + lua_replace(L, -4); + } else { + luaL_pushresult(&b); + lua_pushnil(L); + lua_pushnil(L); + } +#ifdef LUASOCKET_DEBUG + /* push time elapsed during operation as the last return value */ + lua_pushnumber(L, timeout_gettime() - timeout_getstart(buf->tm)); +#endif + return lua_gettop(L) - top; +} + +/*-------------------------------------------------------------------------*\ +* Determines if there is any data in the read buffer +\*-------------------------------------------------------------------------*/ +int buffer_isempty(p_buffer buf) { + return buf->first >= buf->last; +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Sends a block of data (unbuffered) +\*-------------------------------------------------------------------------*/ +#define STEPSIZE 8192 +static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent) { + p_io io = buf->io; + p_timeout tm = buf->tm; + size_t total = 0; + int err = IO_DONE; + while (total < count && err == IO_DONE) { + size_t done = 0; + size_t step = (count-total <= STEPSIZE)? count-total: STEPSIZE; + err = io->send(io->ctx, data+total, step, &done, tm); + total += done; + } + *sent = total; + buf->sent += total; + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a fixed number of bytes (buffered) +\*-------------------------------------------------------------------------*/ +static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) { + int err = IO_DONE; + size_t total = 0; + while (err == IO_DONE) { + size_t count; const char *data; + err = buffer_get(buf, &data, &count); + count = MIN(count, wanted - total); + luaL_addlstring(b, data, count); + buffer_skip(buf, count); + total += count; + if (total >= wanted) break; + } + return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads everything until the connection is closed (buffered) +\*-------------------------------------------------------------------------*/ +static int recvall(p_buffer buf, luaL_Buffer *b) { + int err = IO_DONE; + size_t total = 0; + while (err == IO_DONE) { + const char *data; size_t count; + err = buffer_get(buf, &data, &count); + total += count; + luaL_addlstring(b, data, count); + buffer_skip(buf, count); + } + if (err == IO_CLOSED) { + if (total > 0) return IO_DONE; + else return IO_CLOSED; + } else return err; +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF +* are not returned by the function and are discarded from the buffer +\*-------------------------------------------------------------------------*/ +static int recvline(p_buffer buf, luaL_Buffer *b) { + int err = IO_DONE; + while (err == IO_DONE) { + size_t count, pos; const char *data; + err = buffer_get(buf, &data, &count); + pos = 0; + while (pos < count && data[pos] != '\n') { + /* we ignore all \r's */ + if (data[pos] != '\r') luaL_addchar(b, data[pos]); + pos++; + } + if (pos < count) { /* found '\n' */ + buffer_skip(buf, pos+1); /* skip '\n' too */ + break; /* we are done */ + } else /* reached the end of the buffer */ + buffer_skip(buf, pos); + } + return err; +} + +/*-------------------------------------------------------------------------*\ +* Skips a given number of bytes from read buffer. No data is read from the +* transport layer +\*-------------------------------------------------------------------------*/ +static void buffer_skip(p_buffer buf, size_t count) { + buf->received += count; + buf->first += count; + if (buffer_isempty(buf)) + buf->first = buf->last = 0; +} + +/*-------------------------------------------------------------------------*\ +* Return any data available in buffer, or get more data from transport layer +* if buffer is empty +\*-------------------------------------------------------------------------*/ +static int buffer_get(p_buffer buf, const char **data, size_t *count) { + int err = IO_DONE; + p_io io = buf->io; + p_timeout tm = buf->tm; + if (buffer_isempty(buf)) { + size_t got; + err = io->recv(io->ctx, buf->data, BUF_SIZE, &got, tm); + buf->first = 0; + buf->last = got; + } + *count = buf->last - buf->first; + *data = buf->data + buf->first; + return err; +} diff --git a/framework/lualib-src/luasocket/src/buffer.h b/framework/lualib-src/luasocket/src/buffer.h new file mode 100644 index 0000000..a0901fc --- /dev/null +++ b/framework/lualib-src/luasocket/src/buffer.h @@ -0,0 +1,52 @@ +#ifndef BUF_H +#define BUF_H +/*=========================================================================*\ +* Input/Output interface for Lua programs +* LuaSocket toolkit +* +* Line patterns require buffering. Reading one character at a time involves +* too many system calls and is very slow. This module implements the +* LuaSocket interface for input/output on connected objects, as seen by +* Lua programs. +* +* Input is buffered. Output is *not* buffered because there was no simple +* way of making sure the buffered output data would ever be sent. +* +* The module is built on top of the I/O abstraction defined in io.h and the +* timeout management is done with the timeout.h interface. +\*=========================================================================*/ +#include "luasocket.h" +#include "io.h" +#include "timeout.h" + +/* buffer size in bytes */ +#define BUF_SIZE 8192 + +/* buffer control structure */ +typedef struct t_buffer_ { + double birthday; /* throttle support info: creation time, */ + size_t sent, received; /* bytes sent, and bytes received */ + p_io io; /* IO driver used for this buffer */ + p_timeout tm; /* timeout management for this buffer */ + size_t first, last; /* index of first and last bytes of stored data */ + char data[BUF_SIZE]; /* storage space for buffer data */ +} t_buffer; +typedef t_buffer *p_buffer; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int buffer_open(lua_State *L); +void buffer_init(p_buffer buf, p_io io, p_timeout tm); +int buffer_meth_getstats(lua_State *L, p_buffer buf); +int buffer_meth_setstats(lua_State *L, p_buffer buf); +int buffer_meth_send(lua_State *L, p_buffer buf); +int buffer_meth_receive(lua_State *L, p_buffer buf); +int buffer_isempty(p_buffer buf); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* BUF_H */ diff --git a/framework/lualib-src/luasocket/src/compat.c b/framework/lualib-src/luasocket/src/compat.c new file mode 100644 index 0000000..34ffdaf --- /dev/null +++ b/framework/lualib-src/luasocket/src/compat.c @@ -0,0 +1,39 @@ +#include "luasocket.h" +#include "compat.h" + +#if LUA_VERSION_NUM==501 + +/* +** Adapted from Lua 5.2 +*/ +void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup) { + luaL_checkstack(L, nup+1, "too many upvalues"); + for (; l->name != NULL; l++) { /* fill the table with given functions */ + int i; + lua_pushstring(L, l->name); + for (i = 0; i < nup; i++) /* copy upvalues to the top */ + lua_pushvalue(L, -(nup+1)); + lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ + lua_settable(L, -(nup + 3)); + } + lua_pop(L, nup); /* remove upvalues */ +} + +/* +** Duplicated from Lua 5.2 +*/ +void *luasocket_testudata (lua_State *L, int ud, const char *tname) { + void *p = lua_touserdata(L, ud); + if (p != NULL) { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + +#endif diff --git a/framework/lualib-src/luasocket/src/compat.h b/framework/lualib-src/luasocket/src/compat.h new file mode 100644 index 0000000..fa2d7d7 --- /dev/null +++ b/framework/lualib-src/luasocket/src/compat.h @@ -0,0 +1,22 @@ +#ifndef COMPAT_H +#define COMPAT_H + +#if LUA_VERSION_NUM==501 + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void luasocket_setfuncs (lua_State *L, const luaL_Reg *l, int nup); +void *luasocket_testudata ( lua_State *L, int arg, const char *tname); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#define luaL_setfuncs luasocket_setfuncs +#define luaL_testudata luasocket_testudata + +#endif + +#endif diff --git a/framework/lualib-src/luasocket/src/except.c b/framework/lualib-src/luasocket/src/except.c new file mode 100644 index 0000000..9c3317f --- /dev/null +++ b/framework/lualib-src/luasocket/src/except.c @@ -0,0 +1,129 @@ +/*=========================================================================*\ +* Simple exception support +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "except.h" +#include + +#if LUA_VERSION_NUM < 502 +#define lua_pcallk(L, na, nr, err, ctx, cont) \ + (((void)ctx),((void)cont),lua_pcall(L, na, nr, err)) +#endif + +#if LUA_VERSION_NUM < 503 +typedef int lua_KContext; +#endif + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int global_protect(lua_State *L); +static int global_newtry(lua_State *L); +static int protected_(lua_State *L); +static int finalize(lua_State *L); +static int do_nothing(lua_State *L); + +/* except functions */ +static luaL_Reg func[] = { + {"newtry", global_newtry}, + {"protect", global_protect}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Try factory +\*-------------------------------------------------------------------------*/ +static void wrap(lua_State *L) { + lua_createtable(L, 1, 0); + lua_pushvalue(L, -2); + lua_rawseti(L, -2, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_setmetatable(L, -2); +} + +static int finalize(lua_State *L) { + if (!lua_toboolean(L, 1)) { + lua_pushvalue(L, lua_upvalueindex(2)); + lua_call(L, 0, 0); + lua_settop(L, 2); + wrap(L); + lua_error(L); + return 0; + } else return lua_gettop(L); +} + +static int do_nothing(lua_State *L) { + (void) L; + return 0; +} + +static int global_newtry(lua_State *L) { + lua_settop(L, 1); + if (lua_isnil(L, 1)) lua_pushcfunction(L, do_nothing); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, -2); + lua_pushcclosure(L, finalize, 2); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Protect factory +\*-------------------------------------------------------------------------*/ +static int unwrap(lua_State *L) { + if (lua_istable(L, -1) && lua_getmetatable(L, -1)) { + int r = lua_rawequal(L, -1, lua_upvalueindex(1)); + lua_pop(L, 1); + if (r) { + lua_pushnil(L); + lua_rawgeti(L, -2, 1); + return 1; + } + } + return 0; +} + +static int protected_finish(lua_State *L, int status, lua_KContext ctx) { + (void)ctx; + if (status != 0 && status != LUA_YIELD) { + if (unwrap(L)) return 2; + else return lua_error(L); + } else return lua_gettop(L); +} + +#if LUA_VERSION_NUM == 502 +static int protected_cont(lua_State *L) { + int ctx = 0; + int status = lua_getctx(L, &ctx); + return protected_finish(L, status, ctx); +} +#else +#define protected_cont protected_finish +#endif + +static int protected_(lua_State *L) { + int status; + lua_pushvalue(L, lua_upvalueindex(2)); + lua_insert(L, 1); + status = lua_pcallk(L, lua_gettop(L) - 1, LUA_MULTRET, 0, 0, protected_cont); + return protected_finish(L, status, 0); +} + +static int global_protect(lua_State *L) { + lua_settop(L, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + lua_pushcclosure(L, protected_, 2); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Init module +\*-------------------------------------------------------------------------*/ +int except_open(lua_State *L) { + lua_newtable(L); /* metatable for wrapped exceptions */ + lua_pushboolean(L, 0); + lua_setfield(L, -2, "__metatable"); + luaL_setfuncs(L, func, 1); + return 0; +} diff --git a/framework/lualib-src/luasocket/src/except.h b/framework/lualib-src/luasocket/src/except.h new file mode 100644 index 0000000..71c31fd --- /dev/null +++ b/framework/lualib-src/luasocket/src/except.h @@ -0,0 +1,46 @@ +#ifndef EXCEPT_H +#define EXCEPT_H +/*=========================================================================*\ +* Exception control +* LuaSocket toolkit (but completely independent from other modules) +* +* This provides support for simple exceptions in Lua. During the +* development of the HTTP/FTP/SMTP support, it became aparent that +* error checking was taking a substantial amount of the coding. These +* function greatly simplify the task of checking errors. +* +* The main idea is that functions should return nil as their first return +* values when they find an error, and return an error message (or value) +* following nil. In case of success, as long as the first value is not nil, +* the other values don't matter. +* +* The idea is to nest function calls with the "try" function. This function +* checks the first value, and, if it's falsy, wraps the second value in a +* table with metatable and calls "error" on it. Otherwise, it returns all +* values it received. Basically, it works like the Lua "assert" function, +* but it creates errors targeted specifically at "protect". +* +* The "newtry" function is a factory for "try" functions that call a +* finalizer in protected mode before calling "error". +* +* The "protect" function returns a new function that behaves exactly like +* the function it receives, but the new function catches exceptions thrown +* by "try" functions and returns nil followed by the error message instead. +* +* With these three functions, it's easy to write functions that throw +* exceptions on error, but that don't interrupt the user script. +\*=========================================================================*/ + +#include "luasocket.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int except_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif diff --git a/framework/lualib-src/luasocket/src/ftp.lua b/framework/lualib-src/luasocket/src/ftp.lua new file mode 100644 index 0000000..bd528ca --- /dev/null +++ b/framework/lualib-src/luasocket/src/ftp.lua @@ -0,0 +1,329 @@ +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +socket.ftp = {} +local _M = socket.ftp +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +_M.TIMEOUT = 60 +-- default port for ftp service +local PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +_M.USER = "ftp" +_M.PASSWORD = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server, port or PORT, _M.TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(_M.TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(_M.TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(_M.TIMEOUT)) + self.try(self.data:connect(self.pasvt.address, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or _M.USER)) + local code, reply = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or _M.PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + address = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + +function metat.__index:epsv() + self.try(self.tp:command("epsv")) + local code, reply = self.try(self.tp:check("229")) + local pattern = "%((.)(.-)%1(.-)%1(.-)%1%)" + local d, prt, address, port = string.match(reply, pattern) + self.try(port, "invalid epsv response") + self.pasvt = { + address = self.tp:getpeername(), + port = port + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.address, self.pasvt.port +end + + +function metat.__index:port(address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", address, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:eprt(family, address, port) + self.pasvt = nil + if not address then + address, port = self.try(self.tp:getsockname()) + self.server = self.try(socket.bind(address, 0)) + address, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local arg = string.format("|%s|%s|%d|", family, address, port) + self.try(self.tp:command("eprt", arg)) + self.try(self.tp:check("2..")) + return 1 +end + + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = { self.tp } + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code,reply = self.try(self.tp:check{"1..", "2.."}) + if (code >= 200) and (code <= 299) then + recvt.sink(reply) + return 1 + end + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = _M.open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:epsv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function genericform(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +_M.genericform = genericform + +local function sput(u, body) + local putt = genericform(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +_M.put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = _M.open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:epsv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = genericform(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +_M.command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = _M.open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + if type(cmdt.command) == "table" then + local argument = cmdt.argument or {} + local check = cmdt.check or {} + for i,cmd in ipairs(cmdt.command) do + f.try(f.tp:command(cmd, argument[i])) + if check[i] then f.try(f.tp:check(check[i])) end + end + else + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + end + f:quit() + return f:close() +end) + +_M.get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + +return _M diff --git a/framework/lualib-src/luasocket/src/headers.lua b/framework/lualib-src/luasocket/src/headers.lua new file mode 100644 index 0000000..1eb8223 --- /dev/null +++ b/framework/lualib-src/luasocket/src/headers.lua @@ -0,0 +1,104 @@ +----------------------------------------------------------------------------- +-- Canonic header field capitalization +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- +local socket = require("socket") +socket.headers = {} +local _M = socket.headers + +_M.canonic = { + ["accept"] = "Accept", + ["accept-charset"] = "Accept-Charset", + ["accept-encoding"] = "Accept-Encoding", + ["accept-language"] = "Accept-Language", + ["accept-ranges"] = "Accept-Ranges", + ["action"] = "Action", + ["alternate-recipient"] = "Alternate-Recipient", + ["age"] = "Age", + ["allow"] = "Allow", + ["arrival-date"] = "Arrival-Date", + ["authorization"] = "Authorization", + ["bcc"] = "Bcc", + ["cache-control"] = "Cache-Control", + ["cc"] = "Cc", + ["comments"] = "Comments", + ["connection"] = "Connection", + ["content-description"] = "Content-Description", + ["content-disposition"] = "Content-Disposition", + ["content-encoding"] = "Content-Encoding", + ["content-id"] = "Content-ID", + ["content-language"] = "Content-Language", + ["content-length"] = "Content-Length", + ["content-location"] = "Content-Location", + ["content-md5"] = "Content-MD5", + ["content-range"] = "Content-Range", + ["content-transfer-encoding"] = "Content-Transfer-Encoding", + ["content-type"] = "Content-Type", + ["cookie"] = "Cookie", + ["date"] = "Date", + ["diagnostic-code"] = "Diagnostic-Code", + ["dsn-gateway"] = "DSN-Gateway", + ["etag"] = "ETag", + ["expect"] = "Expect", + ["expires"] = "Expires", + ["final-log-id"] = "Final-Log-ID", + ["final-recipient"] = "Final-Recipient", + ["from"] = "From", + ["host"] = "Host", + ["if-match"] = "If-Match", + ["if-modified-since"] = "If-Modified-Since", + ["if-none-match"] = "If-None-Match", + ["if-range"] = "If-Range", + ["if-unmodified-since"] = "If-Unmodified-Since", + ["in-reply-to"] = "In-Reply-To", + ["keywords"] = "Keywords", + ["last-attempt-date"] = "Last-Attempt-Date", + ["last-modified"] = "Last-Modified", + ["location"] = "Location", + ["max-forwards"] = "Max-Forwards", + ["message-id"] = "Message-ID", + ["mime-version"] = "MIME-Version", + ["original-envelope-id"] = "Original-Envelope-ID", + ["original-recipient"] = "Original-Recipient", + ["pragma"] = "Pragma", + ["proxy-authenticate"] = "Proxy-Authenticate", + ["proxy-authorization"] = "Proxy-Authorization", + ["range"] = "Range", + ["received"] = "Received", + ["received-from-mta"] = "Received-From-MTA", + ["references"] = "References", + ["referer"] = "Referer", + ["remote-mta"] = "Remote-MTA", + ["reply-to"] = "Reply-To", + ["reporting-mta"] = "Reporting-MTA", + ["resent-bcc"] = "Resent-Bcc", + ["resent-cc"] = "Resent-Cc", + ["resent-date"] = "Resent-Date", + ["resent-from"] = "Resent-From", + ["resent-message-id"] = "Resent-Message-ID", + ["resent-reply-to"] = "Resent-Reply-To", + ["resent-sender"] = "Resent-Sender", + ["resent-to"] = "Resent-To", + ["retry-after"] = "Retry-After", + ["return-path"] = "Return-Path", + ["sender"] = "Sender", + ["server"] = "Server", + ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", + ["status"] = "Status", + ["subject"] = "Subject", + ["te"] = "TE", + ["to"] = "To", + ["trailer"] = "Trailer", + ["transfer-encoding"] = "Transfer-Encoding", + ["upgrade"] = "Upgrade", + ["user-agent"] = "User-Agent", + ["vary"] = "Vary", + ["via"] = "Via", + ["warning"] = "Warning", + ["will-retry-until"] = "Will-Retry-Until", + ["www-authenticate"] = "WWW-Authenticate", + ["x-mailer"] = "X-Mailer", +} + +return _M \ No newline at end of file diff --git a/framework/lualib-src/luasocket/src/http.lua b/framework/lualib-src/luasocket/src/http.lua new file mode 100644 index 0000000..6a3416e --- /dev/null +++ b/framework/lualib-src/luasocket/src/http.lua @@ -0,0 +1,420 @@ +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +socket.http = {} +local _M = socket.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +_M.TIMEOUT = 60 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +-- supported schemes and their particulars +local SCHEMES = { + http = { + port = 80 + , create = function(t) + return socket.tcp end } + , https = { + port = 443 + , create = function(t) + local https = assert( + require("ssl.https"), 'LuaSocket: LuaSec not found') + local tcp = assert( + https.tcp, 'LuaSocket: Function tcp() not available from LuaSec') + return tcp(t) end }} + +-- default scheme and port for document retrieval +local SCHEME = 'http' +local PORT = SCHEMES[SCHEME].port +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try(create()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(_M.TIMEOUT)) + h.try(c:connect(host, port)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status,ec = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then + if ec == "timeout" then + return 408 + end + return nil, status + end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not _M.PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local host = reqt.host + local port = tostring(reqt.port) + if port ~= tostring(SCHEMES[reqt.scheme].port) then + host = host .. ':' .. port end + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. + url.unescape(reqt.password))) + end + -- if we have proxy authentication information, pass it along + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + if proxy.user and proxy.password then + lower["proxy-authorization"] = + "Basic " .. (mime.b64(proxy.user .. ":" .. proxy.password)) + end + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + path ="/" + , scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + -- default to scheme particulars + local schemedefs, host, port, method + = SCHEMES[nreqt.scheme], nreqt.host, nreqt.port, nreqt.method + if not nreqt.create then nreqt.create = schemedefs.create(nreqt) end + if not (port and port ~= '') then nreqt.port = schemedefs.port end + if not (method and method ~= '') then nreqt.method = 'GET' end + if not (host and host ~= "") then + socket.try(nil, "invalid host '" .. base.tostring(nreqt.host) .. "'") + end + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + local location = headers.location + if not location then return false end + location = string.gsub(location, "%s", "") + if location == "" then return false end + local scheme = url.parse(location).scheme + if scheme and (not SCHEMES[scheme]) then return false end + -- avoid https downgrades + if ('https' == reqt.scheme) and ('https' ~= scheme) then return false end + return (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and ((false == reqt.maxredirects) + or ((reqt.nredirects or 0) + < (reqt.maxredirects or 5))) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + local newurl = url.absolute(reqt.url, location) + -- if switching schemes, reset port and create function + if url.parse(newurl).scheme ~= reqt.scheme then + reqt.port = nil + reqt.create = nil end + -- make new request + local result, code, headers, status = trequest { + url = newurl, + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + maxredirects = reqt.maxredirects, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + elseif code == 408 then + return 1, code + end + local headers + -- ignore any 100-continue messages + while code == 100 do + headers = h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +-- turns an url and a body into a generic request +local function genericform(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t), + target = t + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + return reqt +end + +_M.genericform = genericform + +local function srequest(u, b) + local reqt = genericform(u, b) + local _, code, headers, status = trequest(reqt) + return table.concat(reqt.target), code, headers, status +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) + +_M.schemes = SCHEMES +return _M diff --git a/framework/lualib-src/luasocket/src/inet.c b/framework/lualib-src/luasocket/src/inet.c new file mode 100644 index 0000000..ec73fea --- /dev/null +++ b/framework/lualib-src/luasocket/src/inet.c @@ -0,0 +1,537 @@ +/*=========================================================================*\ +* Internet domain functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "inet.h" + +#include +#include +#include + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int inet_global_toip(lua_State *L); +static int inet_global_getaddrinfo(lua_State *L); +static int inet_global_tohostname(lua_State *L); +static int inet_global_getnameinfo(lua_State *L); +static void inet_pushresolved(lua_State *L, struct hostent *hp); +static int inet_global_gethostname(lua_State *L); + +/* DNS functions */ +static luaL_Reg func[] = { + { "toip", inet_global_toip}, + { "getaddrinfo", inet_global_getaddrinfo}, + { "tohostname", inet_global_tohostname}, + { "getnameinfo", inet_global_getnameinfo}, + { "gethostname", inet_global_gethostname}, + { NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int inet_open(lua_State *L) +{ + lua_pushstring(L, "dns"); + lua_newtable(L); + luaL_setfuncs(L, func, 0); + lua_settable(L, -3); + return 0; +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Returns all information provided by the resolver given a host name +* or ip address +\*-------------------------------------------------------------------------*/ +static int inet_gethost(const char *address, struct hostent **hp) { + struct in_addr addr; + if (inet_aton(address, &addr)) + return socket_gethostbyaddr((char *) &addr, sizeof(addr), hp); + else + return socket_gethostbyname(address, hp); +} + +/*-------------------------------------------------------------------------*\ +* Returns all information provided by the resolver given a host name +* or ip address +\*-------------------------------------------------------------------------*/ +static int inet_global_tohostname(lua_State *L) { + const char *address = luaL_checkstring(L, 1); + struct hostent *hp = NULL; + int err = inet_gethost(address, &hp); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_hoststrerror(err)); + return 2; + } + lua_pushstring(L, hp->h_name); + inet_pushresolved(L, hp); + return 2; +} + +static int inet_global_getnameinfo(lua_State *L) { + char hbuf[NI_MAXHOST]; + char sbuf[NI_MAXSERV]; + int i, ret; + struct addrinfo hints; + struct addrinfo *resolved, *iter; + const char *host = luaL_optstring(L, 1, NULL); + const char *serv = luaL_optstring(L, 2, NULL); + + if (!(host || serv)) + luaL_error(L, "host and serv cannot be both nil"); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + + ret = getaddrinfo(host, serv, &hints, &resolved); + if (ret != 0) { + lua_pushnil(L); + lua_pushstring(L, socket_gaistrerror(ret)); + return 2; + } + + lua_newtable(L); + for (i = 1, iter = resolved; iter; i++, iter = iter->ai_next) { + getnameinfo(iter->ai_addr, (socklen_t) iter->ai_addrlen, + hbuf, host? (socklen_t) sizeof(hbuf): 0, + sbuf, serv? (socklen_t) sizeof(sbuf): 0, 0); + if (host) { + lua_pushnumber(L, i); + lua_pushstring(L, hbuf); + lua_settable(L, -3); + } + } + freeaddrinfo(resolved); + + if (serv) { + lua_pushstring(L, sbuf); + return 2; + } else { + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns all information provided by the resolver given a host name +* or ip address +\*-------------------------------------------------------------------------*/ +static int inet_global_toip(lua_State *L) +{ + const char *address = luaL_checkstring(L, 1); + struct hostent *hp = NULL; + int err = inet_gethost(address, &hp); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_hoststrerror(err)); + return 2; + } + lua_pushstring(L, inet_ntoa(*((struct in_addr *) hp->h_addr))); + inet_pushresolved(L, hp); + return 2; +} + +int inet_optfamily(lua_State* L, int narg, const char* def) +{ + static const char* optname[] = { "unspec", "inet", "inet6", NULL }; + static int optvalue[] = { AF_UNSPEC, AF_INET, AF_INET6, 0 }; + + return optvalue[luaL_checkoption(L, narg, def, optname)]; +} + +int inet_optsocktype(lua_State* L, int narg, const char* def) +{ + static const char* optname[] = { "stream", "dgram", NULL }; + static int optvalue[] = { SOCK_STREAM, SOCK_DGRAM, 0 }; + + return optvalue[luaL_checkoption(L, narg, def, optname)]; +} + +static int inet_global_getaddrinfo(lua_State *L) +{ + const char *hostname = luaL_checkstring(L, 1); + struct addrinfo *iterator = NULL, *resolved = NULL; + struct addrinfo hints; + int i = 1, ret = 0; + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = AF_UNSPEC; + ret = getaddrinfo(hostname, NULL, &hints, &resolved); + if (ret != 0) { + lua_pushnil(L); + lua_pushstring(L, socket_gaistrerror(ret)); + return 2; + } + lua_newtable(L); + for (iterator = resolved; iterator; iterator = iterator->ai_next) { + char hbuf[NI_MAXHOST]; + ret = getnameinfo(iterator->ai_addr, (socklen_t) iterator->ai_addrlen, + hbuf, (socklen_t) sizeof(hbuf), NULL, 0, NI_NUMERICHOST); + if (ret){ + freeaddrinfo(resolved); + lua_pushnil(L); + lua_pushstring(L, socket_gaistrerror(ret)); + return 2; + } + lua_pushnumber(L, i); + lua_newtable(L); + switch (iterator->ai_family) { + case AF_INET: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "inet"); + lua_settable(L, -3); + break; + case AF_INET6: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "inet6"); + lua_settable(L, -3); + break; + case AF_UNSPEC: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "unspec"); + lua_settable(L, -3); + break; + default: + lua_pushliteral(L, "family"); + lua_pushliteral(L, "unknown"); + lua_settable(L, -3); + break; + } + lua_pushliteral(L, "addr"); + lua_pushstring(L, hbuf); + lua_settable(L, -3); + lua_settable(L, -3); + i++; + } + freeaddrinfo(resolved); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets the host name +\*-------------------------------------------------------------------------*/ +static int inet_global_gethostname(lua_State *L) +{ + char name[257]; + name[256] = '\0'; + if (gethostname(name, 256) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } else { + lua_pushstring(L, name); + return 1; + } +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Retrieves socket peer name +\*-------------------------------------------------------------------------*/ +int inet_meth_getpeername(lua_State *L, p_socket ps, int family) +{ + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + char name[INET6_ADDRSTRLEN]; + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + err = getnameinfo((struct sockaddr *) &peer, peer_len, + name, INET6_ADDRSTRLEN, + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + if (err) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + return 2; + } + lua_pushstring(L, name); + lua_pushinteger(L, (int) strtol(port, (char **) NULL, 10)); + switch (family) { + case AF_INET: lua_pushliteral(L, "inet"); break; + case AF_INET6: lua_pushliteral(L, "inet6"); break; + case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; + default: lua_pushliteral(L, "unknown"); break; + } + return 3; +} + +/*-------------------------------------------------------------------------*\ +* Retrieves socket local name +\*-------------------------------------------------------------------------*/ +int inet_meth_getsockname(lua_State *L, p_socket ps, int family) +{ + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + char name[INET6_ADDRSTRLEN]; + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getsockname(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + err=getnameinfo((struct sockaddr *)&peer, peer_len, + name, INET6_ADDRSTRLEN, port, 6, NI_NUMERICHOST | NI_NUMERICSERV); + if (err) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + return 2; + } + lua_pushstring(L, name); + lua_pushstring(L, port); + switch (family) { + case AF_INET: lua_pushliteral(L, "inet"); break; + case AF_INET6: lua_pushliteral(L, "inet6"); break; + case AF_UNSPEC: lua_pushliteral(L, "unspec"); break; + default: lua_pushliteral(L, "unknown"); break; + } + return 3; +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Passes all resolver information to Lua as a table +\*-------------------------------------------------------------------------*/ +static void inet_pushresolved(lua_State *L, struct hostent *hp) +{ + char **alias; + struct in_addr **addr; + int i, resolved; + lua_newtable(L); resolved = lua_gettop(L); + lua_pushstring(L, "name"); + lua_pushstring(L, hp->h_name); + lua_settable(L, resolved); + lua_pushstring(L, "ip"); + lua_pushstring(L, "alias"); + i = 1; + alias = hp->h_aliases; + lua_newtable(L); + if (alias) { + while (*alias) { + lua_pushnumber(L, i); + lua_pushstring(L, *alias); + lua_settable(L, -3); + i++; alias++; + } + } + lua_settable(L, resolved); + i = 1; + lua_newtable(L); + addr = (struct in_addr **) hp->h_addr_list; + if (addr) { + while (*addr) { + lua_pushnumber(L, i); + lua_pushstring(L, inet_ntoa(**addr)); + lua_settable(L, -3); + i++; addr++; + } + } + lua_settable(L, resolved); +} + +/*-------------------------------------------------------------------------*\ +* Tries to create a new inet socket +\*-------------------------------------------------------------------------*/ +const char *inet_trycreate(p_socket ps, int family, int type, int protocol) { + const char *err = socket_strerror(socket_create(ps, family, type, protocol)); + if (err == NULL && family == AF_INET6) { + int yes = 1; + setsockopt(*ps, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&yes, sizeof(yes)); + } + return err; +} + +/*-------------------------------------------------------------------------*\ +* "Disconnects" a DGRAM socket +\*-------------------------------------------------------------------------*/ +const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm) +{ + switch (family) { + case AF_INET: { + struct sockaddr_in sin; + memset((char *) &sin, 0, sizeof(sin)); + sin.sin_family = AF_UNSPEC; + sin.sin_addr.s_addr = INADDR_ANY; + return socket_strerror(socket_connect(ps, (SA *) &sin, + sizeof(sin), tm)); + } + case AF_INET6: { + struct sockaddr_in6 sin6; + struct in6_addr addrany = IN6ADDR_ANY_INIT; + memset((char *) &sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_UNSPEC; + sin6.sin6_addr = addrany; + return socket_strerror(socket_connect(ps, (SA *) &sin6, + sizeof(sin6), tm)); + } + } + return NULL; +} + +/*-------------------------------------------------------------------------*\ +* Tries to connect to remote address (address, port) +\*-------------------------------------------------------------------------*/ +const char *inet_tryconnect(p_socket ps, int *family, const char *address, + const char *serv, p_timeout tm, struct addrinfo *connecthints) +{ + struct addrinfo *iterator = NULL, *resolved = NULL; + const char *err = NULL; + int current_family = *family; + /* try resolving */ + err = socket_gaistrerror(getaddrinfo(address, serv, + connecthints, &resolved)); + if (err != NULL) { + if (resolved) freeaddrinfo(resolved); + return err; + } + for (iterator = resolved; iterator; iterator = iterator->ai_next) { + timeout_markstart(tm); + /* create new socket if necessary. if there was no + * bind, we need to create one for every new family + * that shows up while iterating. if there was a + * bind, all families will be the same and we will + * not enter this branch. */ + if (current_family != iterator->ai_family || *ps == SOCKET_INVALID) { + socket_destroy(ps); + err = inet_trycreate(ps, iterator->ai_family, + iterator->ai_socktype, iterator->ai_protocol); + if (err) continue; + current_family = iterator->ai_family; + /* set non-blocking before connect */ + socket_setnonblocking(ps); + } + /* try connecting to remote address */ + err = socket_strerror(socket_connect(ps, (SA *) iterator->ai_addr, + (socklen_t) iterator->ai_addrlen, tm)); + /* if success or timeout is zero, break out of loop */ + if (err == NULL || timeout_iszero(tm)) { + *family = current_family; + break; + } + } + freeaddrinfo(resolved); + /* here, if err is set, we failed */ + return err; +} + +/*-------------------------------------------------------------------------*\ +* Tries to accept a socket +\*-------------------------------------------------------------------------*/ +const char *inet_tryaccept(p_socket server, int family, p_socket client, + p_timeout tm) { + socklen_t len; + t_sockaddr_storage addr; + switch (family) { + case AF_INET6: len = sizeof(struct sockaddr_in6); break; + case AF_INET: len = sizeof(struct sockaddr_in); break; + default: len = sizeof(addr); break; + } + return socket_strerror(socket_accept(server, client, (SA *) &addr, + &len, tm)); +} + +/*-------------------------------------------------------------------------*\ +* Tries to bind socket to (address, port) +\*-------------------------------------------------------------------------*/ +const char *inet_trybind(p_socket ps, int *family, const char *address, + const char *serv, struct addrinfo *bindhints) { + struct addrinfo *iterator = NULL, *resolved = NULL; + const char *err = NULL; + int current_family = *family; + /* translate luasocket special values to C */ + if (strcmp(address, "*") == 0) address = NULL; + if (!serv) serv = "0"; + /* try resolving */ + err = socket_gaistrerror(getaddrinfo(address, serv, bindhints, &resolved)); + if (err) { + if (resolved) freeaddrinfo(resolved); + return err; + } + /* iterate over resolved addresses until one is good */ + for (iterator = resolved; iterator; iterator = iterator->ai_next) { + if (current_family != iterator->ai_family || *ps == SOCKET_INVALID) { + socket_destroy(ps); + err = inet_trycreate(ps, iterator->ai_family, + iterator->ai_socktype, iterator->ai_protocol); + if (err) continue; + current_family = iterator->ai_family; + } + /* try binding to local address */ + err = socket_strerror(socket_bind(ps, (SA *) iterator->ai_addr, + (socklen_t) iterator->ai_addrlen)); + /* keep trying unless bind succeeded */ + if (err == NULL) { + *family = current_family; + /* set to non-blocking after bind */ + socket_setnonblocking(ps); + break; + } + } + /* cleanup and return error */ + freeaddrinfo(resolved); + /* here, if err is set, we failed */ + return err; +} + +/*-------------------------------------------------------------------------*\ +* Some systems do not provide these so that we provide our own. +\*-------------------------------------------------------------------------*/ +#ifdef LUASOCKET_INET_ATON +int inet_aton(const char *cp, struct in_addr *inp) +{ + unsigned int a = 0, b = 0, c = 0, d = 0; + int n = 0, r; + unsigned long int addr = 0; + r = sscanf(cp, "%u.%u.%u.%u%n", &a, &b, &c, &d, &n); + if (r == 0 || n == 0) return 0; + cp += n; + if (*cp) return 0; + if (a > 255 || b > 255 || c > 255 || d > 255) return 0; + if (inp) { + addr += a; addr <<= 8; + addr += b; addr <<= 8; + addr += c; addr <<= 8; + addr += d; + inp->s_addr = htonl(addr); + } + return 1; +} +#endif + +#ifdef LUASOCKET_INET_PTON +int inet_pton(int af, const char *src, void *dst) +{ + struct addrinfo hints, *res; + int ret = 1; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = af; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(src, NULL, &hints, &res) != 0) return -1; + if (af == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in *) res->ai_addr; + memcpy(dst, &in->sin_addr, sizeof(in->sin_addr)); + } else if (af == AF_INET6) { + struct sockaddr_in6 *in = (struct sockaddr_in6 *) res->ai_addr; + memcpy(dst, &in->sin6_addr, sizeof(in->sin6_addr)); + } else { + ret = -1; + } + freeaddrinfo(res); + return ret; +} + +#endif diff --git a/framework/lualib-src/luasocket/src/inet.h b/framework/lualib-src/luasocket/src/inet.h new file mode 100644 index 0000000..5618b61 --- /dev/null +++ b/framework/lualib-src/luasocket/src/inet.h @@ -0,0 +1,56 @@ +#ifndef INET_H +#define INET_H +/*=========================================================================*\ +* Internet domain functions +* LuaSocket toolkit +* +* This module implements the creation and connection of internet domain +* sockets, on top of the socket.h interface, and the interface of with the +* resolver. +* +* The function inet_aton is provided for the platforms where it is not +* available. The module also implements the interface of the internet +* getpeername and getsockname functions as seen by Lua programs. +* +* The Lua functions toip and tohostname are also implemented here. +\*=========================================================================*/ +#include "luasocket.h" +#include "socket.h" +#include "timeout.h" + +#ifdef _WIN32 +#define LUASOCKET_INET_ATON +#endif + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int inet_open(lua_State *L); + +int inet_optfamily(lua_State* L, int narg, const char* def); +int inet_optsocktype(lua_State* L, int narg, const char* def); + +int inet_meth_getpeername(lua_State *L, p_socket ps, int family); +int inet_meth_getsockname(lua_State *L, p_socket ps, int family); + +const char *inet_trycreate(p_socket ps, int family, int type, int protocol); +const char *inet_trydisconnect(p_socket ps, int family, p_timeout tm); +const char *inet_tryconnect(p_socket ps, int *family, const char *address, const char *serv, p_timeout tm, struct addrinfo *connecthints); +const char *inet_tryaccept(p_socket server, int family, p_socket client, p_timeout tm); +const char *inet_trybind(p_socket ps, int *family, const char *address, const char *serv, struct addrinfo *bindhints); + +#ifdef LUASOCKET_INET_ATON +int inet_aton(const char *cp, struct in_addr *inp); +#endif + +#ifdef LUASOCKET_INET_PTON +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); +int inet_pton(int af, const char *src, void *dst); +#endif + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* INET_H */ diff --git a/framework/lualib-src/luasocket/src/io.c b/framework/lualib-src/luasocket/src/io.c new file mode 100644 index 0000000..5ad4b3a --- /dev/null +++ b/framework/lualib-src/luasocket/src/io.c @@ -0,0 +1,28 @@ +/*=========================================================================*\ +* Input/Output abstraction +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "io.h" + +/*-------------------------------------------------------------------------*\ +* Initializes C structure +\*-------------------------------------------------------------------------*/ +void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx) { + io->send = send; + io->recv = recv; + io->error = error; + io->ctx = ctx; +} + +/*-------------------------------------------------------------------------*\ +* I/O error strings +\*-------------------------------------------------------------------------*/ +const char *io_strerror(int err) { + switch (err) { + case IO_DONE: return NULL; + case IO_CLOSED: return "closed"; + case IO_TIMEOUT: return "timeout"; + default: return "unknown error"; + } +} diff --git a/framework/lualib-src/luasocket/src/io.h b/framework/lualib-src/luasocket/src/io.h new file mode 100644 index 0000000..b8a54df --- /dev/null +++ b/framework/lualib-src/luasocket/src/io.h @@ -0,0 +1,70 @@ +#ifndef IO_H +#define IO_H +/*=========================================================================*\ +* Input/Output abstraction +* LuaSocket toolkit +* +* This module defines the interface that LuaSocket expects from the +* transport layer for streamed input/output. The idea is that if any +* transport implements this interface, then the buffer.c functions +* automatically work on it. +* +* The module socket.h implements this interface, and thus the module tcp.h +* is very simple. +\*=========================================================================*/ +#include "luasocket.h" +#include "timeout.h" + +/* IO error codes */ +enum { + IO_DONE = 0, /* operation completed successfully */ + IO_TIMEOUT = -1, /* operation timed out */ + IO_CLOSED = -2, /* the connection has been closed */ + IO_UNKNOWN = -3 +}; + +/* interface to error message function */ +typedef const char *(*p_error) ( + void *ctx, /* context needed by send */ + int err /* error code */ +); + +/* interface to send function */ +typedef int (*p_send) ( + void *ctx, /* context needed by send */ + const char *data, /* pointer to buffer with data to send */ + size_t count, /* number of bytes to send from buffer */ + size_t *sent, /* number of bytes sent uppon return */ + p_timeout tm /* timeout control */ +); + +/* interface to recv function */ +typedef int (*p_recv) ( + void *ctx, /* context needed by recv */ + char *data, /* pointer to buffer where data will be writen */ + size_t count, /* number of bytes to receive into buffer */ + size_t *got, /* number of bytes received uppon return */ + p_timeout tm /* timeout control */ +); + +/* IO driver definition */ +typedef struct t_io_ { + void *ctx; /* context needed by send/recv */ + p_send send; /* send function pointer */ + p_recv recv; /* receive function pointer */ + p_error error; /* strerror function */ +} t_io; +typedef t_io *p_io; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx); +const char *io_strerror(int err); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* IO_H */ diff --git a/framework/lualib-src/luasocket/src/ltn12.lua b/framework/lualib-src/luasocket/src/ltn12.lua new file mode 100644 index 0000000..afa735d --- /dev/null +++ b/framework/lualib-src/luasocket/src/ltn12.lua @@ -0,0 +1,319 @@ +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local unpack = unpack or table.unpack +local base = _G +local _M = {} +if module then -- heuristic for exporting a global package table + ltn12 = _M +end +local filter,source,sink,pump = {},{},{},{} + +_M.filter = filter +_M.source = source +_M.sink = sink +_M.pump = pump + +local unpack = unpack or table.unpack +local select = base.select + +-- 2048 seems to be better in windows... +_M.BLOCKSIZE = 2048 +_M._VERSION = "LTN12 1.0.3" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- +-- returns a high level filter that cycles a low-level filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- chains a bunch of filters together +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local arg = {...} + local n = base.select('#',...) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +-- returns a source that just outputs an error +function source.error(err) + return function() + return nil, err + end +end + +-- creates a file source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(_M.BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +-- turns a fancy source into a simple source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +-- creates string source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) + i = i + _M.BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +-- creates table source +function source.table(t) + base.assert('table' == type(t)) + local i = 0 + return function() + i = i + 1 + return t[i] + end +end + +-- creates rewindable source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + table.insert(t, chunk) + end + end +end + +-- chains a source with one or several filter(s) +function source.chain(src, f, ...) + if ... then f=filter.chain(f, ...) end + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- creates a source that produces contents of several sources, one after the +-- other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local arg = {...} + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- +-- creates a sink that stores into a table +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +-- turns a fancy sink into a simple sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +-- creates a file sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +-- creates a sink that just returns an error +function sink.error(err) + return function() + return nil, err + end +end + +-- chains a sink with one or several filter(s) +function sink.chain(f, snk, ...) + if ... then + local args = { f, snk, ... } + snk = table.remove(args, #args) + f = filter.chain(unpack(args)) + end + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- +-- pumps one chunk from the source to the sink +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +-- pumps all data from a source to a sink, using a step function +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + +return _M diff --git a/framework/lualib-src/luasocket/src/luasocket.c b/framework/lualib-src/luasocket/src/luasocket.c new file mode 100755 index 0000000..0fd99f7 --- /dev/null +++ b/framework/lualib-src/luasocket/src/luasocket.c @@ -0,0 +1,104 @@ +/*=========================================================================*\ +* LuaSocket toolkit +* Networking support for the Lua language +* Diego Nehab +* 26/11/1999 +* +* This library is part of an effort to progressively increase the network +* connectivity of the Lua language. The Lua interface to networking +* functions follows the Sockets API closely, trying to simplify all tasks +* involved in setting up both client and server connections. The provided +* IO routines, however, follow the Lua style, being very similar to the +* standard Lua read and write functions. +\*=========================================================================*/ + +#include "luasocket.h" +#include "auxiliar.h" +#include "except.h" +#include "timeout.h" +#include "buffer.h" +#include "inet.h" +#include "tcp.h" +#include "udp.h" +#include "select.h" + +/*-------------------------------------------------------------------------*\ +* Internal function prototypes +\*-------------------------------------------------------------------------*/ +static int global_skip(lua_State *L); +static int global_unload(lua_State *L); +static int base_open(lua_State *L); + +/*-------------------------------------------------------------------------*\ +* Modules and functions +\*-------------------------------------------------------------------------*/ +static const luaL_Reg mod[] = { + {"auxiliar", auxiliar_open}, + {"except", except_open}, + {"timeout", timeout_open}, + {"buffer", buffer_open}, + {"inet", inet_open}, + {"tcp", tcp_open}, + {"udp", udp_open}, + {"select", select_open}, + {NULL, NULL} +}; + +static luaL_Reg func[] = { + {"skip", global_skip}, + {"__unload", global_unload}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Skip a few arguments +\*-------------------------------------------------------------------------*/ +static int global_skip(lua_State *L) { + int amount = (int) luaL_checkinteger(L, 1); + int ret = lua_gettop(L) - amount - 1; + return ret >= 0 ? ret : 0; +} + +/*-------------------------------------------------------------------------*\ +* Unloads the library +\*-------------------------------------------------------------------------*/ +static int global_unload(lua_State *L) { + (void) L; + socket_close(); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Setup basic stuff. +\*-------------------------------------------------------------------------*/ +static int base_open(lua_State *L) { + if (socket_open()) { + /* export functions (and leave namespace table on top of stack) */ + lua_newtable(L); + luaL_setfuncs(L, func, 0); +#ifdef LUASOCKET_DEBUG + lua_pushstring(L, "_DEBUG"); + lua_pushboolean(L, 1); + lua_rawset(L, -3); +#endif + /* make version string available to scripts */ + lua_pushstring(L, "_VERSION"); + lua_pushstring(L, LUASOCKET_VERSION); + lua_rawset(L, -3); + return 1; + } else { + lua_pushstring(L, "unable to initialize library"); + lua_error(L); + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Initializes all library modules. +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_core(lua_State *L) { + int i; + base_open(L); + for (i = 0; mod[i].name; i++) mod[i].func(L); + return 1; +} diff --git a/framework/lualib-src/luasocket/src/luasocket.h b/framework/lualib-src/luasocket/src/luasocket.h new file mode 100644 index 0000000..d22b1be --- /dev/null +++ b/framework/lualib-src/luasocket/src/luasocket.h @@ -0,0 +1,36 @@ +#ifndef LUASOCKET_H +#define LUASOCKET_H +/*=========================================================================*\ +* LuaSocket toolkit +* Networking support for the Lua language +* Diego Nehab +* 9/11/1999 +\*=========================================================================*/ + +/*-------------------------------------------------------------------------* \ +* Current socket library version +\*-------------------------------------------------------------------------*/ +#define LUASOCKET_VERSION "LuaSocket 3.0-rc1" +#define LUASOCKET_COPYRIGHT "Copyright (C) 1999-2013 Diego Nehab" + +/*-------------------------------------------------------------------------*\ +* This macro prefixes all exported API functions +\*-------------------------------------------------------------------------*/ +#ifndef LUASOCKET_API +#ifdef _WIN32 +#define LUASOCKET_API __declspec(dllexport) +#else +#define LUASOCKET_API __attribute__ ((visibility ("default"))) +#endif +#endif + +#include "lua.h" +#include "lauxlib.h" +#include "compat.h" + +/*-------------------------------------------------------------------------*\ +* Initializes the library. +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_core(lua_State *L); + +#endif /* LUASOCKET_H */ diff --git a/framework/lualib-src/luasocket/src/makefile b/framework/lualib-src/luasocket/src/makefile new file mode 100755 index 0000000..522d378 --- /dev/null +++ b/framework/lualib-src/luasocket/src/makefile @@ -0,0 +1,461 @@ +# luasocket src/makefile +# +# Definitions in this section can be overriden on the command line or in the +# environment. +# +# These are equivalent: +# +# export PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw +# make +# +# and +# +# make PLAT=linux DEBUG=DEBUG LUAV=5.2 prefix=/sw + +# PLAT: linux macosx win32 win64 mingw +# platform to build for +PLAT?=linux + +# LUAV: 5.1 5.2 +# lua version to build against +LUAV?=5.1 + +# MYCFLAGS: to be set by user if needed +MYCFLAGS?= + +# MYLDFLAGS: to be set by user if needed +MYLDFLAGS?= + +# DEBUG: NODEBUG DEBUG +# debug mode causes luasocket to collect and returns timing information useful +# for testing and debugging luasocket itself +DEBUG?=NODEBUG + +# where lua headers are found for macosx builds +# LUAINC_macosx: +# /opt/local/include +LUAINC_macosx_base?=/opt/local/include +LUAINC_macosx?=$(LUAINC_macosx_base)/lua/$(LUAV) $(LUAINC_macosx_base)/lua$(LUAV) $(LUAINC_macosx_base)/lua-$(LUAV) + +# FIXME default should this default to fink or to macports? +# What happens when more than one Lua version is installed? +LUAPREFIX_macosx?=/opt/local +CDIR_macosx?=lib/lua/$(LUAV) +LDIR_macosx?=share/lua/$(LUAV) + +# LUAINC_linux: +# /usr/include/lua$(LUAV) +# /usr/local/include +# /usr/local/include/lua$(LUAV) +# where lua headers are found for linux builds +LUAINC_linux_base?=/usr/include +LUAINC_linux?=$(LUAINC_linux_base)/lua/$(LUAV) $(LUAINC_linux_base)/lua$(LUAV) +LUAPREFIX_linux?=/usr/local +CDIR_linux?=lib/lua/$(LUAV) +LDIR_linux?=share/lua/$(LUAV) + +# LUAINC_freebsd: +# /usr/local/include/lua$(LUAV) +# where lua headers are found for freebsd builds +LUAINC_freebsd_base?=/usr/local/include/ +LUAINC_freebsd?=$(LUAINC_freebsd_base)/lua/$(LUAV) $(LUAINC_freebsd_base)/lua$(LUAV) +LUAPREFIX_freebsd?=/usr/local/ +CDIR_freebsd?=lib/lua/$(LUAV) +LDIR_freebsd?=share/lua/$(LUAV) + +# where lua headers are found for mingw builds +# LUAINC_mingw: +# /opt/local/include +LUAINC_mingw_base?=/usr/include +LUAINC_mingw?=$(LUAINC_mingw_base)/lua/$(LUAV) $(LUAINC_mingw_base)/lua$(LUAV) +LUALIB_mingw_base?=/usr/bin +LUALIB_mingw?=$(LUALIB_mingw_base)/lua/$(LUAV)/lua$(subst .,,$(LUAV)).dll +LUAPREFIX_mingw?=/usr +CDIR_mingw?=lua/$(LUAV) +LDIR_mingw?=lua/$(LUAV)/lua + + +# LUAINC_win32: +# LUALIB_win32: +# where lua headers and libraries are found for win32 builds +LUAPREFIX_win32?= +LUAINC_win32?=$(LUAPREFIX_win32)/include/lua/$(LUAV) $(LUAPREFIX_win32)/include/lua$(LUAV) +PLATFORM_win32?=Release +CDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32) +LDIR_win32?=bin/lua/$(LUAV)/$(PLATFORM_win32)/lua +LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUAV)/$(PLATFORM_win32) +LUALIBNAME_win32?=lua$(subst .,,$(LUAV)).lib + +# LUAINC_win64: +# LUALIB_win64: +# where lua headers and libraries are found for win64 builds +LUAPREFIX_win64?= +LUAINC_win64?=$(LUAPREFIX_win64)/include/lua/$(LUAV) $(LUAPREFIX_win64)/include/lua$(LUAV) +PLATFORM_win64?=x64/Release +CDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64) +LDIR_win64?=bin/lua/$(LUAV)/$(PLATFORM_win64)/lua +LUALIB_win64?=$(LUAPREFIX_win64)/lib/lua/$(LUAV)/$(PLATFORM_win64) +LUALIBNAME_win64?=lua$(subst .,,$(LUAV)).lib + + +# LUAINC_solaris: +LUAINC_solaris_base?=/usr/include +LUAINC_solaris?=$(LUAINC_solaris_base)/lua/$(LUAV) $(LUAINC_solaris_base)/lua$(LUAV) +LUAPREFIX_solaris?=/usr/local +CDIR_solaris?=lib/lua/$(LUAV) +LDIR_solaris?=share/lua/$(LUAV) + +# prefix: /usr/local /usr /opt/local /sw +# the top of the default install tree +prefix?=$(LUAPREFIX_$(PLAT)) + +CDIR?=$(CDIR_$(PLAT)) +LDIR?=$(LDIR_$(PLAT)) + +# DESTDIR: (no default) +# used by package managers to install into a temporary destination +DESTDIR?= + +#------ +# Definitions below can be overridden on the make command line, but +# shouldn't have to be. + + +#------ +# Install directories +# + +INSTALL_DIR=install -d +INSTALL_DATA=install -m644 +INSTALL_EXEC=install +INSTALL_TOP=$(DESTDIR)$(prefix) + +INSTALL_TOP_LDIR=$(INSTALL_TOP)/$(LDIR) +INSTALL_TOP_CDIR=$(INSTALL_TOP)/$(CDIR) + +INSTALL_SOCKET_LDIR=$(INSTALL_TOP_LDIR)/socket +INSTALL_SOCKET_CDIR=$(INSTALL_TOP_CDIR)/socket +INSTALL_MIME_LDIR=$(INSTALL_TOP_LDIR)/mime +INSTALL_MIME_CDIR=$(INSTALL_TOP_CDIR)/mime + +print: + @echo PLAT=$(PLAT) + @echo LUAV=$(LUAV) + @echo DEBUG=$(DEBUG) + @echo prefix=$(prefix) + @echo LUAINC_$(PLAT)=$(LUAINC_$(PLAT)) + @echo LUALIB_$(PLAT)=$(LUALIB_$(PLAT)) + @echo INSTALL_TOP_CDIR=$(INSTALL_TOP_CDIR) + @echo INSTALL_TOP_LDIR=$(INSTALL_TOP_LDIR) + @echo CFLAGS=$(CFLAGS) + @echo LDFLAGS=$(LDFLAGS) + +#------ +# Supported platforms +# +PLATS= macosx linux win32 win64 mingw solaris + +#------ +# Compiler and linker settings +# for Mac OS X +SO_macosx=so +O_macosx=o +CC_macosx=gcc +DEF_macosx= -DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +CFLAGS_macosx=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +LDFLAGS_macosx= -bundle -undefined dynamic_lookup -o +LD_macosx=gcc +SOCKET_macosx=usocket.o + +#------ +# Compiler and linker settings +# for Linux +SO_linux=so +O_linux=o +CC_linux=gcc +DEF_linux=-DLUASOCKET_$(DEBUG) +CFLAGS_linux=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_linux=-O -shared -fpic -o +LD_linux=gcc +SOCKET_linux=usocket.o + +#------ +# Compiler and linker settings +# for FreeBSD +SO_freebsd=so +O_freebsd=o +CC_freebsd=gcc +DEF_freebsd=-DLUASOCKET_$(DEBUG) -DUNIX_HAS_SUN_LEN +CFLAGS_freebsd=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_freebsd=-O -shared -fpic -o +LD_freebsd=gcc +SOCKET_freebsd=usocket.o + +#------ +# Compiler and linker settings +# for Solaris +SO_solaris=so +O_solaris=o +CC_solaris=gcc +DEF_solaris=-DLUASOCKET_$(DEBUG) +CFLAGS_solaris=$(LUAINC:%=-I%) $(DEF) -Wall -Wshadow -Wextra \ + -Wimplicit -O2 -ggdb3 -fpic +LDFLAGS_solaris=-lnsl -lsocket -lresolv -O -shared -fpic -o +LD_solaris=gcc +SOCKET_solaris=usocket.o + +#------ +# Compiler and linker settings +# for MingW +SO_mingw=dll +O_mingw=o +CC_mingw=gcc +DEF_mingw= -DLUASOCKET_$(DEBUG) \ + -DWINVER=0x0501 +CFLAGS_mingw=$(LUAINC:%=-I%) $(DEF) -Wall -O2 -fno-common +LDFLAGS_mingw= $(LUALIB) -shared -Wl,-s -lws2_32 -o +LD_mingw=gcc +SOCKET_mingw=wsocket.o + + +#------ +# Compiler and linker settings +# for Win32 +SO_win32=dll +O_win32=obj +CC_win32=cl +DEF_win32= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ + //D "_CRT_SECURE_NO_WARNINGS" \ + //D "_WINDLL" \ + //D "LUASOCKET_$(DEBUG)" +CFLAGS_win32=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +LDFLAGS_win32= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ + //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ + /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ + //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ + //MACHINE:X86 /LIBPATH:"$(LUALIB)" \ + $(LUALIBNAME_win32) ws2_32.lib //OUT: + +LD_win32=cl +SOCKET_win32=wsocket.obj + +#------ +# Compiler and linker settings +# for Win64 +SO_win64=dll +O_win64=obj +CC_win64=cl +DEF_win64= //D "WIN32" //D "NDEBUG" //D "_WINDOWS" //D "_USRDLL" \ + //D "_CRT_SECURE_NO_WARNINGS" \ + //D "_WINDLL" \ + //D "LUASOCKET_$(DEBUG)" +CFLAGS_win64=$(LUAINC:%=//I "%") $(DEF) //O2 //Ot //MD //W3 //nologo +LDFLAGS_win64= //nologo //link //NOLOGO //DLL //INCREMENTAL:NO \ + //MANIFEST //MANIFESTFILE:"intermediate.manifest" \ + /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ + //SUBSYSTEM:WINDOWS //OPT:REF //OPT:ICF //DYNAMICBASE:NO \ + /LIBPATH:"$(LUALIB)" \ + $(LUALIBNAME_win64) ws2_32.lib //OUT: + +LD_win64=cl +SOCKET_win64=wsocket.obj + +.SUFFIXES: .obj + +.c.obj: + $(CC) $(CFLAGS) //Fo"$@" //c $< + +#------ +# Output file names +# +SO=$(SO_$(PLAT)) +O=$(O_$(PLAT)) +SOCKET_V=3.0-rc1 +MIME_V=1.0.3 +SOCKET_SO=socket-$(SOCKET_V).$(SO) +MIME_SO=mime-$(MIME_V).$(SO) +UNIX_SO=unix.$(SO) +SERIAL_SO=serial.$(SO) +SOCKET=$(SOCKET_$(PLAT)) + +#------ +# Settings selected for platform +# +CC=$(CC_$(PLAT)) +DEF=$(DEF_$(PLAT)) +CFLAGS=$(MYCFLAGS) $(CFLAGS_$(PLAT)) +LDFLAGS=$(MYLDFLAGS) $(LDFLAGS_$(PLAT)) +LD=$(LD_$(PLAT)) +LUAINC= $(LUAINC_$(PLAT)) +LUALIB= $(LUALIB_$(PLAT)) + +#------ +# Modules belonging to socket-core +# +SOCKET_OBJS= \ + luasocket.$(O) \ + timeout.$(O) \ + buffer.$(O) \ + io.$(O) \ + auxiliar.$(O) \ + compat.$(O) \ + options.$(O) \ + inet.$(O) \ + $(SOCKET) \ + except.$(O) \ + select.$(O) \ + tcp.$(O) \ + udp.$(O) + +#------ +# Modules belonging mime-core +# +MIME_OBJS= \ + mime.$(O) \ + compat.$(O) + +#------ +# Modules belonging unix (local domain sockets) +# +UNIX_OBJS=\ + buffer.$(O) \ + auxiliar.$(O) \ + options.$(O) \ + timeout.$(O) \ + io.$(O) \ + usocket.$(O) \ + unixstream.$(O) \ + unixdgram.$(O) \ + compat.$(O) \ + unix.$(O) + +#------ +# Modules belonging to serial (device streams) +# +SERIAL_OBJS=\ + buffer.$(O) \ + compat.$(O) \ + auxiliar.$(O) \ + options.$(O) \ + timeout.$(O) \ + io.$(O) \ + usocket.$(O) \ + serial.$(O) + +#------ +# Files to install +# +TO_SOCKET_LDIR= \ + http.lua \ + url.lua \ + tp.lua \ + ftp.lua \ + headers.lua \ + smtp.lua + +TO_TOP_LDIR= \ + ltn12.lua \ + socket.lua \ + mime.lua + +#------ +# Targets +# +default: $(PLAT) + + +freebsd: + $(MAKE) all-unix PLAT=freebsd + +macosx: + $(MAKE) all-unix PLAT=macosx + +win32: + $(MAKE) all PLAT=win32 + +win64: + $(MAKE) all PLAT=win64 + +linux: + $(MAKE) all-unix PLAT=linux + +mingw: + $(MAKE) all PLAT=mingw + +solaris: + $(MAKE) all-unix PLAT=solaris + +none: + @echo "Please run" + @echo " make PLATFORM" + @echo "where PLATFORM is one of these:" + @echo " $(PLATS)" + +all: $(SOCKET_SO) $(MIME_SO) + +$(SOCKET_SO): $(SOCKET_OBJS) + $(LD) $(SOCKET_OBJS) $(LDFLAGS)$@ + +$(MIME_SO): $(MIME_OBJS) + $(LD) $(MIME_OBJS) $(LDFLAGS)$@ + +all-unix: all $(UNIX_SO) $(SERIAL_SO) + +$(UNIX_SO): $(UNIX_OBJS) + $(LD) $(UNIX_OBJS) $(LDFLAGS)$@ + +$(SERIAL_SO): $(SERIAL_OBJS) + $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ + +install: + $(INSTALL_DIR) $(INSTALL_TOP_LDIR) + $(INSTALL_DATA) $(TO_TOP_LDIR) $(INSTALL_TOP_LDIR) + $(INSTALL_DIR) $(INSTALL_SOCKET_LDIR) + $(INSTALL_DATA) $(TO_SOCKET_LDIR) $(INSTALL_SOCKET_LDIR) + $(INSTALL_DIR) $(INSTALL_SOCKET_CDIR) + $(INSTALL_EXEC) $(SOCKET_SO) $(INSTALL_SOCKET_CDIR)/core.$(SO) + $(INSTALL_DIR) $(INSTALL_MIME_CDIR) + $(INSTALL_EXEC) $(MIME_SO) $(INSTALL_MIME_CDIR)/core.$(SO) + +install-unix: install + $(INSTALL_EXEC) $(UNIX_SO) $(INSTALL_SOCKET_CDIR)/$(UNIX_SO) + $(INSTALL_EXEC) $(SERIAL_SO) $(INSTALL_SOCKET_CDIR)/$(SERIAL_SO) + +local: + $(MAKE) install INSTALL_TOP_CDIR=.. INSTALL_TOP_LDIR=.. + +clean: + rm -f $(SOCKET_SO) $(SOCKET_OBJS) $(SERIAL_OBJS) + rm -f $(MIME_SO) $(UNIX_SO) $(SERIAL_SO) $(MIME_OBJS) $(UNIX_OBJS) + +.PHONY: all $(PLATS) default clean echo none + +#------ +# List of dependencies +# +compat.$(O): compat.c compat.h +auxiliar.$(O): auxiliar.c auxiliar.h +buffer.$(O): buffer.c buffer.h io.h timeout.h +except.$(O): except.c except.h +inet.$(O): inet.c inet.h socket.h io.h timeout.h usocket.h +io.$(O): io.c io.h timeout.h +luasocket.$(O): luasocket.c luasocket.h auxiliar.h except.h \ + timeout.h buffer.h io.h inet.h socket.h usocket.h tcp.h \ + udp.h select.h +mime.$(O): mime.c mime.h +options.$(O): options.c auxiliar.h options.h socket.h io.h \ + timeout.h usocket.h inet.h +select.$(O): select.c socket.h io.h timeout.h usocket.h select.h +serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ + options.h unix.h buffer.h +tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ + inet.h options.h tcp.h buffer.h +timeout.$(O): timeout.c auxiliar.h timeout.h +udp.$(O): udp.c auxiliar.h socket.h io.h timeout.h usocket.h \ + inet.h options.h udp.h +unix.$(O): unix.c auxiliar.h socket.h io.h timeout.h usocket.h \ + options.h unix.h buffer.h +usocket.$(O): usocket.c socket.h io.h timeout.h usocket.h +wsocket.$(O): wsocket.c socket.h io.h timeout.h usocket.h diff --git a/framework/lualib-src/luasocket/src/mbox.lua b/framework/lualib-src/luasocket/src/mbox.lua new file mode 100644 index 0000000..ed9e781 --- /dev/null +++ b/framework/lualib-src/luasocket/src/mbox.lua @@ -0,0 +1,92 @@ +local _M = {} + +if module then + mbox = _M +end + +function _M.split_message(message_s) + local message = {} + message_s = string.gsub(message_s, "\r\n", "\n") + string.gsub(message_s, "^(.-\n)\n", function (h) message.headers = h end) + string.gsub(message_s, "^.-\n\n(.*)", function (b) message.body = b end) + if not message.body then + string.gsub(message_s, "^\n(.*)", function (b) message.body = b end) + end + if not message.headers and not message.body then + message.headers = message_s + end + return message.headers or "", message.body or "" +end + +function _M.split_headers(headers_s) + local headers = {} + headers_s = string.gsub(headers_s, "\r\n", "\n") + headers_s = string.gsub(headers_s, "\n[ ]+", " ") + string.gsub("\n" .. headers_s, "\n([^\n]+)", function (h) table.insert(headers, h) end) + return headers +end + +function _M.parse_header(header_s) + header_s = string.gsub(header_s, "\n[ ]+", " ") + header_s = string.gsub(header_s, "\n+", "") + local _, __, name, value = string.find(header_s, "([^%s:]-):%s*(.*)") + return name, value +end + +function _M.parse_headers(headers_s) + local headers_t = _M.split_headers(headers_s) + local headers = {} + for i = 1, #headers_t do + local name, value = _M.parse_header(headers_t[i]) + if name then + name = string.lower(name) + if headers[name] then + headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + end + return headers +end + +function _M.parse_from(from) + local _, __, name, address = string.find(from, "^%s*(.-)%s*%<(.-)%>") + if not address then + _, __, address = string.find(from, "%s*(.+)%s*") + end + name = name or "" + address = address or "" + if name == "" then name = address end + name = string.gsub(name, '"', "") + return name, address +end + +function _M.split_mbox(mbox_s) + local mbox = {} + mbox_s = string.gsub(mbox_s, "\r\n", "\n") .."\n\nFrom \n" + local nj, i, j = 1, 1, 1 + while 1 do + i, nj = string.find(mbox_s, "\n\nFrom .-\n", j) + if not i then break end + local message = string.sub(mbox_s, j, i-1) + table.insert(mbox, message) + j = nj+1 + end + return mbox +end + +function _M.parse(mbox_s) + local mbox = _M.split_mbox(mbox_s) + for i = 1, #mbox do + mbox[i] = _M.parse_message(mbox[i]) + end + return mbox +end + +function _M.parse_message(message_s) + local message = {} + message.headers, message.body = _M.split_message(message_s) + message.headers = _M.parse_headers(message.headers) + return message +end + +return _M diff --git a/framework/lualib-src/luasocket/src/mime.c b/framework/lualib-src/luasocket/src/mime.c new file mode 100755 index 0000000..6c210e4 --- /dev/null +++ b/framework/lualib-src/luasocket/src/mime.c @@ -0,0 +1,852 @@ +/*=========================================================================*\ +* MIME support functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "mime.h" +#include +#include + +/*=========================================================================*\ +* Don't want to trust escape character constants +\*=========================================================================*/ +typedef unsigned char UC; +static const char CRLF[] = "\r\n"; +static const char EQCRLF[] = "=\r\n"; + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static int mime_global_wrp(lua_State *L); +static int mime_global_b64(lua_State *L); +static int mime_global_unb64(lua_State *L); +static int mime_global_qp(lua_State *L); +static int mime_global_unqp(lua_State *L); +static int mime_global_qpwrp(lua_State *L); +static int mime_global_eol(lua_State *L); +static int mime_global_dot(lua_State *L); + +static size_t dot(int c, size_t state, luaL_Buffer *buffer); +//static void b64setup(UC *base); +static size_t b64encode(UC c, UC *input, size_t size, luaL_Buffer *buffer); +static size_t b64pad(const UC *input, size_t size, luaL_Buffer *buffer); +static size_t b64decode(UC c, UC *input, size_t size, luaL_Buffer *buffer); + +//static void qpsetup(UC *class, UC *unbase); +static void qpquote(UC c, luaL_Buffer *buffer); +static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer); +static size_t qpencode(UC c, UC *input, size_t size, + const char *marker, luaL_Buffer *buffer); +static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer); + +/* code support functions */ +static luaL_Reg func[] = { + { "dot", mime_global_dot }, + { "b64", mime_global_b64 }, + { "eol", mime_global_eol }, + { "qp", mime_global_qp }, + { "qpwrp", mime_global_qpwrp }, + { "unb64", mime_global_unb64 }, + { "unqp", mime_global_unqp }, + { "wrp", mime_global_wrp }, + { NULL, NULL } +}; + +/*-------------------------------------------------------------------------*\ +* Quoted-printable globals +\*-------------------------------------------------------------------------*/ +enum {QP_PLAIN, QP_QUOTED, QP_CR, QP_IF_LAST}; + +static const UC qpclass[] = { + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_CR, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_IF_LAST, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_QUOTED, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, QP_PLAIN, + QP_PLAIN, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED, + QP_QUOTED, QP_QUOTED, QP_QUOTED, QP_QUOTED +}; + +static const UC qpbase[] = "0123456789ABCDEF"; + +static const UC qpunbase[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, + 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255 +}; + +/*-------------------------------------------------------------------------*\ +* Base64 globals +\*-------------------------------------------------------------------------*/ +static const UC b64base[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const UC b64unbase[] = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, + 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, + 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}; + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_mime_core(lua_State *L) +{ + lua_newtable(L); + luaL_setfuncs(L, func, 0); + /* make version string available to scripts */ + lua_pushstring(L, "_VERSION"); + lua_pushstring(L, MIME_VERSION); + lua_rawset(L, -3); + /* initialize lookup tables */ + // qpsetup(qpclass, qpunbase); + // b64setup(b64unbase); + return 1; +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Incrementaly breaks a string into lines. The string can have CRLF breaks. +* A, n = wrp(l, B, length) +* A is a copy of B, broken into lines of at most 'length' bytes. +* 'l' is how many bytes are left for the first line of B. +* 'n' is the number of bytes left in the last line of A. +\*-------------------------------------------------------------------------*/ +static int mime_global_wrp(lua_State *L) +{ + size_t size = 0; + int left = (int) luaL_checknumber(L, 1); + const UC *input = (const UC *) luaL_optlstring(L, 2, NULL, &size); + const UC *last = input + size; + int length = (int) luaL_optnumber(L, 3, 76); + luaL_Buffer buffer; + /* end of input black-hole */ + if (!input) { + /* if last line has not been terminated, add a line break */ + if (left < length) lua_pushstring(L, CRLF); + /* otherwise, we are done */ + else lua_pushnil(L); + lua_pushnumber(L, length); + return 2; + } + luaL_buffinit(L, &buffer); + while (input < last) { + switch (*input) { + case '\r': + break; + case '\n': + luaL_addstring(&buffer, CRLF); + left = length; + break; + default: + if (left <= 0) { + left = length; + luaL_addstring(&buffer, CRLF); + } + luaL_addchar(&buffer, *input); + left--; + break; + } + input++; + } + luaL_pushresult(&buffer); + lua_pushnumber(L, left); + return 2; +} + +#if 0 +/*-------------------------------------------------------------------------*\ +* Fill base64 decode map. +\*-------------------------------------------------------------------------*/ +static void b64setup(UC *unbase) +{ + int i; + for (i = 0; i <= 255; i++) unbase[i] = (UC) 255; + for (i = 0; i < 64; i++) unbase[b64base[i]] = (UC) i; + unbase['='] = 0; + + printf("static const UC b64unbase[] = {\n"); + for (int i = 0; i < 256; i++) { + printf("%d, ", unbase[i]); + } + printf("\n}\n;"); +} +#endif + +/*-------------------------------------------------------------------------*\ +* Acumulates bytes in input buffer until 3 bytes are available. +* Translate the 3 bytes into Base64 form and append to buffer. +* Returns new number of bytes in buffer. +\*-------------------------------------------------------------------------*/ +static size_t b64encode(UC c, UC *input, size_t size, + luaL_Buffer *buffer) +{ + input[size++] = c; + if (size == 3) { + UC code[4]; + unsigned long value = 0; + value += input[0]; value <<= 8; + value += input[1]; value <<= 8; + value += input[2]; + code[3] = b64base[value & 0x3f]; value >>= 6; + code[2] = b64base[value & 0x3f]; value >>= 6; + code[1] = b64base[value & 0x3f]; value >>= 6; + code[0] = b64base[value]; + luaL_addlstring(buffer, (char *) code, 4); + size = 0; + } + return size; +} + +/*-------------------------------------------------------------------------*\ +* Encodes the Base64 last 1 or 2 bytes and adds padding '=' +* Result, if any, is appended to buffer. +* Returns 0. +\*-------------------------------------------------------------------------*/ +static size_t b64pad(const UC *input, size_t size, + luaL_Buffer *buffer) +{ + unsigned long value = 0; + UC code[4] = {'=', '=', '=', '='}; + switch (size) { + case 1: + value = input[0] << 4; + code[1] = b64base[value & 0x3f]; value >>= 6; + code[0] = b64base[value]; + luaL_addlstring(buffer, (char *) code, 4); + break; + case 2: + value = input[0]; value <<= 8; + value |= input[1]; value <<= 2; + code[2] = b64base[value & 0x3f]; value >>= 6; + code[1] = b64base[value & 0x3f]; value >>= 6; + code[0] = b64base[value]; + luaL_addlstring(buffer, (char *) code, 4); + break; + default: + break; + } + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Acumulates bytes in input buffer until 4 bytes are available. +* Translate the 4 bytes from Base64 form and append to buffer. +* Returns new number of bytes in buffer. +\*-------------------------------------------------------------------------*/ +static size_t b64decode(UC c, UC *input, size_t size, + luaL_Buffer *buffer) +{ + /* ignore invalid characters */ + if (b64unbase[c] > 64) return size; + input[size++] = c; + /* decode atom */ + if (size == 4) { + UC decoded[3]; + int valid, value = 0; + value = b64unbase[input[0]]; value <<= 6; + value |= b64unbase[input[1]]; value <<= 6; + value |= b64unbase[input[2]]; value <<= 6; + value |= b64unbase[input[3]]; + decoded[2] = (UC) (value & 0xff); value >>= 8; + decoded[1] = (UC) (value & 0xff); value >>= 8; + decoded[0] = (UC) value; + /* take care of paddding */ + valid = (input[2] == '=') ? 1 : (input[3] == '=') ? 2 : 3; + luaL_addlstring(buffer, (char *) decoded, valid); + return 0; + /* need more data */ + } else return size; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally applies the Base64 transfer content encoding to a string +* A, B = b64(C, D) +* A is the encoded version of the largest prefix of C .. D that is +* divisible by 3. B has the remaining bytes of C .. D, *without* encoding. +* The easiest thing would be to concatenate the two strings and +* encode the result, but we can't afford that or Lua would dupplicate +* every chunk we received. +\*-------------------------------------------------------------------------*/ +static int mime_global_b64(lua_State *L) +{ + UC atom[3]; + size_t isize = 0, asize = 0; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 2); + /* process first part of the input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = b64encode(*input++, atom, asize, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second part is nil, we are done */ + if (!input) { + size_t osize = 0; + asize = b64pad(atom, asize, &buffer); + luaL_pushresult(&buffer); + /* if the output is empty and the input is nil, return nil */ + lua_tolstring(L, -1, &osize); + if (osize == 0) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise process the second part */ + last = input + isize; + while (input < last) + asize = b64encode(*input++, atom, asize, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally removes the Base64 transfer content encoding from a string +* A, B = b64(C, D) +* A is the encoded version of the largest prefix of C .. D that is +* divisible by 4. B has the remaining bytes of C .. D, *without* encoding. +\*-------------------------------------------------------------------------*/ +static int mime_global_unb64(lua_State *L) +{ + UC atom[4]; + size_t isize = 0, asize = 0; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 2); + /* process first part of the input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = b64decode(*input++, atom, asize, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second is nil, we are done */ + if (!input) { + size_t osize = 0; + luaL_pushresult(&buffer); + /* if the output is empty and the input is nil, return nil */ + lua_tolstring(L, -1, &osize); + if (osize == 0) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise, process the rest of the input */ + last = input + isize; + while (input < last) + asize = b64decode(*input++, atom, asize, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Quoted-printable encoding scheme +* all (except CRLF in text) can be =XX +* CLRL in not text must be =XX=XX +* 33 through 60 inclusive can be plain +* 62 through 126 inclusive can be plain +* 9 and 32 can be plain, unless in the end of a line, where must be =XX +* encoded lines must be no longer than 76 not counting CRLF +* soft line-break are =CRLF +* To encode one byte, we need to see the next two. +* Worst case is when we see a space, and wonder if a CRLF is comming +\*-------------------------------------------------------------------------*/ +#if 0 +/*-------------------------------------------------------------------------*\ +* Split quoted-printable characters into classes +* Precompute reverse map for encoding +\*-------------------------------------------------------------------------*/ +static void qpsetup(UC *cl, UC *unbase) +{ + + int i; + for (i = 0; i < 256; i++) cl[i] = QP_QUOTED; + for (i = 33; i <= 60; i++) cl[i] = QP_PLAIN; + for (i = 62; i <= 126; i++) cl[i] = QP_PLAIN; + cl['\t'] = QP_IF_LAST; + cl[' '] = QP_IF_LAST; + cl['\r'] = QP_CR; + for (i = 0; i < 256; i++) unbase[i] = 255; + unbase['0'] = 0; unbase['1'] = 1; unbase['2'] = 2; + unbase['3'] = 3; unbase['4'] = 4; unbase['5'] = 5; + unbase['6'] = 6; unbase['7'] = 7; unbase['8'] = 8; + unbase['9'] = 9; unbase['A'] = 10; unbase['a'] = 10; + unbase['B'] = 11; unbase['b'] = 11; unbase['C'] = 12; + unbase['c'] = 12; unbase['D'] = 13; unbase['d'] = 13; + unbase['E'] = 14; unbase['e'] = 14; unbase['F'] = 15; + unbase['f'] = 15; + +printf("static UC qpclass[] = {"); + for (int i = 0; i < 256; i++) { + if (i % 6 == 0) { + printf("\n "); + } + switch(cl[i]) { + case QP_QUOTED: + printf("QP_QUOTED, "); + break; + case QP_PLAIN: + printf("QP_PLAIN, "); + break; + case QP_CR: + printf("QP_CR, "); + break; + case QP_IF_LAST: + printf("QP_IF_LAST, "); + break; + } + } +printf("\n};\n"); + +printf("static const UC qpunbase[] = {"); + for (int i = 0; i < 256; i++) { + int c = qpunbase[i]; + printf("%d, ", c); + } +printf("\";\n"); +} +#endif + +/*-------------------------------------------------------------------------*\ +* Output one character in form =XX +\*-------------------------------------------------------------------------*/ +static void qpquote(UC c, luaL_Buffer *buffer) +{ + luaL_addchar(buffer, '='); + luaL_addchar(buffer, qpbase[c >> 4]); + luaL_addchar(buffer, qpbase[c & 0x0F]); +} + +/*-------------------------------------------------------------------------*\ +* Accumulate characters until we are sure about how to deal with them. +* Once we are sure, output to the buffer, in the correct form. +\*-------------------------------------------------------------------------*/ +static size_t qpencode(UC c, UC *input, size_t size, + const char *marker, luaL_Buffer *buffer) +{ + input[size++] = c; + /* deal with all characters we can have */ + while (size > 0) { + switch (qpclass[input[0]]) { + /* might be the CR of a CRLF sequence */ + case QP_CR: + if (size < 2) return size; + if (input[1] == '\n') { + luaL_addstring(buffer, marker); + return 0; + } else qpquote(input[0], buffer); + break; + /* might be a space and that has to be quoted if last in line */ + case QP_IF_LAST: + if (size < 3) return size; + /* if it is the last, quote it and we are done */ + if (input[1] == '\r' && input[2] == '\n') { + qpquote(input[0], buffer); + luaL_addstring(buffer, marker); + return 0; + } else luaL_addchar(buffer, input[0]); + break; + /* might have to be quoted always */ + case QP_QUOTED: + qpquote(input[0], buffer); + break; + /* might never have to be quoted */ + default: + luaL_addchar(buffer, input[0]); + break; + } + input[0] = input[1]; input[1] = input[2]; + size--; + } + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Deal with the final characters +\*-------------------------------------------------------------------------*/ +static size_t qppad(UC *input, size_t size, luaL_Buffer *buffer) +{ + size_t i; + for (i = 0; i < size; i++) { + if (qpclass[input[i]] == QP_PLAIN) luaL_addchar(buffer, input[i]); + else qpquote(input[i], buffer); + } + if (size > 0) luaL_addstring(buffer, EQCRLF); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally converts a string to quoted-printable +* A, B = qp(C, D, marker) +* Marker is the text to be used to replace CRLF sequences found in A. +* A is the encoded version of the largest prefix of C .. D that +* can be encoded without doubts. +* B has the remaining bytes of C .. D, *without* encoding. +\*-------------------------------------------------------------------------*/ +static int mime_global_qp(lua_State *L) +{ + size_t asize = 0, isize = 0; + UC atom[3]; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + const char *marker = luaL_optstring(L, 3, CRLF); + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 3); + /* process first part of input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = qpencode(*input++, atom, asize, marker, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second part is nil, we are done */ + if (!input) { + asize = qppad(atom, asize, &buffer); + luaL_pushresult(&buffer); + if (!(*lua_tostring(L, -1))) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise process rest of input */ + last = input + isize; + while (input < last) + asize = qpencode(*input++, atom, asize, marker, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Accumulate characters until we are sure about how to deal with them. +* Once we are sure, output the to the buffer, in the correct form. +\*-------------------------------------------------------------------------*/ +static size_t qpdecode(UC c, UC *input, size_t size, luaL_Buffer *buffer) { + int d; + input[size++] = c; + /* deal with all characters we can deal */ + switch (input[0]) { + /* if we have an escape character */ + case '=': + if (size < 3) return size; + /* eliminate soft line break */ + if (input[1] == '\r' && input[2] == '\n') return 0; + /* decode quoted representation */ + c = qpunbase[input[1]]; d = qpunbase[input[2]]; + /* if it is an invalid, do not decode */ + if (c > 15 || d > 15) luaL_addlstring(buffer, (char *)input, 3); + else luaL_addchar(buffer, (char) ((c << 4) + d)); + return 0; + case '\r': + if (size < 2) return size; + if (input[1] == '\n') luaL_addlstring(buffer, (char *)input, 2); + return 0; + default: + if (input[0] == '\t' || (input[0] > 31 && input[0] < 127)) + luaL_addchar(buffer, input[0]); + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Incrementally decodes a string in quoted-printable +* A, B = qp(C, D) +* A is the decoded version of the largest prefix of C .. D that +* can be decoded without doubts. +* B has the remaining bytes of C .. D, *without* decoding. +\*-------------------------------------------------------------------------*/ +static int mime_global_unqp(lua_State *L) +{ + size_t asize = 0, isize = 0; + UC atom[3]; + const UC *input = (const UC *) luaL_optlstring(L, 1, NULL, &isize); + const UC *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* make sure we don't confuse buffer stuff with arguments */ + lua_settop(L, 2); + /* process first part of input */ + luaL_buffinit(L, &buffer); + while (input < last) + asize = qpdecode(*input++, atom, asize, &buffer); + input = (const UC *) luaL_optlstring(L, 2, NULL, &isize); + /* if second part is nil, we are done */ + if (!input) { + luaL_pushresult(&buffer); + if (!(*lua_tostring(L, -1))) lua_pushnil(L); + lua_pushnil(L); + return 2; + } + /* otherwise process rest of input */ + last = input + isize; + while (input < last) + asize = qpdecode(*input++, atom, asize, &buffer); + luaL_pushresult(&buffer); + lua_pushlstring(L, (char *) atom, asize); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Incrementally breaks a quoted-printed string into lines +* A, n = qpwrp(l, B, length) +* A is a copy of B, broken into lines of at most 'length' bytes. +* 'l' is how many bytes are left for the first line of B. +* 'n' is the number of bytes left in the last line of A. +* There are two complications: lines can't be broken in the middle +* of an encoded =XX, and there might be line breaks already +\*-------------------------------------------------------------------------*/ +static int mime_global_qpwrp(lua_State *L) +{ + size_t size = 0; + int left = (int) luaL_checknumber(L, 1); + const UC *input = (const UC *) luaL_optlstring(L, 2, NULL, &size); + const UC *last = input + size; + int length = (int) luaL_optnumber(L, 3, 76); + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + if (left < length) lua_pushstring(L, EQCRLF); + else lua_pushnil(L); + lua_pushnumber(L, length); + return 2; + } + /* process all input */ + luaL_buffinit(L, &buffer); + while (input < last) { + switch (*input) { + case '\r': + break; + case '\n': + left = length; + luaL_addstring(&buffer, CRLF); + break; + case '=': + if (left <= 3) { + left = length; + luaL_addstring(&buffer, EQCRLF); + } + luaL_addchar(&buffer, *input); + left--; + break; + default: + if (left <= 1) { + left = length; + luaL_addstring(&buffer, EQCRLF); + } + luaL_addchar(&buffer, *input); + left--; + break; + } + input++; + } + luaL_pushresult(&buffer); + lua_pushnumber(L, left); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Here is what we do: \n, and \r are considered candidates for line +* break. We issue *one* new line marker if any of them is seen alone, or +* followed by a different one. That is, \n\n and \r\r will issue two +* end of line markers each, but \r\n, \n\r etc will only issue *one* +* marker. This covers Mac OS, Mac OS X, VMS, Unix and DOS, as well as +* probably other more obscure conventions. +* +* c is the current character being processed +* last is the previous character +\*-------------------------------------------------------------------------*/ +#define eolcandidate(c) (c == '\r' || c == '\n') +static int eolprocess(int c, int last, const char *marker, + luaL_Buffer *buffer) +{ + if (eolcandidate(c)) { + if (eolcandidate(last)) { + if (c == last) luaL_addstring(buffer, marker); + return 0; + } else { + luaL_addstring(buffer, marker); + return c; + } + } else { + luaL_addchar(buffer, (char) c); + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Converts a string to uniform EOL convention. +* A, n = eol(o, B, marker) +* A is the converted version of the largest prefix of B that can be +* converted unambiguously. 'o' is the context returned by the previous +* call. 'n' is the new context. +\*-------------------------------------------------------------------------*/ +static int mime_global_eol(lua_State *L) +{ + int ctx = (int) luaL_checkinteger(L, 1); + size_t isize = 0; + const char *input = luaL_optlstring(L, 2, NULL, &isize); + const char *last = input + isize; + const char *marker = luaL_optstring(L, 3, CRLF); + luaL_Buffer buffer; + luaL_buffinit(L, &buffer); + /* end of input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnumber(L, 0); + return 2; + } + /* process all input */ + while (input < last) + ctx = eolprocess(*input++, ctx, marker, &buffer); + luaL_pushresult(&buffer); + lua_pushnumber(L, ctx); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Takes one byte and stuff it if needed. +\*-------------------------------------------------------------------------*/ +static size_t dot(int c, size_t state, luaL_Buffer *buffer) +{ + luaL_addchar(buffer, (char) c); + switch (c) { + case '\r': + return 1; + case '\n': + return (state == 1)? 2: 0; + case '.': + if (state == 2) + luaL_addchar(buffer, '.'); + /* Falls through. */ + default: + return 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Incrementally applies smtp stuffing to a string +* A, n = dot(l, D) +\*-------------------------------------------------------------------------*/ +static int mime_global_dot(lua_State *L) +{ + size_t isize = 0, state = (size_t) luaL_checknumber(L, 1); + const char *input = luaL_optlstring(L, 2, NULL, &isize); + const char *last = input + isize; + luaL_Buffer buffer; + /* end-of-input blackhole */ + if (!input) { + lua_pushnil(L); + lua_pushnumber(L, 2); + return 2; + } + /* process all input */ + luaL_buffinit(L, &buffer); + while (input < last) + state = dot(*input++, state, &buffer); + luaL_pushresult(&buffer); + lua_pushnumber(L, (lua_Number) state); + return 2; +} + diff --git a/framework/lualib-src/luasocket/src/mime.h b/framework/lualib-src/luasocket/src/mime.h new file mode 100644 index 0000000..4d938f4 --- /dev/null +++ b/framework/lualib-src/luasocket/src/mime.h @@ -0,0 +1,22 @@ +#ifndef MIME_H +#define MIME_H +/*=========================================================================*\ +* Core MIME support +* LuaSocket toolkit +* +* This module provides functions to implement transfer content encodings +* and formatting conforming to RFC 2045. It is used by mime.lua, which +* provide a higher level interface to this functionality. +\*=========================================================================*/ +#include "luasocket.h" + +/*-------------------------------------------------------------------------*\ +* Current MIME library version +\*-------------------------------------------------------------------------*/ +#define MIME_VERSION "MIME 1.0.3" +#define MIME_COPYRIGHT "Copyright (C) 2004-2013 Diego Nehab" +#define MIME_AUTHORS "Diego Nehab" + +LUASOCKET_API int luaopen_mime_core(lua_State *L); + +#endif /* MIME_H */ diff --git a/framework/lualib-src/luasocket/src/mime.lua b/framework/lualib-src/luasocket/src/mime.lua new file mode 100644 index 0000000..d3abac5 --- /dev/null +++ b/framework/lualib-src/luasocket/src/mime.lua @@ -0,0 +1,89 @@ +----------------------------------------------------------------------------- +-- MIME support for the Lua language. +-- Author: Diego Nehab +-- Conforming to RFCs 2045-2049 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local ltn12 = require("ltn12") +local mime = require("mime.core") +local string = require("string") +local _M = mime + +-- encode, decode and wrap algorithm tables +local encodet, decodet, wrapt = {},{},{} + +_M.encodet = encodet +_M.decodet = decodet +_M.wrapt = wrapt + +-- creates a function that chooses a filter by name from a given table +local function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else return f(opt1, opt2) end + end +end + +-- define the encoding filters +encodet['base64'] = function() + return ltn12.filter.cycle(_M.b64, "") +end + +encodet['quoted-printable'] = function(mode) + return ltn12.filter.cycle(_M.qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") +end + +-- define the decoding filters +decodet['base64'] = function() + return ltn12.filter.cycle(_M.unb64, "") +end + +decodet['quoted-printable'] = function() + return ltn12.filter.cycle(_M.unqp, "") +end + +local function format(chunk) + if chunk then + if chunk == "" then return "''" + else return string.len(chunk) end + else return "nil" end +end + +-- define the line-wrap filters +wrapt['text'] = function(length) + length = length or 76 + return ltn12.filter.cycle(_M.wrp, length, length) +end +wrapt['base64'] = wrapt['text'] +wrapt['default'] = wrapt['text'] + +wrapt['quoted-printable'] = function() + return ltn12.filter.cycle(_M.qpwrp, 76, 76) +end + +-- function that choose the encoding, decoding or wrap algorithm +_M.encode = choose(encodet) +_M.decode = choose(decodet) +_M.wrap = choose(wrapt) + +-- define the end-of-line normalization filter +function _M.normalize(marker) + return ltn12.filter.cycle(_M.eol, 0, marker) +end + +-- high level stuffing filter +function _M.stuff() + return ltn12.filter.cycle(_M.dot, 2) +end + +return _M diff --git a/framework/lualib-src/luasocket/src/options.c b/framework/lualib-src/luasocket/src/options.c new file mode 100644 index 0000000..06ab58d --- /dev/null +++ b/framework/lualib-src/luasocket/src/options.c @@ -0,0 +1,454 @@ +/*=========================================================================*\ +* Common option interface +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" +#include "auxiliar.h" +#include "options.h" +#include "inet.h" +#include + +/*=========================================================================*\ +* Internal functions prototypes +\*=========================================================================*/ +static int opt_setmembership(lua_State *L, p_socket ps, int level, int name); +static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name); +static int opt_setboolean(lua_State *L, p_socket ps, int level, int name); +static int opt_getboolean(lua_State *L, p_socket ps, int level, int name); +static int opt_setint(lua_State *L, p_socket ps, int level, int name); +static int opt_getint(lua_State *L, p_socket ps, int level, int name); +static int opt_set(lua_State *L, p_socket ps, int level, int name, + void *val, int len); +static int opt_get(lua_State *L, p_socket ps, int level, int name, + void *val, int* len); + +/*=========================================================================*\ +* Exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Calls appropriate option handler +\*-------------------------------------------------------------------------*/ +int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps) +{ + const char *name = luaL_checkstring(L, 2); /* obj, name, ... */ + while (opt->name && strcmp(name, opt->name)) + opt++; + if (!opt->func) { + char msg[57]; + sprintf(msg, "unsupported option `%.35s'", name); + luaL_argerror(L, 2, msg); + } + return opt->func(L, ps); +} + +int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps) +{ + const char *name = luaL_checkstring(L, 2); /* obj, name, ... */ + while (opt->name && strcmp(name, opt->name)) + opt++; + if (!opt->func) { + char msg[57]; + sprintf(msg, "unsupported option `%.35s'", name); + luaL_argerror(L, 2, msg); + } + return opt->func(L, ps); +} + +// ------------------------------------------------------- +/* enables reuse of local address */ +int opt_set_reuseaddr(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); +} + +int opt_get_reuseaddr(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); +} + +// ------------------------------------------------------- +/* enables reuse of local port */ +int opt_set_reuseport(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); +} + +int opt_get_reuseport(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); +} + +// ------------------------------------------------------- +/* disables the Nagle algorithm */ +int opt_set_tcp_nodelay(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); +} + +int opt_get_tcp_nodelay(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); +} + +// ------------------------------------------------------- +#ifdef TCP_KEEPIDLE + +int opt_get_tcp_keepidle(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); +} + +int opt_set_tcp_keepidle(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPIDLE); +} + +#endif + +// ------------------------------------------------------- +#ifdef TCP_KEEPCNT + +int opt_get_tcp_keepcnt(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); +} + +int opt_set_tcp_keepcnt(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPCNT); +} + +#endif + +// ------------------------------------------------------- +#ifdef TCP_KEEPINTVL + +int opt_get_tcp_keepintvl(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); +} + +int opt_set_tcp_keepintvl(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_TCP, TCP_KEEPINTVL); +} + +#endif + +// ------------------------------------------------------- +int opt_set_keepalive(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); +} + +int opt_get_keepalive(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); +} + +// ------------------------------------------------------- +int opt_set_dontroute(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); +} + +int opt_get_dontroute(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); +} + +// ------------------------------------------------------- +int opt_set_broadcast(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, SOL_SOCKET, SO_BROADCAST); +} + +int opt_get_broadcast(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_BROADCAST); +} + +// ------------------------------------------------------- +int opt_set_recv_buf_size(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, SOL_SOCKET, SO_RCVBUF); +} + +int opt_get_recv_buf_size(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, SOL_SOCKET, SO_RCVBUF); +} + +// ------------------------------------------------------- +int opt_get_send_buf_size(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, SOL_SOCKET, SO_SNDBUF); +} + +int opt_set_send_buf_size(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, SOL_SOCKET, SO_SNDBUF); +} + +// ------------------------------------------------------- +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +} + +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +} + +// ------------------------------------------------------- +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); +} + +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); +} + +// ------------------------------------------------------- +int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); +} + +int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); +} + +// ------------------------------------------------------- +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +} + +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +} + +// ------------------------------------------------------- +int opt_set_linger(lua_State *L, p_socket ps) +{ + struct linger li; /* obj, name, table */ + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "on"); + lua_gettable(L, 3); + if (!lua_isboolean(L, -1)) + luaL_argerror(L, 3, "boolean 'on' field expected"); + li.l_onoff = (u_short) lua_toboolean(L, -1); + lua_pushstring(L, "timeout"); + lua_gettable(L, 3); + if (!lua_isnumber(L, -1)) + luaL_argerror(L, 3, "number 'timeout' field expected"); + li.l_linger = (u_short) lua_tonumber(L, -1); + return opt_set(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li)); +} + +int opt_get_linger(lua_State *L, p_socket ps) +{ + struct linger li; /* obj, name */ + int len = sizeof(li); + int err = opt_get(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, &len); + if (err) + return err; + lua_newtable(L); + lua_pushboolean(L, li.l_onoff); + lua_setfield(L, -2, "on"); + lua_pushinteger(L, li.l_linger); + lua_setfield(L, -2, "timeout"); + return 1; +} + +// ------------------------------------------------------- +int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IP, IP_MULTICAST_TTL); +} + +// ------------------------------------------------------- +int opt_set_ip_multicast_if(lua_State *L, p_socket ps) +{ + const char *address = luaL_checkstring(L, 3); /* obj, name, ip */ + struct in_addr val; + val.s_addr = htonl(INADDR_ANY); + if (strcmp(address, "*") && !inet_aton(address, &val)) + luaL_argerror(L, 3, "ip expected"); + return opt_set(L, ps, IPPROTO_IP, IP_MULTICAST_IF, + (char *) &val, sizeof(val)); +} + +int opt_get_ip_multicast_if(lua_State *L, p_socket ps) +{ + struct in_addr val; + socklen_t len = sizeof(val); + if (getsockopt(*ps, IPPROTO_IP, IP_MULTICAST_IF, (char *) &val, &len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + lua_pushstring(L, inet_ntoa(val)); + return 1; +} + +// ------------------------------------------------------- +int opt_set_ip_add_membership(lua_State *L, p_socket ps) +{ + return opt_setmembership(L, ps, IPPROTO_IP, IP_ADD_MEMBERSHIP); +} + +int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) +{ + return opt_setmembership(L, ps, IPPROTO_IP, IP_DROP_MEMBERSHIP); +} + +// ------------------------------------------------------- +int opt_set_ip6_add_membership(lua_State *L, p_socket ps) +{ + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP); +} + +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) +{ + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP); +} +// ------------------------------------------------------- +int opt_get_ip6_v6only(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); +} + +int opt_set_ip6_v6only(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); +} + +// ------------------------------------------------------- +int opt_get_error(lua_State *L, p_socket ps) +{ + int val = 0; + socklen_t len = sizeof(val); + if (getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *) &val, &len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + lua_pushstring(L, socket_strerror(val)); + return 1; +} + +/*=========================================================================*\ +* Auxiliar functions +\*=========================================================================*/ +static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) +{ + struct ip_mreq val; /* obj, name, table */ + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "multiaddr"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'multiaddr' field expected"); + if (!inet_aton(lua_tostring(L, -1), &val.imr_multiaddr)) + luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); + lua_pushstring(L, "interface"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'interface' field expected"); + val.imr_interface.s_addr = htonl(INADDR_ANY); + if (strcmp(lua_tostring(L, -1), "*") && + !inet_aton(lua_tostring(L, -1), &val.imr_interface)) + luaL_argerror(L, 3, "invalid 'interface' ip address"); + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + +static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) +{ + struct ipv6_mreq val; /* obj, opt-name, table */ + memset(&val, 0, sizeof(val)); + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "multiaddr"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'multiaddr' field expected"); + if (!inet_pton(AF_INET6, lua_tostring(L, -1), &val.ipv6mr_multiaddr)) + luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); + lua_pushstring(L, "interface"); + lua_gettable(L, 3); + /* By default we listen to interface on default route + * (sigh). However, interface= can override it. We should + * support either number, or name for it. Waiting for + * windows port of if_nametoindex */ + if (!lua_isnil(L, -1)) { + if (lua_isnumber(L, -1)) { + val.ipv6mr_interface = (unsigned int) lua_tonumber(L, -1); + } else + luaL_argerror(L, -1, "number 'interface' field expected"); + } + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + +static +int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) +{ + socklen_t socklen = *len; + if (getsockopt(*ps, level, name, (char *) val, &socklen) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + *len = socklen; + return 0; +} + +static +int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len) +{ + if (setsockopt(*ps, level, name, (char *) val, len) < 0) { + lua_pushnil(L); + lua_pushstring(L, "setsockopt failed"); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int opt_getboolean(lua_State *L, p_socket ps, int level, int name) +{ + int val = 0; + int len = sizeof(val); + int err = opt_get(L, ps, level, name, (char *) &val, &len); + if (err) + return err; + lua_pushboolean(L, val); + return 1; +} + +static int opt_setboolean(lua_State *L, p_socket ps, int level, int name) +{ + int val = auxiliar_checkboolean(L, 3); /* obj, name, bool */ + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + +static int opt_getint(lua_State *L, p_socket ps, int level, int name) +{ + int val = 0; + int len = sizeof(val); + int err = opt_get(L, ps, level, name, (char *) &val, &len); + if (err) + return err; + lua_pushnumber(L, val); + return 1; +} + +static int opt_setint(lua_State *L, p_socket ps, int level, int name) +{ + int val = (int) lua_tonumber(L, 3); /* obj, name, int */ + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} diff --git a/framework/lualib-src/luasocket/src/options.h b/framework/lualib-src/luasocket/src/options.h new file mode 100644 index 0000000..41f7337 --- /dev/null +++ b/framework/lualib-src/luasocket/src/options.h @@ -0,0 +1,102 @@ +#ifndef OPTIONS_H +#define OPTIONS_H +/*=========================================================================*\ +* Common option interface +* LuaSocket toolkit +* +* This module provides a common interface to socket options, used mainly by +* modules UDP and TCP. +\*=========================================================================*/ + +#include "luasocket.h" +#include "socket.h" + +/* option registry */ +typedef struct t_opt { + const char *name; + int (*func)(lua_State *L, p_socket ps); +} t_opt; +typedef t_opt *p_opt; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); +int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps); + +int opt_set_reuseaddr(lua_State *L, p_socket ps); +int opt_get_reuseaddr(lua_State *L, p_socket ps); + +int opt_set_reuseport(lua_State *L, p_socket ps); +int opt_get_reuseport(lua_State *L, p_socket ps); + +int opt_set_tcp_nodelay(lua_State *L, p_socket ps); +int opt_get_tcp_nodelay(lua_State *L, p_socket ps); + +#ifdef TCP_KEEPIDLE +int opt_set_tcp_keepidle(lua_State *L, p_socket ps); +int opt_get_tcp_keepidle(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_KEEPCNT +int opt_set_tcp_keepcnt(lua_State *L, p_socket ps); +int opt_get_tcp_keepcnt(lua_State *L, p_socket ps); +#endif + +#ifdef TCP_KEEPINTVL +int opt_set_tcp_keepintvl(lua_State *L, p_socket ps); +int opt_get_tcp_keepintvl(lua_State *L, p_socket ps); +#endif + +int opt_set_keepalive(lua_State *L, p_socket ps); +int opt_get_keepalive(lua_State *L, p_socket ps); + +int opt_set_dontroute(lua_State *L, p_socket ps); +int opt_get_dontroute(lua_State *L, p_socket ps); + +int opt_set_broadcast(lua_State *L, p_socket ps); +int opt_get_broadcast(lua_State *L, p_socket ps); + +int opt_set_recv_buf_size(lua_State *L, p_socket ps); +int opt_get_recv_buf_size(lua_State *L, p_socket ps); + +int opt_set_send_buf_size(lua_State *L, p_socket ps); +int opt_get_send_buf_size(lua_State *L, p_socket ps); + +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps); + +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); + +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps); + +int opt_set_linger(lua_State *L, p_socket ps); +int opt_get_linger(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps); + +int opt_set_ip_multicast_if(lua_State *L, p_socket ps); +int opt_get_ip_multicast_if(lua_State *L, p_socket ps); + +int opt_set_ip_add_membership(lua_State *L, p_socket ps); +int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); + +int opt_set_ip6_add_membership(lua_State *L, p_socket ps); +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps); + +int opt_set_ip6_v6only(lua_State *L, p_socket ps); +int opt_get_ip6_v6only(lua_State *L, p_socket ps); + +int opt_get_error(lua_State *L, p_socket ps); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif diff --git a/framework/lualib-src/luasocket/src/pierror.h b/framework/lualib-src/luasocket/src/pierror.h new file mode 100644 index 0000000..cb773ab --- /dev/null +++ b/framework/lualib-src/luasocket/src/pierror.h @@ -0,0 +1,28 @@ +#ifndef PIERROR_H +#define PIERROR_H +/*=========================================================================*\ +* Error messages +* Defines platform independent error messages +\*=========================================================================*/ + +#define PIE_HOST_NOT_FOUND "host not found" +#define PIE_ADDRINUSE "address already in use" +#define PIE_ISCONN "already connected" +#define PIE_ACCESS "permission denied" +#define PIE_CONNREFUSED "connection refused" +#define PIE_CONNABORTED "closed" +#define PIE_CONNRESET "closed" +#define PIE_TIMEDOUT "timeout" +#define PIE_AGAIN "temporary failure in name resolution" +#define PIE_BADFLAGS "invalid value for ai_flags" +#define PIE_BADHINTS "invalid value for hints" +#define PIE_FAIL "non-recoverable failure in name resolution" +#define PIE_FAMILY "ai_family not supported" +#define PIE_MEMORY "memory allocation failure" +#define PIE_NONAME "host or service not provided, or not known" +#define PIE_OVERFLOW "argument buffer overflow" +#define PIE_PROTOCOL "resolved protocol is unknown" +#define PIE_SERVICE "service not supported for socket type" +#define PIE_SOCKTYPE "ai_socktype not supported" + +#endif diff --git a/framework/lualib-src/luasocket/src/select.c b/framework/lualib-src/luasocket/src/select.c new file mode 100644 index 0000000..bb47c45 --- /dev/null +++ b/framework/lualib-src/luasocket/src/select.c @@ -0,0 +1,214 @@ +/*=========================================================================*\ +* Select implementation +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "socket.h" +#include "timeout.h" +#include "select.h" + +#include + +/*=========================================================================*\ +* Internal function prototypes. +\*=========================================================================*/ +static t_socket getfd(lua_State *L); +static int dirty(lua_State *L); +static void collect_fd(lua_State *L, int tab, int itab, + fd_set *set, t_socket *max_fd); +static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set); +static void return_fd(lua_State *L, fd_set *set, t_socket max_fd, + int itab, int tab, int start); +static void make_assoc(lua_State *L, int tab); +static int global_select(lua_State *L); + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"select", global_select}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int select_open(lua_State *L) { + lua_pushstring(L, "_SETSIZE"); + lua_pushinteger(L, FD_SETSIZE); + lua_rawset(L, -3); + lua_pushstring(L, "_SOCKETINVALID"); + lua_pushinteger(L, SOCKET_INVALID); + lua_rawset(L, -3); + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Global Lua functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Waits for a set of sockets until a condition is met or timeout. +\*-------------------------------------------------------------------------*/ +static int global_select(lua_State *L) { + int rtab, wtab, itab, ret, ndirty; + t_socket max_fd = SOCKET_INVALID; + fd_set rset, wset; + t_timeout tm; + double t = luaL_optnumber(L, 3, -1); + FD_ZERO(&rset); FD_ZERO(&wset); + lua_settop(L, 3); + lua_newtable(L); itab = lua_gettop(L); + lua_newtable(L); rtab = lua_gettop(L); + lua_newtable(L); wtab = lua_gettop(L); + collect_fd(L, 1, itab, &rset, &max_fd); + collect_fd(L, 2, itab, &wset, &max_fd); + ndirty = check_dirty(L, 1, rtab, &rset); + t = ndirty > 0? 0.0: t; + timeout_init(&tm, t, -1); + timeout_markstart(&tm); + ret = socket_select(max_fd+1, &rset, &wset, NULL, &tm); + if (ret > 0 || ndirty > 0) { + return_fd(L, &rset, max_fd+1, itab, rtab, ndirty); + return_fd(L, &wset, max_fd+1, itab, wtab, 0); + make_assoc(L, rtab); + make_assoc(L, wtab); + return 2; + } else if (ret == 0) { + lua_pushstring(L, "timeout"); + return 3; + } else { + luaL_error(L, "select failed"); + return 3; + } +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +static t_socket getfd(lua_State *L) { + t_socket fd = SOCKET_INVALID; + lua_pushstring(L, "getfd"); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + if (lua_isnumber(L, -1)) { + double numfd = lua_tonumber(L, -1); + fd = (numfd >= 0.0)? (t_socket) numfd: SOCKET_INVALID; + } + } + lua_pop(L, 1); + return fd; +} + +static int dirty(lua_State *L) { + int is = 0; + lua_pushstring(L, "dirty"); + lua_gettable(L, -2); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -2); + lua_call(L, 1, 1); + is = lua_toboolean(L, -1); + } + lua_pop(L, 1); + return is; +} + +static void collect_fd(lua_State *L, int tab, int itab, + fd_set *set, t_socket *max_fd) { + int i = 1, n = 0; + /* nil is the same as an empty table */ + if (lua_isnil(L, tab)) return; + /* otherwise we need it to be a table */ + luaL_checktype(L, tab, LUA_TTABLE); + for ( ;; ) { + t_socket fd; + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + /* getfd figures out if this is a socket */ + fd = getfd(L); + if (fd != SOCKET_INVALID) { + /* make sure we don't overflow the fd_set */ +#ifdef _WIN32 + if (n >= FD_SETSIZE) + luaL_argerror(L, tab, "too many sockets"); +#else + if (fd >= FD_SETSIZE) + luaL_argerror(L, tab, "descriptor too large for set size"); +#endif + FD_SET(fd, set); + n++; + /* keep track of the largest descriptor so far */ + if (*max_fd == SOCKET_INVALID || *max_fd < fd) + *max_fd = fd; + /* make sure we can map back from descriptor to the object */ + lua_pushnumber(L, (lua_Number) fd); + lua_pushvalue(L, -2); + lua_settable(L, itab); + } + lua_pop(L, 1); + i = i + 1; + } +} + +static int check_dirty(lua_State *L, int tab, int dtab, fd_set *set) { + int ndirty = 0, i = 1; + if (lua_isnil(L, tab)) + return 0; + for ( ;; ) { + t_socket fd; + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + fd = getfd(L); + if (fd != SOCKET_INVALID && dirty(L)) { + lua_pushnumber(L, ++ndirty); + lua_pushvalue(L, -2); + lua_settable(L, dtab); + FD_CLR(fd, set); + } + lua_pop(L, 1); + i = i + 1; + } + return ndirty; +} + +static void return_fd(lua_State *L, fd_set *set, t_socket max_fd, + int itab, int tab, int start) { + t_socket fd; + for (fd = 0; fd < max_fd; fd++) { + if (FD_ISSET(fd, set)) { + lua_pushnumber(L, ++start); + lua_pushnumber(L, (lua_Number) fd); + lua_gettable(L, itab); + lua_settable(L, tab); + } + } +} + +static void make_assoc(lua_State *L, int tab) { + int i = 1, atab; + lua_newtable(L); atab = lua_gettop(L); + for ( ;; ) { + lua_pushnumber(L, i); + lua_gettable(L, tab); + if (!lua_isnil(L, -1)) { + lua_pushnumber(L, i); + lua_pushvalue(L, -2); + lua_settable(L, atab); + lua_pushnumber(L, i); + lua_settable(L, atab); + } else { + lua_pop(L, 1); + break; + } + i = i+1; + } +} diff --git a/framework/lualib-src/luasocket/src/select.h b/framework/lualib-src/luasocket/src/select.h new file mode 100644 index 0000000..5d45fe7 --- /dev/null +++ b/framework/lualib-src/luasocket/src/select.h @@ -0,0 +1,23 @@ +#ifndef SELECT_H +#define SELECT_H +/*=========================================================================*\ +* Select implementation +* LuaSocket toolkit +* +* Each object that can be passed to the select function has to export +* method getfd() which returns the descriptor to be passed to the +* underlying select function. Another method, dirty(), should return +* true if there is data ready for reading (required for buffered input). +\*=========================================================================*/ + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int select_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* SELECT_H */ diff --git a/framework/lualib-src/luasocket/src/serial.c b/framework/lualib-src/luasocket/src/serial.c new file mode 100644 index 0000000..21485d3 --- /dev/null +++ b/framework/lualib-src/luasocket/src/serial.c @@ -0,0 +1,171 @@ +/*=========================================================================*\ +* Serial stream +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unix.h" + +#include +#include + +/* +Reuses userdata definition from unix.h, since it is useful for all +stream-like objects. + +If we stored the serial path for use in error messages or userdata +printing, we might need our own userdata definition. + +Group usage is semi-inherited from unix.c, but unnecessary since we +have only one object type. +*/ + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_send(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_close(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); + +/* serial object methods */ +static luaL_Reg serial_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"close", meth_close}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"settimeout", meth_settimeout}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_serial(lua_State *L) { + /* create classes */ + auxiliar_newclass(L, "serial{client}", serial_methods); + /* create class groups */ + auxiliar_add2group(L, "serial{client}", "serial{any}"); + lua_pushcfunction(L, global_create); + return 1; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_send(L, &un->buf); +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_receive(L, &un->buf); +} + +static int meth_getstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_getstats(L, &un->buf); +} + +static int meth_setstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_setstats(L, &un->buf); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + lua_pushboolean(L, !buffer_isempty(&un->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ + + +/*-------------------------------------------------------------------------*\ +* Creates a serial object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) { + const char* path = luaL_checkstring(L, 1); + + /* allocate unix object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + + /* open serial device */ + t_socket sock = open(path, O_NOCTTY|O_RDWR); + + /*printf("open %s on %d\n", path, sock);*/ + + if (sock < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + lua_pushnumber(L, errno); + return 3; + } + /* set its type as client object */ + auxiliar_setclass(L, "serial{client}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; +} diff --git a/framework/lualib-src/luasocket/src/smtp.lua b/framework/lualib-src/luasocket/src/smtp.lua new file mode 100644 index 0000000..b113d00 --- /dev/null +++ b/framework/lualib-src/luasocket/src/smtp.lua @@ -0,0 +1,256 @@ +----------------------------------------------------------------------------- +-- SMTP client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local coroutine = require("coroutine") +local string = require("string") +local math = require("math") +local os = require("os") +local socket = require("socket") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +local headers = require("socket.headers") +local mime = require("mime") + +socket.smtp = {} +local _M = socket.smtp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout for connection +_M.TIMEOUT = 60 +-- default server used to send e-mails +_M.SERVER = "localhost" +-- default port +_M.PORT = 25 +-- domain used in HELO command and default sendmail +-- If we are under a CGI, try to get from environment +_M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" +-- default time zone (means we don't know) +_M.ZONE = "-0000" + +--------------------------------------------------------------------------- +-- Low level SMTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function metat.__index:greet(domain) + self.try(self.tp:check("2..")) + self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) + return socket.skip(1, self.try(self.tp:check("2.."))) +end + +function metat.__index:mail(from) + self.try(self.tp:command("MAIL", "FROM:" .. from)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:rcpt(to) + self.try(self.tp:command("RCPT", "TO:" .. to)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:data(src, step) + self.try(self.tp:command("DATA")) + self.try(self.tp:check("3..")) + self.try(self.tp:source(src, step)) + self.try(self.tp:send("\r\n.\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:quit() + self.try(self.tp:command("QUIT")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:close() + return self.tp:close() +end + +function metat.__index:login(user, password) + self.try(self.tp:command("AUTH", "LOGIN")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(user) .. "\r\n")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(password) .. "\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:plain(user, password) + local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) + self.try(self.tp:command("AUTH", auth)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:auth(user, password, ext) + if not user or not password then return 1 end + if string.find(ext, "AUTH[^\n]+LOGIN") then + return self:login(user, password) + elseif string.find(ext, "AUTH[^\n]+PLAIN") then + return self:plain(user, password) + else + self.try(nil, "authentication not supported") + end +end + +-- send message or throw an exception +function metat.__index:send(mailt) + self:mail(mailt.from) + if base.type(mailt.rcpt) == "table" then + for i,v in base.ipairs(mailt.rcpt) do + self:rcpt(v) + end + else + self:rcpt(mailt.rcpt) + end + self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) +end + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, + _M.TIMEOUT, create)) + local s = base.setmetatable({tp = tp}, metat) + -- make sure tp is closed if we get an exception + s.try = socket.newtry(function() + s:close() + end) + return s +end + +-- convert headers to lowercase +local function lower_headers(headers) + local lower = {} + for i,v in base.pairs(headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +--------------------------------------------------------------------------- +-- Multipart message source +----------------------------------------------------------------------------- +-- returns a hopefully unique mime boundary +local seqno = 0 +local function newboundary() + seqno = seqno + 1 + return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), + math.random(0, 99999), seqno) +end + +-- send_message forward declaration +local send_message + +-- yield the headers all at once, it's faster +local function send_headers(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f,v in base.pairs(tosend) do + h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h + end + coroutine.yield(h) +end + +-- yield multipart message body from a multipart message table +local function send_multipart(mesgt) + -- make sure we have our boundary and send headers + local bd = newboundary() + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or 'multipart/mixed' + headers['content-type'] = headers['content-type'] .. + '; boundary="' .. bd .. '"' + send_headers(headers) + -- send preamble + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end + -- send each part separated by a boundary + for i, m in base.ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + send_message(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end +end + +-- yield message body from a source +local function send_source(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then coroutine.yield(nil, err) + elseif chunk then coroutine.yield(chunk) + else break end + end +end + +-- yield message body from a string +local function send_string(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from string + coroutine.yield(mesgt.body) +end + +-- message source +function send_message(mesgt) + if base.type(mesgt.body) == "table" then send_multipart(mesgt) + elseif base.type(mesgt.body) == "function" then send_source(mesgt) + else send_string(mesgt) end +end + +-- set defaul headers +local function adjust_headers(mesgt) + local lower = lower_headers(mesgt.headers) + lower["date"] = lower["date"] or + os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) + lower["x-mailer"] = lower["x-mailer"] or socket._VERSION + -- this can't be overriden + lower["mime-version"] = "1.0" + return lower +end + +function _M.message(mesgt) + mesgt.headers = adjust_headers(mesgt) + -- create and return message source + local co = coroutine.create(function() send_message(mesgt) end) + return function() + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end + end +end + +--------------------------------------------------------------------------- +-- High level SMTP API +----------------------------------------------------------------------------- +_M.send = socket.protect(function(mailt) + local s = _M.open(mailt.server, mailt.port, mailt.create) + local ext = s:greet(mailt.domain) + s:auth(mailt.user, mailt.password, ext) + s:send(mailt) + s:quit() + return s:close() +end) + +return _M \ No newline at end of file diff --git a/framework/lualib-src/luasocket/src/socket.h b/framework/lualib-src/luasocket/src/socket.h new file mode 100644 index 0000000..e541f27 --- /dev/null +++ b/framework/lualib-src/luasocket/src/socket.h @@ -0,0 +1,73 @@ +#ifndef SOCKET_H +#define SOCKET_H +/*=========================================================================*\ +* Socket compatibilization module +* LuaSocket toolkit +* +* BSD Sockets and WinSock are similar, but there are a few irritating +* differences. Also, not all *nix platforms behave the same. This module +* (and the associated usocket.h and wsocket.h) factor these differences and +* creates a interface compatible with the io.h module. +\*=========================================================================*/ +#include "io.h" + +/*=========================================================================*\ +* Platform specific compatibilization +\*=========================================================================*/ +#ifdef _WIN32 +#include "wsocket.h" +#else +#include "usocket.h" +#endif + +/*=========================================================================*\ +* The connect and accept functions accept a timeout and their +* implementations are somewhat complicated. We chose to move +* the timeout control into this module for these functions in +* order to simplify the modules that use them. +\*=========================================================================*/ +#include "timeout.h" + +/* convenient shorthand */ +typedef struct sockaddr SA; + +/*=========================================================================*\ +* Functions bellow implement a comfortable platform independent +* interface to sockets +\*=========================================================================*/ + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int socket_waitfd(p_socket ps, int sw, p_timeout tm); +int socket_open(void); +int socket_close(void); +void socket_destroy(p_socket ps); +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, p_timeout tm); +int socket_create(p_socket ps, int domain, int type, int protocol); +int socket_bind(p_socket ps, SA *addr, socklen_t addr_len); +int socket_listen(p_socket ps, int backlog); +void socket_shutdown(p_socket ps, int how); +int socket_connect(p_socket ps, SA *addr, socklen_t addr_len, p_timeout tm); +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *addr_len, p_timeout tm); +int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, SA *addr, socklen_t addr_len, p_timeout tm); +int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, p_timeout tm); +int socket_write(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); +int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); +void socket_setblocking(p_socket ps); +void socket_setnonblocking(p_socket ps); +int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp); +int socket_gethostbyname(const char *addr, struct hostent **hp); +const char *socket_hoststrerror(int err); +const char *socket_strerror(int err); +const char *socket_ioerror(p_socket ps, int err); +const char *socket_gaistrerror(int err); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* SOCKET_H */ diff --git a/framework/lualib-src/luasocket/src/socket.lua b/framework/lualib-src/luasocket/src/socket.lua new file mode 100644 index 0000000..d1c0b16 --- /dev/null +++ b/framework/lualib-src/luasocket/src/socket.lua @@ -0,0 +1,149 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") + +local _M = socket + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function _M.connect4(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet") +end + +function _M.connect6(address, port, laddress, lport) + return socket.connect(address, port, laddress, lport, "inet6") +end + +function _M.bind(host, port, backlog) + if host == "*" then host = "0.0.0.0" end + local addrinfo, err = socket.dns.getaddrinfo(host); + if not addrinfo then return nil, err end + local sock, res + err = "no info on address" + for i, alt in base.ipairs(addrinfo) do + if alt.family == "inet" then + sock, err = socket.tcp4() + else + sock, err = socket.tcp6() + end + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + res, err = sock:bind(alt.addr, port) + if not res then + sock:close() + else + res, err = sock:listen(backlog) + if not res then + sock:close() + else + return sock + end + end + end + return nil, err +end + +_M.try = _M.newtry() + +function _M.choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +local sourcet, sinkt = {}, {} +_M.sourcet = sourcet +_M.sinkt = sinkt + +_M.BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +_M.sink = _M.choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +_M.source = _M.choose(sourcet) + +return _M diff --git a/framework/lualib-src/luasocket/src/tcp.c b/framework/lualib-src/luasocket/src/tcp.c new file mode 100644 index 0000000..5876bfb --- /dev/null +++ b/framework/lualib-src/luasocket/src/tcp.c @@ -0,0 +1,471 @@ +/*=========================================================================*\ +* TCP object +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "inet.h" +#include "options.h" +#include "tcp.h" + +#include + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int global_create4(lua_State *L); +static int global_create6(lua_State *L); +static int global_connect(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_listen(lua_State *L); +static int meth_getfamily(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); +static int meth_getsockname(lua_State *L); +static int meth_getpeername(lua_State *L); +static int meth_shutdown(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_accept(lua_State *L); +static int meth_close(lua_State *L); +static int meth_getoption(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); + +/* tcp object methods */ +static luaL_Reg tcp_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"accept", meth_accept}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfamily", meth_getfamily}, + {"getfd", meth_getfd}, + {"getoption", meth_getoption}, + {"getpeername", meth_getpeername}, + {"getsockname", meth_getsockname}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"listen", meth_listen}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {"shutdown", meth_shutdown}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optget[] = { + {"keepalive", opt_get_keepalive}, + {"reuseaddr", opt_get_reuseaddr}, + {"reuseport", opt_get_reuseport}, + {"tcp-nodelay", opt_get_tcp_nodelay}, +#ifdef TCP_KEEPIDLE + {"tcp-keepidle", opt_get_tcp_keepidle}, +#endif +#ifdef TCP_KEEPCNT + {"tcp-keepcnt", opt_get_tcp_keepcnt}, +#endif +#ifdef TCP_KEEPINTVL + {"tcp-keepintvl", opt_get_tcp_keepintvl}, +#endif + {"linger", opt_get_linger}, + {"error", opt_get_error}, + {"recv-buffer-size", opt_get_recv_buf_size}, + {"send-buffer-size", opt_get_send_buf_size}, + {NULL, NULL} +}; + +static t_opt optset[] = { + {"keepalive", opt_set_keepalive}, + {"reuseaddr", opt_set_reuseaddr}, + {"reuseport", opt_set_reuseport}, + {"tcp-nodelay", opt_set_tcp_nodelay}, +#ifdef TCP_KEEPIDLE + {"tcp-keepidle", opt_set_tcp_keepidle}, +#endif +#ifdef TCP_KEEPCNT + {"tcp-keepcnt", opt_set_tcp_keepcnt}, +#endif +#ifdef TCP_KEEPINTVL + {"tcp-keepintvl", opt_set_tcp_keepintvl}, +#endif + {"ipv6-v6only", opt_set_ip6_v6only}, + {"linger", opt_set_linger}, + {"recv-buffer-size", opt_set_recv_buf_size}, + {"send-buffer-size", opt_set_send_buf_size}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"tcp", global_create}, + {"tcp4", global_create4}, + {"tcp6", global_create6}, + {"connect", global_connect}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int tcp_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "tcp{master}", tcp_methods); + auxiliar_newclass(L, "tcp{client}", tcp_methods); + auxiliar_newclass(L, "tcp{server}", tcp_methods); + /* create class groups */ + auxiliar_add2group(L, "tcp{master}", "tcp{any}"); + auxiliar_add2group(L, "tcp{client}", "tcp{any}"); + auxiliar_add2group(L, "tcp{server}", "tcp{any}"); + /* define library functions */ + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_send(L, &tcp->buf); +} + +static int meth_receive(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_receive(L, &tcp->buf); +} + +static int meth_getstats(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_getstats(L, &tcp->buf); +} + +static int meth_setstats(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + return buffer_meth_setstats(L, &tcp->buf); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_getoption(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return opt_meth_getoption(L, optget, &tcp->sock); +} + +static int meth_setoption(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return opt_meth_setoption(L, optset, &tcp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + lua_pushnumber(L, (int) tcp->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + tcp->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + lua_pushboolean(L, !buffer_isempty(&tcp->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int meth_accept(lua_State *L) +{ + p_tcp server = (p_tcp) auxiliar_checkclass(L, "tcp{server}", 1); + p_timeout tm = timeout_markstart(&server->tm); + t_socket sock; + const char *err = inet_tryaccept(&server->sock, server->family, &sock, tm); + /* if successful, push client socket */ + if (err == NULL) { + p_tcp clnt = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + auxiliar_setclass(L, "tcp{client}", -1); + /* initialize structure fields */ + memset(clnt, 0, sizeof(t_tcp)); + socket_setnonblocking(&sock); + clnt->sock = sock; + io_init(&clnt->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &clnt->sock); + timeout_init(&clnt->tm, -1, -1); + buffer_init(&clnt->buf, &clnt->io, &clnt->tm); + clnt->family = server->family; + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static int meth_bind(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); + const char *address = luaL_checkstring(L, 2); + const char *port = luaL_checkstring(L, 3); + const char *err; + struct addrinfo bindhints; + memset(&bindhints, 0, sizeof(bindhints)); + bindhints.ai_socktype = SOCK_STREAM; + bindhints.ai_family = tcp->family; + bindhints.ai_flags = AI_PASSIVE; + err = inet_trybind(&tcp->sock, &tcp->family, address, port, &bindhints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master tcp object into a client object. +\*-------------------------------------------------------------------------*/ +static int meth_connect(lua_State *L) { + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + const char *address = luaL_checkstring(L, 2); + const char *port = luaL_checkstring(L, 3); + struct addrinfo connecthints; + const char *err; + memset(&connecthints, 0, sizeof(connecthints)); + connecthints.ai_socktype = SOCK_STREAM; + /* make sure we try to connect only to the same family */ + connecthints.ai_family = tcp->family; + timeout_markstart(&tcp->tm); + err = inet_tryconnect(&tcp->sock, &tcp->family, address, port, + &tcp->tm, &connecthints); + /* have to set the class even if it failed due to non-blocking connects */ + auxiliar_setclass(L, "tcp{client}", 1); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + socket_destroy(&tcp->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Returns family as string +\*-------------------------------------------------------------------------*/ +static int meth_getfamily(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + if (tcp->family == AF_INET6) { + lua_pushliteral(L, "inet6"); + return 1; + } else if (tcp->family == AF_INET) { + lua_pushliteral(L, "inet4"); + return 1; + } else { + lua_pushliteral(L, "inet4"); + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Puts the sockt in listen mode +\*-------------------------------------------------------------------------*/ +static int meth_listen(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); + int backlog = (int) luaL_optnumber(L, 2, 32); + int err = socket_listen(&tcp->sock, backlog); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } + /* turn master object into a server object */ + auxiliar_setclass(L, "tcp{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Shuts the connection down partially +\*-------------------------------------------------------------------------*/ +static int meth_shutdown(lua_State *L) +{ + /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ + static const char* methods[] = { "receive", "send", "both", NULL }; + p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); + int how = luaL_checkoption(L, 2, "both", methods); + socket_shutdown(&tcp->sock, how); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call inet methods +\*-------------------------------------------------------------------------*/ +static int meth_getpeername(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return inet_meth_getpeername(L, &tcp->sock, tcp->family); +} + +static int meth_getsockname(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return inet_meth_getsockname(L, &tcp->sock, tcp->family); +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return timeout_meth_settimeout(L, &tcp->tm); +} + +static int meth_gettimeout(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return timeout_meth_gettimeout(L, &tcp->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master tcp object +\*-------------------------------------------------------------------------*/ +static int tcp_create(lua_State *L, int family) { + p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + memset(tcp, 0, sizeof(t_tcp)); + /* set its type as master object */ + auxiliar_setclass(L, "tcp{master}", -1); + /* if family is AF_UNSPEC, we leave the socket invalid and + * store AF_UNSPEC into family. This will allow it to later be + * replaced with an AF_INET6 or AF_INET socket upon first use. */ + tcp->sock = SOCKET_INVALID; + tcp->family = family; + io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &tcp->sock); + timeout_init(&tcp->tm, -1, -1); + buffer_init(&tcp->buf, &tcp->io, &tcp->tm); + if (family != AF_UNSPEC) { + const char *err = inet_trycreate(&tcp->sock, family, SOCK_STREAM, 0); + if (err != NULL) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + socket_setnonblocking(&tcp->sock); + } + return 1; +} + +static int global_create(lua_State *L) { + return tcp_create(L, AF_UNSPEC); +} + +static int global_create4(lua_State *L) { + return tcp_create(L, AF_INET); +} + +static int global_create6(lua_State *L) { + return tcp_create(L, AF_INET6); +} + +static int global_connect(lua_State *L) { + const char *remoteaddr = luaL_checkstring(L, 1); + const char *remoteserv = luaL_checkstring(L, 2); + const char *localaddr = luaL_optstring(L, 3, NULL); + const char *localserv = luaL_optstring(L, 4, "0"); + int family = inet_optfamily(L, 5, "unspec"); + p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); + struct addrinfo bindhints, connecthints; + const char *err = NULL; + /* initialize tcp structure */ + memset(tcp, 0, sizeof(t_tcp)); + io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &tcp->sock); + timeout_init(&tcp->tm, -1, -1); + buffer_init(&tcp->buf, &tcp->io, &tcp->tm); + tcp->sock = SOCKET_INVALID; + tcp->family = AF_UNSPEC; + /* allow user to pick local address and port */ + memset(&bindhints, 0, sizeof(bindhints)); + bindhints.ai_socktype = SOCK_STREAM; + bindhints.ai_family = family; + bindhints.ai_flags = AI_PASSIVE; + if (localaddr) { + err = inet_trybind(&tcp->sock, &tcp->family, localaddr, + localserv, &bindhints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + } + /* try to connect to remote address and port */ + memset(&connecthints, 0, sizeof(connecthints)); + connecthints.ai_socktype = SOCK_STREAM; + /* make sure we try to connect only to the same family */ + connecthints.ai_family = tcp->family; + err = inet_tryconnect(&tcp->sock, &tcp->family, remoteaddr, remoteserv, + &tcp->tm, &connecthints); + if (err) { + socket_destroy(&tcp->sock); + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + auxiliar_setclass(L, "tcp{client}", -1); + return 1; +} diff --git a/framework/lualib-src/luasocket/src/tcp.h b/framework/lualib-src/luasocket/src/tcp.h new file mode 100644 index 0000000..9b282ef --- /dev/null +++ b/framework/lualib-src/luasocket/src/tcp.h @@ -0,0 +1,43 @@ +#ifndef TCP_H +#define TCP_H +/*=========================================================================*\ +* TCP object +* LuaSocket toolkit +* +* The tcp.h module is basicly a glue that puts together modules buffer.h, +* timeout.h socket.h and inet.h to provide the LuaSocket TCP (AF_INET, +* SOCK_STREAM) support. +* +* Three classes are defined: master, client and server. The master class is +* a newly created tcp object, that has not been bound or connected. Server +* objects are tcp objects bound to some local address. Client objects are +* tcp objects either connected to some address or returned by the accept +* method of a server object. +\*=========================================================================*/ +#include "luasocket.h" + +#include "buffer.h" +#include "timeout.h" +#include "socket.h" + +typedef struct t_tcp_ { + t_socket sock; + t_io io; + t_buffer buf; + t_timeout tm; + int family; +} t_tcp; + +typedef t_tcp *p_tcp; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int tcp_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* TCP_H */ diff --git a/framework/lualib-src/luasocket/src/timeout.c b/framework/lualib-src/luasocket/src/timeout.c new file mode 100644 index 0000000..2bdc069 --- /dev/null +++ b/framework/lualib-src/luasocket/src/timeout.c @@ -0,0 +1,226 @@ +/*=========================================================================*\ +* Timeout management functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "timeout.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int timeout_lua_gettime(lua_State *L); +static int timeout_lua_sleep(lua_State *L); + +static luaL_Reg func[] = { + { "gettime", timeout_lua_gettime }, + { "sleep", timeout_lua_sleep }, + { NULL, NULL } +}; + +/*=========================================================================*\ +* Exported functions. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initialize structure +\*-------------------------------------------------------------------------*/ +void timeout_init(p_timeout tm, double block, double total) { + tm->block = block; + tm->total = total; +} + +/*-------------------------------------------------------------------------*\ +* Determines how much time we have left for the next system call, +* if the previous call was successful +* Input +* tm: timeout control structure +* Returns +* the number of ms left or -1 if there is no time limit +\*-------------------------------------------------------------------------*/ +double timeout_get(p_timeout tm) { + if (tm->block < 0.0 && tm->total < 0.0) { + return -1; + } else if (tm->block < 0.0) { + double t = tm->total - timeout_gettime() + tm->start; + return MAX(t, 0.0); + } else if (tm->total < 0.0) { + return tm->block; + } else { + double t = tm->total - timeout_gettime() + tm->start; + return MIN(tm->block, MAX(t, 0.0)); + } +} + +/*-------------------------------------------------------------------------*\ +* Returns time since start of operation +* Input +* tm: timeout control structure +* Returns +* start field of structure +\*-------------------------------------------------------------------------*/ +double timeout_getstart(p_timeout tm) { + return tm->start; +} + +/*-------------------------------------------------------------------------*\ +* Determines how much time we have left for the next system call, +* if the previous call was a failure +* Input +* tm: timeout control structure +* Returns +* the number of ms left or -1 if there is no time limit +\*-------------------------------------------------------------------------*/ +double timeout_getretry(p_timeout tm) { + if (tm->block < 0.0 && tm->total < 0.0) { + return -1; + } else if (tm->block < 0.0) { + double t = tm->total - timeout_gettime() + tm->start; + return MAX(t, 0.0); + } else if (tm->total < 0.0) { + double t = tm->block - timeout_gettime() + tm->start; + return MAX(t, 0.0); + } else { + double t = tm->total - timeout_gettime() + tm->start; + return MIN(tm->block, MAX(t, 0.0)); + } +} + +/*-------------------------------------------------------------------------*\ +* Marks the operation start time in structure +* Input +* tm: timeout control structure +\*-------------------------------------------------------------------------*/ +p_timeout timeout_markstart(p_timeout tm) { + tm->start = timeout_gettime(); + return tm; +} + +/*-------------------------------------------------------------------------*\ +* Gets time in s, relative to January 1, 1970 (UTC) +* Returns +* time in s. +\*-------------------------------------------------------------------------*/ +#ifdef _WIN32 +double timeout_gettime(void) { + FILETIME ft; + double t; + GetSystemTimeAsFileTime(&ft); + /* Windows file time (time since January 1, 1601 (UTC)) */ + t = ft.dwLowDateTime/1.0e7 + ft.dwHighDateTime*(4294967296.0/1.0e7); + /* convert to Unix Epoch time (time since January 1, 1970 (UTC)) */ + return (t - 11644473600.0); +} +#else +double timeout_gettime(void) { + struct timeval v; + gettimeofday(&v, (struct timezone *) NULL); + /* Unix Epoch time (time since January 1, 1970 (UTC)) */ + return v.tv_sec + v.tv_usec/1.0e6; +} +#endif + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int timeout_open(lua_State *L) { + luaL_setfuncs(L, func, 0); + return 0; +} + +/*-------------------------------------------------------------------------*\ +* Sets timeout values for IO operations +* Lua Input: base, time [, mode] +* time: time out value in seconds +* mode: "b" for block timeout, "t" for total timeout. (default: b) +\*-------------------------------------------------------------------------*/ +int timeout_meth_settimeout(lua_State *L, p_timeout tm) { + double t = luaL_optnumber(L, 2, -1); + const char *mode = luaL_optstring(L, 3, "b"); + switch (*mode) { + case 'b': + tm->block = t; + break; + case 'r': case 't': + tm->total = t; + break; + default: + luaL_argcheck(L, 0, 3, "invalid timeout mode"); + break; + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets timeout values for IO operations +* Lua Output: block, total +\*-------------------------------------------------------------------------*/ +int timeout_meth_gettimeout(lua_State *L, p_timeout tm) { + lua_pushnumber(L, tm->block); + lua_pushnumber(L, tm->total); + return 2; +} + +/*=========================================================================*\ +* Test support functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Returns the time the system has been up, in secconds. +\*-------------------------------------------------------------------------*/ +static int timeout_lua_gettime(lua_State *L) +{ + lua_pushnumber(L, timeout_gettime()); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Sleep for n seconds. +\*-------------------------------------------------------------------------*/ +#ifdef _WIN32 +int timeout_lua_sleep(lua_State *L) +{ + double n = luaL_checknumber(L, 1); + if (n < 0.0) n = 0.0; + if (n < DBL_MAX/1000.0) n *= 1000.0; + if (n > INT_MAX) n = INT_MAX; + Sleep((int)n); + return 0; +} +#else +int timeout_lua_sleep(lua_State *L) +{ + double n = luaL_checknumber(L, 1); + struct timespec t, r; + if (n < 0.0) n = 0.0; + if (n > INT_MAX) n = INT_MAX; + t.tv_sec = (int) n; + n -= t.tv_sec; + t.tv_nsec = (int) (n * 1000000000); + if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999; + while (nanosleep(&t, &r) != 0) { + t.tv_sec = r.tv_sec; + t.tv_nsec = r.tv_nsec; + } + return 0; +} +#endif diff --git a/framework/lualib-src/luasocket/src/timeout.h b/framework/lualib-src/luasocket/src/timeout.h new file mode 100644 index 0000000..9e5250d --- /dev/null +++ b/framework/lualib-src/luasocket/src/timeout.h @@ -0,0 +1,40 @@ +#ifndef TIMEOUT_H +#define TIMEOUT_H +/*=========================================================================*\ +* Timeout management functions +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +/* timeout control structure */ +typedef struct t_timeout_ { + double block; /* maximum time for blocking calls */ + double total; /* total number of miliseconds for operation */ + double start; /* time of start of operation */ +} t_timeout; +typedef t_timeout *p_timeout; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +void timeout_init(p_timeout tm, double block, double total); +double timeout_get(p_timeout tm); +double timeout_getstart(p_timeout tm); +double timeout_getretry(p_timeout tm); +p_timeout timeout_markstart(p_timeout tm); + +double timeout_gettime(void); + +int timeout_open(lua_State *L); + +int timeout_meth_settimeout(lua_State *L, p_timeout tm); +int timeout_meth_gettimeout(lua_State *L, p_timeout tm); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#define timeout_iszero(tm) ((tm)->block == 0.0) + +#endif /* TIMEOUT_H */ diff --git a/framework/lualib-src/luasocket/src/tp.lua b/framework/lualib-src/luasocket/src/tp.lua new file mode 100644 index 0000000..b8ebc56 --- /dev/null +++ b/framework/lualib-src/luasocket/src/tp.lua @@ -0,0 +1,134 @@ +----------------------------------------------------------------------------- +-- Unified SMTP/FTP subsystem +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local socket = require("socket") +local ltn12 = require("ltn12") + +socket.tp = {} +local _M = socket.tp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +_M.TIMEOUT = 60 + +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- +-- gets server reply (works for SMTP and FTP) +local function get_reply(c) + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply +end + +-- metatable for sock object +local metat = { __index = {} } + +function metat.__index:getpeername() + return self.c:getpeername() +end + +function metat.__index:getsockname() + return self.c:getpeername() +end + +function metat.__index:check(ok) + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply + end + end + return nil, reply + else + if string.find(code, ok) then return base.tonumber(code), reply + else return nil, reply end + end + else return ok(base.tonumber(code), reply) end +end + +function metat.__index:command(cmd, arg) + cmd = string.upper(cmd) + if arg then + return self.c:send(cmd .. " " .. arg.. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end +end + +function metat.__index:sink(snk, pat) + local chunk, err = self.c:receive(pat) + return snk(chunk, err) +end + +function metat.__index:send(data) + return self.c:send(data) +end + +function metat.__index:receive(pat) + return self.c:receive(pat) +end + +function metat.__index:getfd() + return self.c:getfd() +end + +function metat.__index:dirty() + return self.c:dirty() +end + +function metat.__index:getcontrol() + return self.c +end + +function metat.__index:source(source, step) + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err +end + +-- closes the underlying c +function metat.__index:close() + self.c:close() + return 1 +end + +-- connect with server and return c object +function _M.connect(host, port, timeout, create) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or _M.TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({c = c}, metat) +end + +return _M diff --git a/framework/lualib-src/luasocket/src/udp.c b/framework/lualib-src/luasocket/src/udp.c new file mode 100644 index 0000000..62b6a20 --- /dev/null +++ b/framework/lualib-src/luasocket/src/udp.c @@ -0,0 +1,488 @@ +/*=========================================================================*\ +* UDP object +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "inet.h" +#include "options.h" +#include "udp.h" + +#include +#include + +/* min and max macros */ +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? x : y) +#endif +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int global_create4(lua_State *L); +static int global_create6(lua_State *L); +static int meth_send(lua_State *L); +static int meth_sendto(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_receivefrom(lua_State *L); +static int meth_getfamily(lua_State *L); +static int meth_getsockname(lua_State *L); +static int meth_getpeername(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_setsockname(lua_State *L); +static int meth_setpeername(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_getoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); + +/* udp object methods */ +static luaL_Reg udp_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"close", meth_close}, + {"dirty", meth_dirty}, + {"getfamily", meth_getfamily}, + {"getfd", meth_getfd}, + {"getpeername", meth_getpeername}, + {"getsockname", meth_getsockname}, + {"receive", meth_receive}, + {"receivefrom", meth_receivefrom}, + {"send", meth_send}, + {"sendto", meth_sendto}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"getoption", meth_getoption}, + {"setpeername", meth_setpeername}, + {"setsockname", meth_setsockname}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {NULL, NULL} +}; + +/* socket options for setoption */ +static t_opt optset[] = { + {"dontroute", opt_set_dontroute}, + {"broadcast", opt_set_broadcast}, + {"reuseaddr", opt_set_reuseaddr}, + {"reuseport", opt_set_reuseport}, + {"ip-multicast-if", opt_set_ip_multicast_if}, + {"ip-multicast-ttl", opt_set_ip_multicast_ttl}, + {"ip-multicast-loop", opt_set_ip_multicast_loop}, + {"ip-add-membership", opt_set_ip_add_membership}, + {"ip-drop-membership", opt_set_ip_drop_membersip}, + {"ipv6-unicast-hops", opt_set_ip6_unicast_hops}, + {"ipv6-multicast-hops", opt_set_ip6_unicast_hops}, + {"ipv6-multicast-loop", opt_set_ip6_multicast_loop}, + {"ipv6-add-membership", opt_set_ip6_add_membership}, + {"ipv6-drop-membership", opt_set_ip6_drop_membersip}, + {"ipv6-v6only", opt_set_ip6_v6only}, + {"recv-buffer-size", opt_set_recv_buf_size}, + {"send-buffer-size", opt_set_send_buf_size}, + {NULL, NULL} +}; + +/* socket options for getoption */ +static t_opt optget[] = { + {"dontroute", opt_get_dontroute}, + {"broadcast", opt_get_broadcast}, + {"reuseaddr", opt_get_reuseaddr}, + {"reuseport", opt_get_reuseport}, + {"ip-multicast-if", opt_get_ip_multicast_if}, + {"ip-multicast-loop", opt_get_ip_multicast_loop}, + {"error", opt_get_error}, + {"ipv6-unicast-hops", opt_get_ip6_unicast_hops}, + {"ipv6-multicast-hops", opt_get_ip6_unicast_hops}, + {"ipv6-multicast-loop", opt_get_ip6_multicast_loop}, + {"ipv6-v6only", opt_get_ip6_v6only}, + {"recv-buffer-size", opt_get_recv_buf_size}, + {"send-buffer-size", opt_get_send_buf_size}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"udp", global_create}, + {"udp4", global_create4}, + {"udp6", global_create6}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int udp_open(lua_State *L) { + /* create classes */ + auxiliar_newclass(L, "udp{connected}", udp_methods); + auxiliar_newclass(L, "udp{unconnected}", udp_methods); + /* create class groups */ + auxiliar_add2group(L, "udp{connected}", "udp{any}"); + auxiliar_add2group(L, "udp{unconnected}", "udp{any}"); + auxiliar_add2group(L, "udp{connected}", "select{able}"); + auxiliar_add2group(L, "udp{unconnected}", "select{able}"); + /* define library functions */ + luaL_setfuncs(L, func, 0); + /* export default UDP size */ + lua_pushliteral(L, "_DATAGRAMSIZE"); + lua_pushinteger(L, UDP_DATAGRAMSIZE); + lua_rawset(L, -3); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +static const char *udp_strerror(int err) { + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + if (err == IO_CLOSED) return "refused"; + else return socket_strerror(err); +} + +/*-------------------------------------------------------------------------*\ +* Send data through connected udp socket +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{connected}", 1); + p_timeout tm = &udp->tm; + size_t count, sent = 0; + int err; + const char *data = luaL_checklstring(L, 2, &count); + timeout_markstart(tm); + err = socket_send(&udp->sock, data, count, &sent, tm); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Send data through unconnected udp socket +\*-------------------------------------------------------------------------*/ +static int meth_sendto(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + size_t count, sent = 0; + const char *data = luaL_checklstring(L, 2, &count); + const char *ip = luaL_checkstring(L, 3); + const char *port = luaL_checkstring(L, 4); + p_timeout tm = &udp->tm; + int err; + struct addrinfo aihint; + struct addrinfo *ai; + memset(&aihint, 0, sizeof(aihint)); + aihint.ai_family = udp->family; + aihint.ai_socktype = SOCK_DGRAM; + aihint.ai_flags = AI_NUMERICHOST; +#ifdef AI_NUMERICSERV + aihint.ai_flags |= AI_NUMERICSERV; +#endif + err = getaddrinfo(ip, port, &aihint, &ai); + if (err) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + return 2; + } + + /* create socket if on first sendto if AF_UNSPEC was set */ + if (udp->family == AF_UNSPEC && udp->sock == SOCKET_INVALID) { + struct addrinfo *ap; + const char *errstr = NULL; + for (ap = ai; ap != NULL; ap = ap->ai_next) { + errstr = inet_trycreate(&udp->sock, ap->ai_family, SOCK_DGRAM, 0); + if (errstr == NULL) { + socket_setnonblocking(&udp->sock); + udp->family = ap->ai_family; + break; + } + } + if (errstr != NULL) { + lua_pushnil(L); + lua_pushstring(L, errstr); + freeaddrinfo(ai); + return 2; + } + } + + timeout_markstart(tm); + err = socket_sendto(&udp->sock, data, count, &sent, ai->ai_addr, + (socklen_t) ai->ai_addrlen, tm); + freeaddrinfo(ai); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data from a UDP socket +\*-------------------------------------------------------------------------*/ +static int meth_receive(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + char buf[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + int err; + p_timeout tm = &udp->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recv(&udp->sock, dgram, wanted, &got, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + if (wanted > sizeof(buf)) free(dgram); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data and sender from a UDP socket +\*-------------------------------------------------------------------------*/ +static int meth_receivefrom(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + char buf[UDP_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + int err; + p_timeout tm = &udp->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recvfrom(&udp->sock, dgram, wanted, &got, (SA *) &addr, + &addr_len, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, + INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); + if (err) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + lua_pushstring(L, addrstr); + lua_pushinteger(L, (int) strtol(portstr, (char **) NULL, 10)); + if (wanted > sizeof(buf)) free(dgram); + return 3; +} + +/*-------------------------------------------------------------------------*\ +* Returns family as string +\*-------------------------------------------------------------------------*/ +static int meth_getfamily(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + if (udp->family == AF_INET6) { + lua_pushliteral(L, "inet6"); + return 1; + } else { + lua_pushliteral(L, "inet4"); + return 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + lua_pushnumber(L, (int) udp->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + udp->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + (void) udp; + lua_pushboolean(L, 0); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call inet methods +\*-------------------------------------------------------------------------*/ +static int meth_getpeername(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{connected}", 1); + return inet_meth_getpeername(L, &udp->sock, udp->family); +} + +static int meth_getsockname(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return inet_meth_getsockname(L, &udp->sock, udp->family); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return opt_meth_setoption(L, optset, &udp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_getoption(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return opt_meth_getoption(L, optget, &udp->sock); +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return timeout_meth_settimeout(L, &udp->tm); +} + +static int meth_gettimeout(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + return timeout_meth_gettimeout(L, &udp->tm); +} + +/*-------------------------------------------------------------------------*\ +* Turns a master udp object into a client object. +\*-------------------------------------------------------------------------*/ +static int meth_setpeername(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + p_timeout tm = &udp->tm; + const char *address = luaL_checkstring(L, 2); + int connecting = strcmp(address, "*"); + const char *port = connecting? luaL_checkstring(L, 3): "0"; + struct addrinfo connecthints; + const char *err; + memset(&connecthints, 0, sizeof(connecthints)); + connecthints.ai_socktype = SOCK_DGRAM; + /* make sure we try to connect only to the same family */ + connecthints.ai_family = udp->family; + if (connecting) { + err = inet_tryconnect(&udp->sock, &udp->family, address, + port, tm, &connecthints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + auxiliar_setclass(L, "udp{connected}", 1); + } else { + /* we ignore possible errors because Mac OS X always + * returns EAFNOSUPPORT */ + inet_trydisconnect(&udp->sock, udp->family, tm); + auxiliar_setclass(L, "udp{unconnected}", 1); + } + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkgroup(L, "udp{any}", 1); + socket_destroy(&udp->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master object into a server object +\*-------------------------------------------------------------------------*/ +static int meth_setsockname(lua_State *L) { + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + const char *address = luaL_checkstring(L, 2); + const char *port = luaL_checkstring(L, 3); + const char *err; + struct addrinfo bindhints; + memset(&bindhints, 0, sizeof(bindhints)); + bindhints.ai_socktype = SOCK_DGRAM; + bindhints.ai_family = udp->family; + bindhints.ai_flags = AI_PASSIVE; + err = inet_trybind(&udp->sock, &udp->family, address, port, &bindhints); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master udp object +\*-------------------------------------------------------------------------*/ +static int udp_create(lua_State *L, int family) { + /* allocate udp object */ + p_udp udp = (p_udp) lua_newuserdata(L, sizeof(t_udp)); + auxiliar_setclass(L, "udp{unconnected}", -1); + /* if family is AF_UNSPEC, we leave the socket invalid and + * store AF_UNSPEC into family. This will allow it to later be + * replaced with an AF_INET6 or AF_INET socket upon first use. */ + udp->sock = SOCKET_INVALID; + timeout_init(&udp->tm, -1, -1); + udp->family = family; + if (family != AF_UNSPEC) { + const char *err = inet_trycreate(&udp->sock, family, SOCK_DGRAM, 0); + if (err != NULL) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + socket_setnonblocking(&udp->sock); + } + return 1; +} + +static int global_create(lua_State *L) { + return udp_create(L, AF_UNSPEC); +} + +static int global_create4(lua_State *L) { + return udp_create(L, AF_INET); +} + +static int global_create6(lua_State *L) { + return udp_create(L, AF_INET6); +} diff --git a/framework/lualib-src/luasocket/src/udp.h b/framework/lualib-src/luasocket/src/udp.h new file mode 100644 index 0000000..07d5247 --- /dev/null +++ b/framework/lualib-src/luasocket/src/udp.h @@ -0,0 +1,39 @@ +#ifndef UDP_H +#define UDP_H +/*=========================================================================*\ +* UDP object +* LuaSocket toolkit +* +* The udp.h module provides LuaSocket with support for UDP protocol +* (AF_INET, SOCK_DGRAM). +* +* Two classes are defined: connected and unconnected. UDP objects are +* originally unconnected. They can be "connected" to a given address +* with a call to the setpeername function. The same function can be used to +* break the connection. +\*=========================================================================*/ +#include "luasocket.h" + +#include "timeout.h" +#include "socket.h" + +#define UDP_DATAGRAMSIZE 8192 + +typedef struct t_udp_ { + t_socket sock; + t_timeout tm; + int family; +} t_udp; +typedef t_udp *p_udp; + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int udp_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UDP_H */ diff --git a/framework/lualib-src/luasocket/src/unix.c b/framework/lualib-src/luasocket/src/unix.c new file mode 100644 index 0000000..268d8b2 --- /dev/null +++ b/framework/lualib-src/luasocket/src/unix.c @@ -0,0 +1,69 @@ +/*=========================================================================*\ +* Unix domain socket +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "unixstream.h" +#include "unixdgram.h" + +/*-------------------------------------------------------------------------*\ +* Modules and functions +\*-------------------------------------------------------------------------*/ +static const luaL_Reg mod[] = { + {"stream", unixstream_open}, + {"dgram", unixdgram_open}, + {NULL, NULL} +}; + +static void add_alias(lua_State *L, int index, const char *name, const char *target) +{ + lua_getfield(L, index, target); + lua_setfield(L, index, name); +} + +static int compat_socket_unix_call(lua_State *L) +{ + /* Look up socket.unix.stream in the socket.unix table (which is the first + * argument). */ + lua_getfield(L, 1, "stream"); + + /* Replace the stack entry for the socket.unix table with the + * socket.unix.stream function. */ + lua_replace(L, 1); + + /* Call socket.unix.stream, passing along any arguments. */ + int n = lua_gettop(L); + lua_call(L, n-1, LUA_MULTRET); + + /* Pass along the return values from socket.unix.stream. */ + n = lua_gettop(L); + return n; +} + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_unix(lua_State *L) +{ + int i; + lua_newtable(L); + int socket_unix_table = lua_gettop(L); + + for (i = 0; mod[i].name; i++) + mod[i].func(L); + + /* Add backwards compatibility aliases "tcp" and "udp" for the "stream" and + * "dgram" functions. */ + add_alias(L, socket_unix_table, "tcp", "stream"); + add_alias(L, socket_unix_table, "udp", "dgram"); + + /* Add a backwards compatibility function and a metatable setup to call it + * for the old socket.unix() interface. */ + lua_pushcfunction(L, compat_socket_unix_call); + lua_setfield(L, socket_unix_table, "__call"); + lua_pushvalue(L, socket_unix_table); + lua_setmetatable(L, socket_unix_table); + + return 1; +} diff --git a/framework/lualib-src/luasocket/src/unix.h b/framework/lualib-src/luasocket/src/unix.h new file mode 100644 index 0000000..c203561 --- /dev/null +++ b/framework/lualib-src/luasocket/src/unix.h @@ -0,0 +1,26 @@ +#ifndef UNIX_H +#define UNIX_H +/*=========================================================================*\ +* Unix domain object +* LuaSocket toolkit +* +* This module is just an example of how to extend LuaSocket with a new +* domain. +\*=========================================================================*/ +#include "luasocket.h" + +#include "buffer.h" +#include "timeout.h" +#include "socket.h" + +typedef struct t_unix_ { + t_socket sock; + t_io io; + t_buffer buf; + t_timeout tm; +} t_unix; +typedef t_unix *p_unix; + +LUASOCKET_API int luaopen_socket_unix(lua_State *L); + +#endif /* UNIX_H */ diff --git a/framework/lualib-src/luasocket/src/unixdgram.c b/framework/lualib-src/luasocket/src/unixdgram.c new file mode 100644 index 0000000..3ac3c5e --- /dev/null +++ b/framework/lualib-src/luasocket/src/unixdgram.c @@ -0,0 +1,405 @@ +/*=========================================================================*\ +* Unix domain socket dgram submodule +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unix.h" + +#include +#include + +#include + +#define UNIXDGRAM_DATAGRAMSIZE 8192 + +// provide a SUN_LEN macro if sys/un.h doesn't (e.g. Android) +#ifndef SUN_LEN +#define SUN_LEN(ptr) \ + ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) +#endif + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_gettimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_receivefrom(lua_State *L); +static int meth_sendto(lua_State *L); +static int meth_getsockname(lua_State *L); + +static const char *unixdgram_tryconnect(p_unix un, const char *path); +static const char *unixdgram_trybind(p_unix un, const char *path); + +/* unixdgram object methods */ +static luaL_Reg unixdgram_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"send", meth_send}, + {"sendto", meth_sendto}, + {"receive", meth_receive}, + {"receivefrom", meth_receivefrom}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getsockname", meth_getsockname}, + {"settimeout", meth_settimeout}, + {"gettimeout", meth_gettimeout}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optset[] = { + {"reuseaddr", opt_set_reuseaddr}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"dgram", global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int unixdgram_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "unixdgram{connected}", unixdgram_methods); + auxiliar_newclass(L, "unixdgram{unconnected}", unixdgram_methods); + /* create class groups */ + auxiliar_add2group(L, "unixdgram{connected}", "unixdgram{any}"); + auxiliar_add2group(L, "unixdgram{unconnected}", "unixdgram{any}"); + auxiliar_add2group(L, "unixdgram{connected}", "select{able}"); + auxiliar_add2group(L, "unixdgram{unconnected}", "select{able}"); + + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +static const char *unixdgram_strerror(int err) +{ + /* a 'closed' error on an unconnected means the target address was not + * accepted by the transport layer */ + if (err == IO_CLOSED) return "refused"; + else return socket_strerror(err); +} + +static int meth_send(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{connected}", 1); + p_timeout tm = &un->tm; + size_t count, sent = 0; + int err; + const char *data = luaL_checklstring(L, 2, &count); + timeout_markstart(tm); + err = socket_send(&un->sock, data, count, &sent, tm); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Send data through unconnected unixdgram socket +\*-------------------------------------------------------------------------*/ +static int meth_sendto(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + size_t count, sent = 0; + const char *data = luaL_checklstring(L, 2, &count); + const char *path = luaL_checkstring(L, 3); + p_timeout tm = &un->tm; + int err; + struct sockaddr_un remote; + size_t len = strlen(path); + + if (len >= sizeof(remote.sun_path)) { + lua_pushnil(L); + lua_pushstring(L, "path too long"); + return 2; + } + + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(tm); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + + len + 1; + err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, remote.sun_len, tm); +#else + err = socket_sendto(&un->sock, data, count, &sent, (SA *) &remote, + sizeof(remote.sun_family) + len, tm); +#endif + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + return 2; + } + lua_pushnumber(L, (lua_Number) sent); + return 1; +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + char buf[UNIXDGRAM_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + int err; + p_timeout tm = &un->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + err = socket_recv(&un->sock, dgram, wanted, &got, tm); + /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + lua_pushlstring(L, dgram, got); + if (wanted > sizeof(buf)) free(dgram); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Receives data and sender from a DGRAM socket +\*-------------------------------------------------------------------------*/ +static int meth_receivefrom(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + char buf[UNIXDGRAM_DATAGRAMSIZE]; + size_t got, wanted = (size_t) luaL_optnumber(L, 2, sizeof(buf)); + char *dgram = wanted > sizeof(buf)? (char *) malloc(wanted): buf; + struct sockaddr_un addr; + socklen_t addr_len = sizeof(addr); + int err; + p_timeout tm = &un->tm; + timeout_markstart(tm); + if (!dgram) { + lua_pushnil(L); + lua_pushliteral(L, "out of memory"); + return 2; + } + addr.sun_path[0] = '\0'; + err = socket_recvfrom(&un->sock, dgram, wanted, &got, (SA *) &addr, + &addr_len, tm); + /* Unlike STREAM, recv() of zero is not closed, but a zero-length packet. */ + if (err != IO_DONE && err != IO_CLOSED) { + lua_pushnil(L); + lua_pushstring(L, unixdgram_strerror(err)); + if (wanted > sizeof(buf)) free(dgram); + return 2; + } + + lua_pushlstring(L, dgram, got); + /* the path may be empty, when client send without bind */ + lua_pushstring(L, addr.sun_path); + if (wanted > sizeof(buf)) free(dgram); + return 2; +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return opt_meth_setoption(L, optset, &un->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + (void) un; + lua_pushboolean(L, 0); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static const char *unixdgram_trybind(p_unix un, const char *path) { + struct sockaddr_un local; + size_t len = strlen(path); + if (len >= sizeof(local.sun_path)) return "path too long"; + memset(&local, 0, sizeof(local)); + strcpy(local.sun_path, path); + local.sun_family = AF_UNIX; + size_t addrlen = SUN_LEN(&local); +#ifdef UNIX_HAS_SUN_LEN + local.sun_len = addrlen + 1; +#endif + int err = socket_bind(&un->sock, (SA *) &local, addrlen); + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_bind(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixdgram{unconnected}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixdgram_trybind(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int meth_getsockname(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + struct sockaddr_un peer = {0}; + socklen_t peer_len = sizeof(peer); + + if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + + lua_pushstring(L, peer.sun_path); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master unixdgram object into a client object. +\*-------------------------------------------------------------------------*/ +static const char *unixdgram_tryconnect(p_unix un, const char *path) +{ + struct sockaddr_un remote; + size_t len = strlen(path); + if (len >= sizeof(remote.sun_path)) return "path too long"; + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(&un->tm); + size_t addrlen = SUN_LEN(&remote); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = addrlen + 1; +#endif + int err = socket_connect(&un->sock, (SA *) &remote, addrlen, &un->tm); + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_connect(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixdgram_tryconnect(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn unconnected object into a connected object */ + auxiliar_setclass(L, "unixdgram{connected}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +static int meth_gettimeout(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixdgram{any}", 1); + return timeout_meth_gettimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master unixdgram object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) +{ + t_socket sock; + int err = socket_create(&sock, AF_UNIX, SOCK_DGRAM, 0); + /* try to allocate a system socket */ + if (err == IO_DONE) { + /* allocate unixdgram object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + /* set its type as master object */ + auxiliar_setclass(L, "unixdgram{unconnected}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} diff --git a/framework/lualib-src/luasocket/src/unixdgram.h b/framework/lualib-src/luasocket/src/unixdgram.h new file mode 100644 index 0000000..a1a0166 --- /dev/null +++ b/framework/lualib-src/luasocket/src/unixdgram.h @@ -0,0 +1,28 @@ +#ifndef UNIXDGRAM_H +#define UNIXDGRAM_H +/*=========================================================================*\ +* DGRAM object +* LuaSocket toolkit +* +* The dgram.h module provides LuaSocket with support for DGRAM protocol +* (AF_INET, SOCK_DGRAM). +* +* Two classes are defined: connected and unconnected. DGRAM objects are +* originally unconnected. They can be "connected" to a given address +* with a call to the setpeername function. The same function can be used to +* break the connection. +\*=========================================================================*/ + +#include "unix.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int unixdgram_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UNIXDGRAM_H */ diff --git a/framework/lualib-src/luasocket/src/unixstream.c b/framework/lualib-src/luasocket/src/unixstream.c new file mode 100644 index 0000000..02aced9 --- /dev/null +++ b/framework/lualib-src/luasocket/src/unixstream.c @@ -0,0 +1,355 @@ +/*=========================================================================*\ +* Unix domain socket stream sub module +* LuaSocket toolkit +\*=========================================================================*/ +#include "luasocket.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unixstream.h" + +#include +#include + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_connect(lua_State *L); +static int meth_listen(lua_State *L); +static int meth_bind(lua_State *L); +static int meth_send(lua_State *L); +static int meth_shutdown(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_accept(lua_State *L); +static int meth_close(lua_State *L); +static int meth_setoption(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); +static int meth_getsockname(lua_State *L); + +static const char *unixstream_tryconnect(p_unix un, const char *path); +static const char *unixstream_trybind(p_unix un, const char *path); + +/* unixstream object methods */ +static luaL_Reg unixstream_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"accept", meth_accept}, + {"bind", meth_bind}, + {"close", meth_close}, + {"connect", meth_connect}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"listen", meth_listen}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"setoption", meth_setoption}, + {"setpeername", meth_connect}, + {"setsockname", meth_bind}, + {"getsockname", meth_getsockname}, + {"settimeout", meth_settimeout}, + {"shutdown", meth_shutdown}, + {NULL, NULL} +}; + +/* socket option handlers */ +static t_opt optset[] = { + {"keepalive", opt_set_keepalive}, + {"reuseaddr", opt_set_reuseaddr}, + {"linger", opt_set_linger}, + {NULL, NULL} +}; + +/* functions in library namespace */ +static luaL_Reg func[] = { + {"stream", global_create}, + {NULL, NULL} +}; + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int unixstream_open(lua_State *L) +{ + /* create classes */ + auxiliar_newclass(L, "unixstream{master}", unixstream_methods); + auxiliar_newclass(L, "unixstream{client}", unixstream_methods); + auxiliar_newclass(L, "unixstream{server}", unixstream_methods); + + /* create class groups */ + auxiliar_add2group(L, "unixstream{master}", "unixstream{any}"); + auxiliar_add2group(L, "unixstream{client}", "unixstream{any}"); + auxiliar_add2group(L, "unixstream{server}", "unixstream{any}"); + + luaL_setfuncs(L, func, 0); + return 0; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_send(L, &un->buf); +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_receive(L, &un->buf); +} + +static int meth_getstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_getstats(L, &un->buf); +} + +static int meth_setstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + return buffer_meth_setstats(L, &un->buf); +} + +/*-------------------------------------------------------------------------*\ +* Just call option handler +\*-------------------------------------------------------------------------*/ +static int meth_setoption(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + return opt_meth_setoption(L, optset, &un->sock); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + lua_pushboolean(L, !buffer_isempty(&un->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int meth_accept(lua_State *L) { + p_unix server = (p_unix) auxiliar_checkclass(L, "unixstream{server}", 1); + p_timeout tm = timeout_markstart(&server->tm); + t_socket sock; + int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); + /* if successful, push client socket */ + if (err == IO_DONE) { + p_unix clnt = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + auxiliar_setclass(L, "unixstream{client}", -1); + /* initialize structure fields */ + socket_setnonblocking(&sock); + clnt->sock = sock; + io_init(&clnt->io, (p_send)socket_send, (p_recv)socket_recv, + (p_error) socket_ioerror, &clnt->sock); + timeout_init(&clnt->tm, -1, -1); + buffer_init(&clnt->buf, &clnt->io, &clnt->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} + +/*-------------------------------------------------------------------------*\ +* Binds an object to an address +\*-------------------------------------------------------------------------*/ +static const char *unixstream_trybind(p_unix un, const char *path) { + struct sockaddr_un local; + size_t len = strlen(path); + int err; + if (len >= sizeof(local.sun_path)) return "path too long"; + memset(&local, 0, sizeof(local)); + strcpy(local.sun_path, path); + local.sun_family = AF_UNIX; +#ifdef UNIX_HAS_SUN_LEN + local.sun_len = sizeof(local.sun_family) + sizeof(local.sun_len) + + len + 1; + err = socket_bind(&un->sock, (SA *) &local, local.sun_len); + +#else + err = socket_bind(&un->sock, (SA *) &local, + sizeof(local.sun_family) + len); +#endif + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_bind(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixstream_trybind(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + lua_pushnumber(L, 1); + return 1; +} + +static int meth_getsockname(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + struct sockaddr_un peer = {0}; + socklen_t peer_len = sizeof(peer); + + if (getsockname(un->sock, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + + lua_pushstring(L, peer.sun_path); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Turns a master unixstream object into a client object. +\*-------------------------------------------------------------------------*/ +static const char *unixstream_tryconnect(p_unix un, const char *path) +{ + struct sockaddr_un remote; + int err; + size_t len = strlen(path); + if (len >= sizeof(remote.sun_path)) return "path too long"; + memset(&remote, 0, sizeof(remote)); + strcpy(remote.sun_path, path); + remote.sun_family = AF_UNIX; + timeout_markstart(&un->tm); +#ifdef UNIX_HAS_SUN_LEN + remote.sun_len = sizeof(remote.sun_family) + sizeof(remote.sun_len) + + len + 1; + err = socket_connect(&un->sock, (SA *) &remote, remote.sun_len, &un->tm); +#else + err = socket_connect(&un->sock, (SA *) &remote, + sizeof(remote.sun_family) + len, &un->tm); +#endif + if (err != IO_DONE) socket_destroy(&un->sock); + return socket_strerror(err); +} + +static int meth_connect(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + const char *path = luaL_checkstring(L, 2); + const char *err = unixstream_tryconnect(un, path); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn master object into a client object */ + auxiliar_setclass(L, "unixstream{client}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Puts the sockt in listen mode +\*-------------------------------------------------------------------------*/ +static int meth_listen(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkclass(L, "unixstream{master}", 1); + int backlog = (int) luaL_optnumber(L, 2, 32); + int err = socket_listen(&un->sock, backlog); + if (err != IO_DONE) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } + /* turn master object into a server object */ + auxiliar_setclass(L, "unixstream{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Shuts the connection down partially +\*-------------------------------------------------------------------------*/ +static int meth_shutdown(lua_State *L) +{ + /* SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, so we can use method index directly */ + static const char* methods[] = { "receive", "send", "both", NULL }; + p_unix stream = (p_unix) auxiliar_checkclass(L, "unixstream{client}", 1); + int how = luaL_checkoption(L, 2, "both", methods); + socket_shutdown(&stream->sock, how); + lua_pushnumber(L, 1); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "unixstream{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a master unixstream object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) { + t_socket sock; + int err = socket_create(&sock, AF_UNIX, SOCK_STREAM, 0); + /* try to allocate a system socket */ + if (err == IO_DONE) { + /* allocate unixstream object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + /* set its type as master object */ + auxiliar_setclass(L, "unixstream{master}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_send, (p_recv) socket_recv, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} diff --git a/framework/lualib-src/luasocket/src/unixstream.h b/framework/lualib-src/luasocket/src/unixstream.h new file mode 100644 index 0000000..7916aff --- /dev/null +++ b/framework/lualib-src/luasocket/src/unixstream.h @@ -0,0 +1,29 @@ +#ifndef UNIXSTREAM_H +#define UNIXSTREAM_H +/*=========================================================================*\ +* UNIX STREAM object +* LuaSocket toolkit +* +* The unixstream.h module is basicly a glue that puts together modules buffer.h, +* timeout.h socket.h and inet.h to provide the LuaSocket UNIX STREAM (AF_UNIX, +* SOCK_STREAM) support. +* +* Three classes are defined: master, client and server. The master class is +* a newly created unixstream object, that has not been bound or connected. Server +* objects are unixstream objects bound to some local address. Client objects are +* unixstream objects either connected to some address or returned by the accept +* method of a server object. +\*=========================================================================*/ +#include "unix.h" + +#ifndef _WIN32 +#pragma GCC visibility push(hidden) +#endif + +int unixstream_open(lua_State *L); + +#ifndef _WIN32 +#pragma GCC visibility pop +#endif + +#endif /* UNIXSTREAM_H */ diff --git a/framework/lualib-src/luasocket/src/url.lua b/framework/lualib-src/luasocket/src/url.lua new file mode 100644 index 0000000..0a3a80a --- /dev/null +++ b/framework/lualib-src/luasocket/src/url.lua @@ -0,0 +1,331 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +local socket = require("socket") + +socket.url = {} +local _M = socket.url + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_M._VERSION = "URL 1.0.3" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed within a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02X", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Unencodes a escaped hexadecimal string into its binary representation +-- Input +-- s: escaped hexadecimal string to be unencoded +-- Returns +-- unescaped binary representation of escaped hexadecimal binary +----------------------------------------------------------------------------- +function _M.unescape(s) + return (string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end)) +end + +----------------------------------------------------------------------------- +-- Removes '..' and '.' components appropriately from a path. +-- Input +-- path +-- Returns +-- dot-normalized path +local function remove_dot_components(path) + local marker = string.char(1) + repeat + local was = path + path = path:gsub('//', '/'..marker..'/', 1) + until path == was + repeat + local was = path + path = path:gsub('/%./', '/', 1) + until path == was + repeat + local was = path + path = path:gsub('[^/]+/%.%./([^/]+)', '%1', 1) + until path == was + path = path:gsub('[^/]+/%.%./*$', '') + path = path:gsub('/%.%.$', '/') + path = path:gsub('/%.$', '/') + path = path:gsub('^/%.%./', '/') + path = path:gsub(marker, '') + return path +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then + return remove_dot_components(relative_path) end + base_path = base_path:gsub("[^/]*$", "") + if not base_path:find'/$' then base_path = base_path .. '/' end + local path = base_path .. relative_path + path = remove_dot_components(path) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function _M.parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get query string + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:%]]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then + -- IPv6? + parsed.host = string.match(authority, "^%[(.+)%]$") or authority + end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function _M.build(parsed) + --local ppath = _M.parse_path(parsed.path or "") + --local url = _M.build_path(ppath) + local url = parsed.path or "" + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if string.find(authority, ":") then -- IPv6? + authority = "[" .. authority .. "]" + end + if parsed.port then authority = authority .. ":" .. base.tostring(parsed.port) end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function _M.absolute(base_url, relative_url) + local base_parsed + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = _M.build(base_parsed) + else + base_parsed = _M.parse(base_url) + end + local result + local relative_parsed = _M.parse(relative_url) + if not base_parsed then + result = relative_url + elseif not relative_parsed then + result = base_url + elseif relative_parsed.scheme then + result = relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + result = _M.build(relative_parsed) + end + return remove_dot_components(result) +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function _M.parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = _M.unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function _M.build_path(parsed, unsafe) + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end + +return _M diff --git a/framework/lualib-src/luasocket/src/usocket.c b/framework/lualib-src/luasocket/src/usocket.c new file mode 100644 index 0000000..acfe186 --- /dev/null +++ b/framework/lualib-src/luasocket/src/usocket.c @@ -0,0 +1,454 @@ +/*=========================================================================*\ +* Socket compatibilization module for Unix +* LuaSocket toolkit +* +* The code is now interrupt-safe. +* The penalty of calling select to avoid busy-wait is only paid when +* the I/O call fail in the first place. +\*=========================================================================*/ +#include "luasocket.h" + +#include "socket.h" +#include "pierror.h" + +#include +#include + +/*-------------------------------------------------------------------------*\ +* Wait for readable/writable/connected socket with timeout +\*-------------------------------------------------------------------------*/ +#ifndef SOCKET_SELECT +#include + +#define WAITFD_R POLLIN +#define WAITFD_W POLLOUT +#define WAITFD_C (POLLIN|POLLOUT) +int socket_waitfd(p_socket ps, int sw, p_timeout tm) { + int ret; + struct pollfd pfd; + pfd.fd = *ps; + pfd.events = sw; + pfd.revents = 0; + if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ + do { + int t = (int)(timeout_getretry(tm)*1e3); + ret = poll(&pfd, 1, t >= 0? t: -1); + } while (ret == -1 && errno == EINTR); + if (ret == -1) return errno; + if (ret == 0) return IO_TIMEOUT; + if (sw == WAITFD_C && (pfd.revents & (POLLIN|POLLERR))) return IO_CLOSED; + return IO_DONE; +} +#else + +#define WAITFD_R 1 +#define WAITFD_W 2 +#define WAITFD_C (WAITFD_R|WAITFD_W) + +int socket_waitfd(p_socket ps, int sw, p_timeout tm) { + int ret; + fd_set rfds, wfds, *rp, *wp; + struct timeval tv, *tp; + double t; + if (*ps >= FD_SETSIZE) return EINVAL; + if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ + do { + /* must set bits within loop, because select may have modifed them */ + rp = wp = NULL; + if (sw & WAITFD_R) { FD_ZERO(&rfds); FD_SET(*ps, &rfds); rp = &rfds; } + if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } + t = timeout_getretry(tm); + tp = NULL; + if (t >= 0.0) { + tv.tv_sec = (int)t; + tv.tv_usec = (int)((t-tv.tv_sec)*1.0e6); + tp = &tv; + } + ret = select(*ps+1, rp, wp, NULL, tp); + } while (ret == -1 && errno == EINTR); + if (ret == -1) return errno; + if (ret == 0) return IO_TIMEOUT; + if (sw == WAITFD_C && FD_ISSET(*ps, &rfds)) return IO_CLOSED; + return IO_DONE; +} +#endif + + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int socket_open(void) { + /* installs a handler to ignore sigpipe or it will crash us */ + signal(SIGPIPE, SIG_IGN); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Close module +\*-------------------------------------------------------------------------*/ +int socket_close(void) { + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Close and inutilize socket +\*-------------------------------------------------------------------------*/ +void socket_destroy(p_socket ps) { + if (*ps != SOCKET_INVALID) { + close(*ps); + *ps = SOCKET_INVALID; + } +} + +/*-------------------------------------------------------------------------*\ +* Select with timeout control +\*-------------------------------------------------------------------------*/ +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, + p_timeout tm) { + int ret; + do { + struct timeval tv; + double t = timeout_getretry(tm); + tv.tv_sec = (int) t; + tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); + /* timeout = 0 means no wait */ + ret = select(n, rfds, wfds, efds, t >= 0.0 ? &tv: NULL); + } while (ret < 0 && errno == EINTR); + return ret; +} + +/*-------------------------------------------------------------------------*\ +* Creates and sets up a socket +\*-------------------------------------------------------------------------*/ +int socket_create(p_socket ps, int domain, int type, int protocol) { + *ps = socket(domain, type, protocol); + if (*ps != SOCKET_INVALID) return IO_DONE; + else return errno; +} + +/*-------------------------------------------------------------------------*\ +* Binds or returns error message +\*-------------------------------------------------------------------------*/ +int socket_bind(p_socket ps, SA *addr, socklen_t len) { + int err = IO_DONE; + socket_setblocking(ps); + if (bind(*ps, addr, len) < 0) err = errno; + socket_setnonblocking(ps); + return err; +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +int socket_listen(p_socket ps, int backlog) { + int err = IO_DONE; + if (listen(*ps, backlog)) err = errno; + return err; +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +void socket_shutdown(p_socket ps, int how) { + shutdown(*ps, how); +} + +/*-------------------------------------------------------------------------*\ +* Connects or returns error message +\*-------------------------------------------------------------------------*/ +int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { + int err; + /* avoid calling on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* call connect until done or failed without being interrupted */ + do if (connect(*ps, addr, len) == 0) return IO_DONE; + while ((err = errno) == EINTR); + /* if connection failed immediately, return error code */ + if (err != EINPROGRESS && err != EAGAIN) return err; + /* zero timeout case optimization */ + if (timeout_iszero(tm)) return IO_TIMEOUT; + /* wait until we have the result of the connection attempt or timeout */ + err = socket_waitfd(ps, WAITFD_C, tm); + if (err == IO_CLOSED) { + if (recv(*ps, (char *) &err, 0, 0) == 0) return IO_DONE; + else return errno; + } else return err; +} + +/*-------------------------------------------------------------------------*\ +* Accept with timeout +\*-------------------------------------------------------------------------*/ +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, p_timeout tm) { + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int err; + if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; + err = errno; + if (err == EINTR) continue; + if (err != EAGAIN && err != ECONNABORTED) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Send with timeout +\*-------------------------------------------------------------------------*/ +int socket_send(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + long put = (long) send(*ps, data, count, 0); + /* if we sent anything, we are done */ + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + /* EPIPE means the connection was closed */ + if (err == EPIPE) return IO_CLOSED; + /* EPROTOTYPE means the connection is being closed (on Yosemite!)*/ + if (err == EPROTOTYPE) continue; + /* we call was interrupted, just try again */ + if (err == EINTR) continue; + /* if failed fatal reason, report error */ + if (err != EAGAIN) return err; + /* wait until we can send something or we timeout */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Sendto with timeout +\*-------------------------------------------------------------------------*/ +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, + SA *addr, socklen_t len, p_timeout tm) +{ + int err; + *sent = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long put = (long) sendto(*ps, data, count, 0, addr, len); + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + if (err == EPIPE) return IO_CLOSED; + if (err == EPROTOTYPE) continue; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Receive with timeout +\*-------------------------------------------------------------------------*/ +int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) recv(*ps, data, count, 0); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Recvfrom with timeout +\*-------------------------------------------------------------------------*/ +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, + SA *addr, socklen_t *len, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) recvfrom(*ps, data, count, 0, addr, len); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + + +/*-------------------------------------------------------------------------*\ +* Write with timeout +* +* socket_read and socket_write are cut-n-paste of socket_send and socket_recv, +* with send/recv replaced with write/read. We can't just use write/read +* in the socket version, because behaviour when size is zero is different. +\*-------------------------------------------------------------------------*/ +int socket_write(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + long put = (long) write(*ps, data, count); + /* if we sent anything, we are done */ + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + /* EPIPE means the connection was closed */ + if (err == EPIPE) return IO_CLOSED; + /* EPROTOTYPE means the connection is being closed (on Yosemite!)*/ + if (err == EPROTOTYPE) continue; + /* we call was interrupted, just try again */ + if (err == EINTR) continue; + /* if failed fatal reason, report error */ + if (err != EAGAIN) return err; + /* wait until we can send something or we timeout */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Read with timeout +* See note for socket_write +\*-------------------------------------------------------------------------*/ +int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) read(*ps, data, count); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setblocking(p_socket ps) { + int flags = fcntl(*ps, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(*ps, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setnonblocking(p_socket ps) { + int flags = fcntl(*ps, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(*ps, F_SETFL, flags); +} + +/*-------------------------------------------------------------------------*\ +* DNS helpers +\*-------------------------------------------------------------------------*/ +int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { + *hp = gethostbyaddr(addr, len, AF_INET); + if (*hp) return IO_DONE; + else if (h_errno) return h_errno; + else if (errno) return errno; + else return IO_UNKNOWN; +} + +int socket_gethostbyname(const char *addr, struct hostent **hp) { + *hp = gethostbyname(addr); + if (*hp) return IO_DONE; + else if (h_errno) return h_errno; + else if (errno) return errno; + else return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Error translation functions +* Make sure important error messages are standard +\*-------------------------------------------------------------------------*/ +const char *socket_hoststrerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case HOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; + default: return hstrerror(err); + } +} + +const char *socket_strerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case EADDRINUSE: return PIE_ADDRINUSE; + case EISCONN: return PIE_ISCONN; + case EACCES: return PIE_ACCESS; + case ECONNREFUSED: return PIE_CONNREFUSED; + case ECONNABORTED: return PIE_CONNABORTED; + case ECONNRESET: return PIE_CONNRESET; + case ETIMEDOUT: return PIE_TIMEDOUT; + default: { + return strerror(err); + } + } +} + +const char *socket_ioerror(p_socket ps, int err) { + (void) ps; + return socket_strerror(err); +} + +const char *socket_gaistrerror(int err) { + if (err == 0) return NULL; + switch (err) { + case EAI_AGAIN: return PIE_AGAIN; + case EAI_BADFLAGS: return PIE_BADFLAGS; +#ifdef EAI_BADHINTS + case EAI_BADHINTS: return PIE_BADHINTS; +#endif + case EAI_FAIL: return PIE_FAIL; + case EAI_FAMILY: return PIE_FAMILY; + case EAI_MEMORY: return PIE_MEMORY; + case EAI_NONAME: return PIE_NONAME; +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: return PIE_OVERFLOW; +#endif +#ifdef EAI_PROTOCOL + case EAI_PROTOCOL: return PIE_PROTOCOL; +#endif + case EAI_SERVICE: return PIE_SERVICE; + case EAI_SOCKTYPE: return PIE_SOCKTYPE; + case EAI_SYSTEM: return strerror(errno); + default: return gai_strerror(err); + } +} diff --git a/framework/lualib-src/luasocket/src/usocket.h b/framework/lualib-src/luasocket/src/usocket.h new file mode 100644 index 0000000..45f2f99 --- /dev/null +++ b/framework/lualib-src/luasocket/src/usocket.h @@ -0,0 +1,59 @@ +#ifndef USOCKET_H +#define USOCKET_H +/*=========================================================================*\ +* Socket compatibilization module for Unix +* LuaSocket toolkit +\*=========================================================================*/ + +/*=========================================================================*\ +* BSD include files +\*=========================================================================*/ +/* error codes */ +#include +/* close function */ +#include +/* fnctnl function and associated constants */ +#include +/* struct sockaddr */ +#include +/* socket function */ +#include +/* struct timeval */ +#include +/* gethostbyname and gethostbyaddr functions */ +#include +/* sigpipe handling */ +#include +/* IP stuff*/ +#include +#include +/* TCP options (nagle algorithm disable) */ +#include +#include + +#ifndef SO_REUSEPORT +#define SO_REUSEPORT SO_REUSEADDR +#endif + +/* Some platforms use IPV6_JOIN_GROUP instead if + * IPV6_ADD_MEMBERSHIP. The semantics are same, though. */ +#ifndef IPV6_ADD_MEMBERSHIP +#ifdef IPV6_JOIN_GROUP +#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP +#endif /* IPV6_JOIN_GROUP */ +#endif /* !IPV6_ADD_MEMBERSHIP */ + +/* Same with IPV6_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP. */ +#ifndef IPV6_DROP_MEMBERSHIP +#ifdef IPV6_LEAVE_GROUP +#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP +#endif /* IPV6_LEAVE_GROUP */ +#endif /* !IPV6_DROP_MEMBERSHIP */ + +typedef int t_socket; +typedef t_socket *p_socket; +typedef struct sockaddr_storage t_sockaddr_storage; + +#define SOCKET_INVALID (-1) + +#endif /* USOCKET_H */ diff --git a/framework/lualib-src/luasocket/src/wsocket.c b/framework/lualib-src/luasocket/src/wsocket.c new file mode 100755 index 0000000..20da330 --- /dev/null +++ b/framework/lualib-src/luasocket/src/wsocket.c @@ -0,0 +1,434 @@ +/*=========================================================================*\ +* Socket compatibilization module for Win32 +* LuaSocket toolkit +* +* The penalty of calling select to avoid busy-wait is only paid when +* the I/O call fail in the first place. +\*=========================================================================*/ +#include "luasocket.h" + +#include + +#include "socket.h" +#include "pierror.h" + +/* WinSock doesn't have a strerror... */ +static const char *wstrerror(int err); + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +int socket_open(void) { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 0); + int err = WSAStartup(wVersionRequested, &wsaData ); + if (err != 0) return 0; + if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) && + (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) { + WSACleanup(); + return 0; + } + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Close module +\*-------------------------------------------------------------------------*/ +int socket_close(void) { + WSACleanup(); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Wait for readable/writable/connected socket with timeout +\*-------------------------------------------------------------------------*/ +#define WAITFD_R 1 +#define WAITFD_W 2 +#define WAITFD_E 4 +#define WAITFD_C (WAITFD_E|WAITFD_W) + +int socket_waitfd(p_socket ps, int sw, p_timeout tm) { + int ret; + fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL; + struct timeval tv, *tp = NULL; + double t; + if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ + if (sw & WAITFD_R) { + FD_ZERO(&rfds); + FD_SET(*ps, &rfds); + rp = &rfds; + } + if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; } + if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; } + if ((t = timeout_get(tm)) >= 0.0) { + tv.tv_sec = (int) t; + tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6); + tp = &tv; + } + ret = select(0, rp, wp, ep, tp); + if (ret == -1) return WSAGetLastError(); + if (ret == 0) return IO_TIMEOUT; + if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED; + return IO_DONE; +} + +/*-------------------------------------------------------------------------*\ +* Select with int timeout in ms +\*-------------------------------------------------------------------------*/ +int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds, + p_timeout tm) { + struct timeval tv; + double t = timeout_get(tm); + tv.tv_sec = (int) t; + tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6); + if (n <= 0) { + Sleep((DWORD) (1000*t)); + return 0; + } else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL); +} + +/*-------------------------------------------------------------------------*\ +* Close and inutilize socket +\*-------------------------------------------------------------------------*/ +void socket_destroy(p_socket ps) { + if (*ps != SOCKET_INVALID) { + socket_setblocking(ps); /* close can take a long time on WIN32 */ + closesocket(*ps); + *ps = SOCKET_INVALID; + } +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +void socket_shutdown(p_socket ps, int how) { + socket_setblocking(ps); + shutdown(*ps, how); + socket_setnonblocking(ps); +} + +/*-------------------------------------------------------------------------*\ +* Creates and sets up a socket +\*-------------------------------------------------------------------------*/ +int socket_create(p_socket ps, int domain, int type, int protocol) { + *ps = socket(domain, type, protocol); + if (*ps != SOCKET_INVALID) return IO_DONE; + else return WSAGetLastError(); +} + +/*-------------------------------------------------------------------------*\ +* Connects or returns error message +\*-------------------------------------------------------------------------*/ +int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) { + int err; + /* don't call on closed socket */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* ask system to connect */ + if (connect(*ps, addr, len) == 0) return IO_DONE; + /* make sure the system is trying to connect */ + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err; + /* zero timeout case optimization */ + if (timeout_iszero(tm)) return IO_TIMEOUT; + /* we wait until something happens */ + err = socket_waitfd(ps, WAITFD_C, tm); + if (err == IO_CLOSED) { + int elen = sizeof(err); + /* give windows time to set the error (yes, disgusting) */ + Sleep(10); + /* find out why we failed */ + getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &elen); + /* we KNOW there was an error. if 'why' is 0, we will return + * "unknown error", but it's not really our fault */ + return err > 0? err: IO_UNKNOWN; + } else return err; + +} + +/*-------------------------------------------------------------------------*\ +* Binds or returns error message +\*-------------------------------------------------------------------------*/ +int socket_bind(p_socket ps, SA *addr, socklen_t len) { + int err = IO_DONE; + socket_setblocking(ps); + if (bind(*ps, addr, len) < 0) err = WSAGetLastError(); + socket_setnonblocking(ps); + return err; +} + +/*-------------------------------------------------------------------------*\ +* +\*-------------------------------------------------------------------------*/ +int socket_listen(p_socket ps, int backlog) { + int err = IO_DONE; + socket_setblocking(ps); + if (listen(*ps, backlog) < 0) err = WSAGetLastError(); + socket_setnonblocking(ps); + return err; +} + +/*-------------------------------------------------------------------------*\ +* Accept with timeout +\*-------------------------------------------------------------------------*/ +int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, + p_timeout tm) { + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int err; + /* try to get client socket */ + if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE; + /* find out why we failed */ + err = WSAGetLastError(); + /* if we failed because there was no connectoin, keep trying */ + if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err; + /* call select to avoid busy wait */ + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Send with timeout +* On windows, if you try to send 10MB, the OS will buffer EVERYTHING +* this can take an awful lot of time and we will end up blocked. +* Therefore, whoever calls this function should not pass a huge buffer. +\*-------------------------------------------------------------------------*/ +int socket_send(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + /* try to send something */ + int put = send(*ps, data, (int) count, 0); + /* if we sent something, we are done */ + if (put > 0) { + *sent = put; + return IO_DONE; + } + /* deal with failure */ + err = WSAGetLastError(); + /* we can only proceed if there was no serious error */ + if (err != WSAEWOULDBLOCK) return err; + /* avoid busy wait */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Sendto with timeout +\*-------------------------------------------------------------------------*/ +int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, + SA *addr, socklen_t len, p_timeout tm) +{ + int err; + *sent = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int put = sendto(*ps, data, (int) count, 0, addr, len); + if (put > 0) { + *sent = put; + return IO_DONE; + } + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) return err; + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Receive with timeout +\*-------------------------------------------------------------------------*/ +int socket_recv(p_socket ps, char *data, size_t count, size_t *got, + p_timeout tm) +{ + int err, prev = IO_DONE; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int taken = recv(*ps, data, (int) count, 0); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + if (taken == 0) return IO_CLOSED; + err = WSAGetLastError(); + /* On UDP, a connreset simply means the previous send failed. + * So we try again. + * On TCP, it means our socket is now useless, so the error passes. + * (We will loop again, exiting because the same error will happen) */ + if (err != WSAEWOULDBLOCK) { + if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; + prev = err; + } + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Recvfrom with timeout +\*-------------------------------------------------------------------------*/ +int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, + SA *addr, socklen_t *len, p_timeout tm) +{ + int err, prev = IO_DONE; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + int taken = recvfrom(*ps, data, (int) count, 0, addr, len); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + if (taken == 0) return IO_CLOSED; + err = WSAGetLastError(); + /* On UDP, a connreset simply means the previous send failed. + * So we try again. + * On TCP, it means our socket is now useless, so the error passes. + * (We will loop again, exiting because the same error will happen) */ + if (err != WSAEWOULDBLOCK) { + if (err != WSAECONNRESET || prev == WSAECONNRESET) return err; + prev = err; + } + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setblocking(p_socket ps) { + u_long argp = 0; + ioctlsocket(*ps, FIONBIO, &argp); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode +\*-------------------------------------------------------------------------*/ +void socket_setnonblocking(p_socket ps) { + u_long argp = 1; + ioctlsocket(*ps, FIONBIO, &argp); +} + +/*-------------------------------------------------------------------------*\ +* DNS helpers +\*-------------------------------------------------------------------------*/ +int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) { + *hp = gethostbyaddr(addr, len, AF_INET); + if (*hp) return IO_DONE; + else return WSAGetLastError(); +} + +int socket_gethostbyname(const char *addr, struct hostent **hp) { + *hp = gethostbyname(addr); + if (*hp) return IO_DONE; + else return WSAGetLastError(); +} + +/*-------------------------------------------------------------------------*\ +* Error translation functions +\*-------------------------------------------------------------------------*/ +const char *socket_hoststrerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; + default: return wstrerror(err); + } +} + +const char *socket_strerror(int err) { + if (err <= 0) return io_strerror(err); + switch (err) { + case WSAEADDRINUSE: return PIE_ADDRINUSE; + case WSAECONNREFUSED : return PIE_CONNREFUSED; + case WSAEISCONN: return PIE_ISCONN; + case WSAEACCES: return PIE_ACCESS; + case WSAECONNABORTED: return PIE_CONNABORTED; + case WSAECONNRESET: return PIE_CONNRESET; + case WSAETIMEDOUT: return PIE_TIMEDOUT; + default: return wstrerror(err); + } +} + +const char *socket_ioerror(p_socket ps, int err) { + (void) ps; + return socket_strerror(err); +} + +static const char *wstrerror(int err) { + switch (err) { + case WSAEINTR: return "Interrupted function call"; + case WSAEACCES: return PIE_ACCESS; // "Permission denied"; + case WSAEFAULT: return "Bad address"; + case WSAEINVAL: return "Invalid argument"; + case WSAEMFILE: return "Too many open files"; + case WSAEWOULDBLOCK: return "Resource temporarily unavailable"; + case WSAEINPROGRESS: return "Operation now in progress"; + case WSAEALREADY: return "Operation already in progress"; + case WSAENOTSOCK: return "Socket operation on nonsocket"; + case WSAEDESTADDRREQ: return "Destination address required"; + case WSAEMSGSIZE: return "Message too long"; + case WSAEPROTOTYPE: return "Protocol wrong type for socket"; + case WSAENOPROTOOPT: return "Bad protocol option"; + case WSAEPROTONOSUPPORT: return "Protocol not supported"; + case WSAESOCKTNOSUPPORT: return PIE_SOCKTYPE; // "Socket type not supported"; + case WSAEOPNOTSUPP: return "Operation not supported"; + case WSAEPFNOSUPPORT: return "Protocol family not supported"; + case WSAEAFNOSUPPORT: return PIE_FAMILY; // "Address family not supported by protocol family"; + case WSAEADDRINUSE: return PIE_ADDRINUSE; // "Address already in use"; + case WSAEADDRNOTAVAIL: return "Cannot assign requested address"; + case WSAENETDOWN: return "Network is down"; + case WSAENETUNREACH: return "Network is unreachable"; + case WSAENETRESET: return "Network dropped connection on reset"; + case WSAECONNABORTED: return "Software caused connection abort"; + case WSAECONNRESET: return PIE_CONNRESET; // "Connection reset by peer"; + case WSAENOBUFS: return "No buffer space available"; + case WSAEISCONN: return PIE_ISCONN; // "Socket is already connected"; + case WSAENOTCONN: return "Socket is not connected"; + case WSAESHUTDOWN: return "Cannot send after socket shutdown"; + case WSAETIMEDOUT: return PIE_TIMEDOUT; // "Connection timed out"; + case WSAECONNREFUSED: return PIE_CONNREFUSED; // "Connection refused"; + case WSAEHOSTDOWN: return "Host is down"; + case WSAEHOSTUNREACH: return "No route to host"; + case WSAEPROCLIM: return "Too many processes"; + case WSASYSNOTREADY: return "Network subsystem is unavailable"; + case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range"; + case WSANOTINITIALISED: + return "Successful WSAStartup not yet performed"; + case WSAEDISCON: return "Graceful shutdown in progress"; + case WSAHOST_NOT_FOUND: return PIE_HOST_NOT_FOUND; // "Host not found"; + case WSATRY_AGAIN: return "Nonauthoritative host not found"; + case WSANO_RECOVERY: return PIE_FAIL; // "Nonrecoverable name lookup error"; + case WSANO_DATA: return "Valid name, no data record of requested type"; + default: return "Unknown error"; + } +} + +const char *socket_gaistrerror(int err) { + if (err == 0) return NULL; + switch (err) { + case EAI_AGAIN: return PIE_AGAIN; + case EAI_BADFLAGS: return PIE_BADFLAGS; +#ifdef EAI_BADHINTS + case EAI_BADHINTS: return PIE_BADHINTS; +#endif + case EAI_FAIL: return PIE_FAIL; + case EAI_FAMILY: return PIE_FAMILY; + case EAI_MEMORY: return PIE_MEMORY; + case EAI_NONAME: return PIE_NONAME; +#ifdef EAI_OVERFLOW + case EAI_OVERFLOW: return PIE_OVERFLOW; +#endif +#ifdef EAI_PROTOCOL + case EAI_PROTOCOL: return PIE_PROTOCOL; +#endif + case EAI_SERVICE: return PIE_SERVICE; + case EAI_SOCKTYPE: return PIE_SOCKTYPE; +#ifdef EAI_SYSTEM + case EAI_SYSTEM: return strerror(errno); +#endif + default: return gai_strerror(err); + } +} diff --git a/framework/lualib-src/luasocket/src/wsocket.h b/framework/lualib-src/luasocket/src/wsocket.h new file mode 100644 index 0000000..3986640 --- /dev/null +++ b/framework/lualib-src/luasocket/src/wsocket.h @@ -0,0 +1,33 @@ +#ifndef WSOCKET_H +#define WSOCKET_H +/*=========================================================================*\ +* Socket compatibilization module for Win32 +* LuaSocket toolkit +\*=========================================================================*/ + +/*=========================================================================*\ +* WinSock include files +\*=========================================================================*/ +#include +#include + +typedef int socklen_t; +typedef SOCKADDR_STORAGE t_sockaddr_storage; +typedef SOCKET t_socket; +typedef t_socket *p_socket; + +#ifndef IPV6_V6ONLY +#define IPV6_V6ONLY 27 +#endif + +#define SOCKET_INVALID (INVALID_SOCKET) + +#ifndef SO_REUSEPORT +#define SO_REUSEPORT SO_REUSEADDR +#endif + +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV (0) +#endif + +#endif /* WSOCKET_H */ diff --git a/framework/lualib-src/luasocket/vc32.bat b/framework/lualib-src/luasocket/vc32.bat new file mode 100755 index 0000000..7ff8c0e --- /dev/null +++ b/framework/lualib-src/luasocket/vc32.bat @@ -0,0 +1,3 @@ +call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\vcvars32.bat" +cls +"c:\Program Files\Git\git-bash.exe" --cd-to-home diff --git a/framework/lualib-src/luasocket/vc64.bat b/framework/lualib-src/luasocket/vc64.bat new file mode 100755 index 0000000..ed5cb3a --- /dev/null +++ b/framework/lualib-src/luasocket/vc64.bat @@ -0,0 +1,3 @@ +call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat" +cls +"c:\Program Files\Git\git-bash.exe" --cd-to-home diff --git a/framework/lualib-src/luasocket/win32.cmd b/framework/lualib-src/luasocket/win32.cmd new file mode 100755 index 0000000..5eda3b1 --- /dev/null +++ b/framework/lualib-src/luasocket/win32.cmd @@ -0,0 +1 @@ +LUAV=5.3 PLAT=win32 LUAPREFIX_win32=/z/data/build/vc14 make diff --git a/framework/lualib-src/luasocket/win64.cmd b/framework/lualib-src/luasocket/win64.cmd new file mode 100755 index 0000000..b1f9ac0 --- /dev/null +++ b/framework/lualib-src/luasocket/win64.cmd @@ -0,0 +1 @@ +LUAV=5.3 PLAT=win64 LUAPREFIX_win64=/z/data/build/vc14 make diff --git a/framework/lualib/3rd/lecs/component.lua b/framework/lualib/3rd/lecs/component.lua deleted file mode 100755 index b6485b2..0000000 --- a/framework/lualib/3rd/lecs/component.lua +++ /dev/null @@ -1,12 +0,0 @@ ----@class Component -local Component = class("Component") - -function Component:ctor() - self.__isinst = true -end - -function Component:isInstComponent() - return self.__isinst -end - -return Component diff --git a/framework/lualib/3rd/lecs/entity.lua b/framework/lualib/3rd/lecs/entity.lua deleted file mode 100755 index 436f2c2..0000000 --- a/framework/lualib/3rd/lecs/entity.lua +++ /dev/null @@ -1,87 +0,0 @@ --- local Component = require("lecs.component") - ----@class Entity ----@field public id number ----@field private _world World ----@field private _singletonName boolean ----@field private _comps table -local Entity = class("Entity") - -function Entity:ctor() - self.id = 0 - self._world = nil - self._singletonName = nil - self._comps = {} -end - ----@public ----@param comp Component -function Entity:AddComponent(comp) - assert(comp:isInstComponent(), comp) - - self[comp.__cname] = comp - self._comps[comp.__cname] = 1 - - self._world:AddEntity(self) -end - ----@public ----@param name component name -function Entity:GetComponent(name) - return self[name] -end - ----@public ----@param comp Component -function Entity:RemoveComponent(comp) - self[comp.__cname] = nil - self._comps[comp.__cname] = nil - - self._world:AddEntity(self) -end - ----@public ----@return boolean -function Entity:IsSingleton() - return self._singletonName ~= nil -end - ----@public ----@return World -function Entity:GetWorld() - return self._world -end - ---region Private - ----@private ----@param world World ----@param id number ----@param singletonName string -function Entity:awakeFromPool(world, id, singletonName) - self.id = id - self._world = world - self._singletonName = singletonName - - if self._singletonName then - self._world:AddSingletonEntity(self) - end -end - ----@private -function Entity:recycleToPool() - ---clear all components - for k, _ in pairs(self._comps) do - self[k] = nil - end - - self._world = nil - self._comps = {} - self._singletonName = nil - - self.id = 0 -end - ---endregion - -return Entity diff --git a/framework/lualib/3rd/lecs/filter.lua b/framework/lualib/3rd/lecs/filter.lua deleted file mode 100755 index 71d620d..0000000 --- a/framework/lualib/3rd/lecs/filter.lua +++ /dev/null @@ -1,65 +0,0 @@ -local TinyECS = require("lecs.tiny_ecs") - ----@class Filter -local Filter = {} - ----@public ----@vararg Comp ----@return function -function Filter.RequireAll(...) - local f = {...} - for i = 1, #f do - f[i] = f[i].__cname - end - - return TinyECS.requireAll(table.unpack(f)) -end - ----@public ----@vararg Component ----@return function -function Filter.RequireAny(...) - local f = {...} - for i = 1, #f do - f[i] = f[i].__cname - end - return TinyECS.requireAny(table.unpack(f)) -end - ----@public ----@vararg Component ----@return function -function Filter.RejectAll(...) - local f = {...} - for i = 1, #f do - f[i] = f[i].__cname - end - return TinyECS.rejectAll(table.unpack(f)) -end - ----@public ----@vararg Component ----@return function -function Filter.RejectAny(...) - local f = {...} - for i = 1, #f do - f[i] = f[i].__cname - end - return TinyECS.rejectAny(table.unpack(f)) -end - ----@public ----@vararg function ----@return function -function Filter.And(...) - return TinyECS.requireAll(...) -end - ----@public ----@vararg function ----@return function -function Filter.Or(...) - return TinyECS.requireAny(...) -end - -return Filter diff --git a/framework/lualib/3rd/lecs/global.lua b/framework/lualib/3rd/lecs/global.lua deleted file mode 100755 index 5c47a4d..0000000 --- a/framework/lualib/3rd/lecs/global.lua +++ /dev/null @@ -1,29 +0,0 @@ -local Pool = require("lecs.pool") -local Entity = require("lecs.entity") - ----@class Global ----@field private _entityPool Pool -local Global = {} - -function Global:init() - self._entityPool = Pool({ - ctor = { - [Pool.DEFAULT_TAG] = Entity.new -- obj create function - } - }) -end - ----@param world World ----@return Entity -function Global:GetEntity(world, eid, singletonName) - return self._entityPool:get(world, eid, singletonName) -end - ----@param entity Entity -function Global:RecycleEntity(entity) - self._entityPool:recycle(entity) -end - -Global:init() - -return Global \ No newline at end of file diff --git a/framework/lualib/3rd/lecs/init.lua b/framework/lualib/3rd/lecs/init.lua deleted file mode 100755 index 5dee961..0000000 --- a/framework/lualib/3rd/lecs/init.lua +++ /dev/null @@ -1,8 +0,0 @@ --- https://github.com/RayStudio36/ray-ecs.lua - -return { - Component = require("lecs.component"), - Entity = require("lecs.entity"), - System = require("lecs.system"), - World = require("lecs.world") -} diff --git a/framework/lualib/3rd/lecs/pool.lua b/framework/lualib/3rd/lecs/pool.lua deleted file mode 100755 index 44afb07..0000000 --- a/framework/lualib/3rd/lecs/pool.lua +++ /dev/null @@ -1,234 +0,0 @@ -local setmetatable = setmetatable -local pairs = pairs - ---- ----Table pool for lua ---- ----@class Pool ----@field public DEFAULT_TAG string @default object tag for pool ----@field private _ctorHandle table ----@field private _pools table -local Pool = {} - -local DEFAULT_TAG = "__" - -Pool.DEFAULT_TAG = DEFAULT_TAG -Pool.__index = Pool - -setmetatable( - Pool, - { - __call = function(class, opt) - local instance = {} - setmetatable(instance, Pool) - instance:new(opt) - return instance - end - } -) - ----@param pool Pool ----@param tag string ----@param create boolean -local function getPoolArr(pool, tag, create) - local poolArr = pool._pools[tag] - if not poolArr and create then - poolArr = {} - pool._pools[tag] = poolArr - end - return poolArr -end - ----@param pool Pool ----@param tag string -local function createInstance(pool, tag) - tag = tag or DEFAULT_TAG - local ctorHandle = pool._ctorHandle[tag] - if ctorHandle then - return ctorHandle() - else - return {} - end -end - ----@param pool Pool ----@param tag string ----@vararg any -local function getFromPool(pool, tag, ...) - local ins = nil - if pool._pools[tag] then - local poolArr = pool._pools[tag] - if #poolArr > 0 then - ins = poolArr[#poolArr] - poolArr[#poolArr] = nil - end - end - - if not ins then - ins = createInstance(pool, tag) - end - - if ins.awakeFromPool then - ins:awakeFromPool(...) - end - - return ins -end - ----@param pool Pool ----@param tag string ----@param t string ----@vararg any -local function recycleToPool(pool, tag, t, ...) - local poolArr = getPoolArr(pool, tag, true) - - poolArr[#poolArr + 1] = t - if t.recycleToPool then - t:recycleToPool(...) - end -end - ----@param pool Pool ----@param tag string ----@param clearCtorHandle boolean -local function clearPool(pool, tag, clearCtorHandle) - pool._pools[tag] = nil - if clearCtorHandle then - pool._ctorHandle[tag] = nil - end -end - ----@private -function Pool:new(opt) - self._ctorHandle = {} - self._pools = {} - - if opt then - if opt.ctor then - for k, v in pairs(opt.ctor) do - self._ctorHandle[k] = v - end - end - - if opt.presize then - for k, v in pairs(opt.presize) do - self:presize(v, k) - end - end - end -end - ---- ----Set table constructor handle for object ----when get a table from pool, if there isn't a table in pool, ----will use this constructor handle to create new one and return it. ----@public ----@param handle function @create object handle ----@param tag string @[option] object tag -function Pool:setCtorHandle(handle, tag) - tag = tag or DEFAULT_TAG - self._ctorHandle[tag] = handle -end - ---- ----Get table from pool ----if table has a awakeFromPool function, ----will call this function with parameters by pass. ----@public ----@vararg any ----@return table -function Pool:get(...) - return getFromPool(self, DEFAULT_TAG, ...) -end - ---- ----Get table from pool with object tag ----if table has a awakeFromPool function, ----will call taht function with parameters by pass. ----@public ----@param tag string ----@vararg any ----@return table -function Pool:getWithTag(tag, ...) - return getFromPool(self, tag, ...) -end - ---- ----Recycle table to pool ----if table has a recycleToPool function, ----will call that function with parameters by pass. ----@public ----@param t table ----@vararg any -function Pool:recycle(t, ...) - recycleToPool(self, DEFAULT_TAG, t, ...) -end - ---- ----Recycle table to pool with object tag ----if table has a recycleToPool function, ----will call that function with parameters by pass. ----@public ----@param t table ----@param tag string ----@vararg any -function Pool:recycleWithTag(t, tag, ...) - recycleToPool(self, tag, t, ...) -end - ---- ----Pre create some table with an option object tag. ----@public ----@param count number ----@param tag string @[option] object tag ----@return boolean -function Pool:presize(count, tag) - local poolArr = getPoolArr(self, tag or DEFAULT_TAG, true) - if #poolArr >= count then - return false - end - - local needCreateSize = count - #poolArr - local ctorHandle = self._ctorHandle[tag] - local createFunction = ctorHandle and function() - return ctorHandle() - end or function() - return {} - end - - for _ = 1, needCreateSize do - poolArr[#poolArr + 1] = createFunction() - end - - return true -end - ---- ----Clear objects in pool ----@public ----@param clearCtorHandle boolean @default false -function Pool:clear(clearCtorHandle) - clearCtorHandle = clearCtorHandle or false - clearPool(self, DEFAULT_TAG, clearCtorHandle) -end - ---- ----Clear all objects in pool ----@public ----@param clearCtorHandle boolean @default false -function Pool:clearAll(_) - self._pools = {} - self._ctorHandle = {} -end - ---- ----Clear all objects with an object tag in pool ----@public ----@param tag string ----@param clearCtorHandle boolean @default false -function Pool:clearTag(tag, clearCtorHandle) - clearCtorHandle = clearCtorHandle or false - clearPool(self, tag, clearCtorHandle) -end - -return Pool diff --git a/framework/lualib/3rd/lecs/system.lua b/framework/lualib/3rd/lecs/system.lua deleted file mode 100755 index df2acf1..0000000 --- a/framework/lualib/3rd/lecs/system.lua +++ /dev/null @@ -1,37 +0,0 @@ -local TinyECS = require("lecs.tiny_ecs") -local Filter = require("lecs.filter") - ----@class System ----@field protected filter Filter ----@field private _system table ----@field private _world World -local System = class("System") - -function System:ctor() - self.filter = Filter - - local system = TinyECS.system() - system.filter = self:CreateFilter() - system.update = function(t, dt) - self:Update(t.entities, dt) - end - - self._system = system - self._world = nil -end - -function System:CreateFilter() -end - ----@param entities Entity[] ----@param dt number -function System:Update(_, _) -end - ----@protected ----@return World -function System:GetWorld() - return self._world -end - -return System diff --git a/framework/lualib/3rd/lecs/test.lua b/framework/lualib/3rd/lecs/test.lua deleted file mode 100755 index 2343541..0000000 --- a/framework/lualib/3rd/lecs/test.lua +++ /dev/null @@ -1,44 +0,0 @@ -local lecs = require("lecs.init") -local Component = lecs.Component -local System = lecs.System -local World = lecs.World - -local PlayerComponent = class("PlayerComponent", Component) -function PlayerComponent:ctor(...) - PlayerComponent.super.ctor(self, ...) - - self.name = "Joe" - self.phrase = "I'm a plumber." - self.mass = 150 - self.hairColor = "brown" -end - -local TalkingSystem = class("TalkingSystem", System) -function TalkingSystem:CreateFilter() - return self.filter.RequireAll(PlayerComponent) -end - -function TalkingSystem:Update(entities, dt) - for _, e in ipairs(entities) do - local PlayerComp = e:GetComponent("PlayerComponent") - PlayerComp.mass = PlayerComp.mass + dt * 3 - print(("%s who weighs %d pounds, says %q."):format(PlayerComp.name, PlayerComp.mass, PlayerComp.phrase)) - end -end - -local world = World.new() -local talk_system = TalkingSystem.new() -world:AddSystem(talk_system) - -local playerEntity = world:CreateEntity() -playerEntity:AddComponent(PlayerComponent.new()) - -return function() - for _ = 1, 20 do - world:Update(1) - end -end - --- skynet.timeout(20 * 100, function () --- require("fecs.test")() --- end) diff --git a/framework/lualib/3rd/lecs/tiny_ecs.lua b/framework/lualib/3rd/lecs/tiny_ecs.lua deleted file mode 100755 index 8f0b26d..0000000 --- a/framework/lualib/3rd/lecs/tiny_ecs.lua +++ /dev/null @@ -1,864 +0,0 @@ ---[[ -Copyright (c) 2016 Calvin Rose - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -]] ---- @module tiny-ecs --- @https://github.com/bakpakin/tiny-ecs --- @author Calvin Rose --- @license MIT --- @copyright 2016 -local tiny = {} - --- Local versions of standard lua functions -local tinsert = table.insert -local tremove = table.remove -local tsort = table.sort -local setmetatable = setmetatable -local type = type -local select = select - --- Local versions of the library functions -local tiny_manageEntities -local tiny_manageSystems -local tiny_addEntity -local tiny_addSystem -local tiny_add -local tiny_removeEntity -local tiny_removeSystem - ---- Filter functions. --- A Filter is a function that selects which Entities apply to a System. --- Filters take two parameters, the System and the Entity, and return a boolean --- value indicating if the Entity should be processed by the System. A truthy --- value includes the entity, while a falsey (nil or false) value excludes the --- entity. --- --- Filters must be added to Systems by setting the `filter` field of the System. --- Filter's returned by tiny-ecs's Filter functions are immutable and can be --- used by multiple Systems. --- --- local f1 = tiny.requireAll("position", "velocity", "size") --- local f2 = tiny.requireAny("position", "velocity", "size") --- --- local e1 = { --- position = {2, 3}, --- velocity = {3, 3}, --- size = {4, 4} --- } --- --- local entity2 = { --- position = {4, 5}, --- size = {4, 4} --- } --- --- local e3 = { --- position = {2, 3}, --- velocity = {3, 3} --- } --- --- print(f1(nil, e1), f1(nil, e2), f1(nil, e3)) -- prints true, false, false --- print(f2(nil, e1), f2(nil, e2), f2(nil, e3)) -- prints true, true, true --- --- Filters can also be passed as arguments to other Filter constructors. This is --- a powerful way to create complex, custom Filters that select a very specific --- set of Entities. --- --- -- Selects Entities with an "image" Component, but not Entities with a --- -- "Player" or "Enemy" Component. --- filter = tiny.requireAll("image", tiny.rejectAny("Player", "Enemy")) --- --- @section Filter - --- A helper function to compile filters. -local filterJoin - --- A helper function to filters from string -local filterBuildString - -do - local loadstring = load - local function getchr(c) - return "\\" .. c:byte() - end - local function make_safe(text) - return ("%q"):format(text):gsub("\n", "n"):gsub("[\128-\255]", getchr) - end - - local function filterJoinRaw(prefix, seperator, ...) - local accum = {} - local build = {} - for i = 1, select("#", ...) do - local item = select(i, ...) - if type(item) == "string" then - accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item)) - elseif type(item) == "function" then - build[#build + 1] = ("local subfilter_%d_ = select(%d, ...)"):format(i, i) - accum[#accum + 1] = ("(subfilter_%d_(system, e))"):format(i) - else - error "Filter token must be a string or a filter function." - end - end - local source = - ("%s\nreturn function(system, e) return %s(%s) end"):format( - table.concat(build, "\n"), - prefix, - table.concat(accum, seperator) - ) - local loader, err = loadstring(source) - if err then - error(err) - end - return loader(...) - end - - function filterJoin(...) - local state, value = pcall(filterJoinRaw, ...) - if state then - return value - else - return nil, value - end - end - - local function buildPart(str) - local accum = {} - local subParts = {} - str = - str:gsub( - "%b()", - function(p) - subParts[#subParts + 1] = buildPart(p:sub(2, -2)) - return ("\255%d"):format(#subParts) - end - ) - for invert, part, sep in str:gmatch("(%!?)([^%|%&%!]+)([%|%&]?)") do - if part:match("^\255%d+$") then - local partIndex = tonumber(part:match(part:sub(2))) - accum[#accum + 1] = ("%s(%s)"):format(invert == "" and "" or "not", subParts[partIndex]) - else - accum[#accum + 1] = ("(e[%s] %s nil)"):format(make_safe(part), invert == "" and "~=" or "==") - end - if sep ~= "" then - accum[#accum + 1] = (sep == "|" and " or " or " and ") - end - end - return table.concat(accum) - end - - function filterBuildString(str) - local source = ("return function(_, e) return %s end"):format(buildPart(str)) - local loader, err = loadstring(source) - if err then - error(err) - end - return loader() - end -end - ---- Makes a Filter that selects Entities with all specified Components and --- Filters. -function tiny.requireAll(...) - return filterJoin("", " and ", ...) -end - ---- Makes a Filter that selects Entities with at least one of the specified --- Components and Filters. -function tiny.requireAny(...) - return filterJoin("", " or ", ...) -end - ---- Makes a Filter that rejects Entities with all specified Components and --- Filters, and selects all other Entities. -function tiny.rejectAll(...) - return filterJoin("not", " and ", ...) -end - ---- Makes a Filter that rejects Entities with at least one of the specified --- Components and Filters, and selects all other Entities. -function tiny.rejectAny(...) - return filterJoin("not", " or ", ...) -end - ---- Makes a Filter from a string. Syntax of `pattern` is as follows. --- --- * Tokens are alphanumeric strings including underscores. --- * Tokens can be separated by |, &, or surrounded by parentheses. --- * Tokens can be prefixed with !, and are then inverted. --- --- Examples are best: --- 'a|b|c' - Matches entities with an 'a' OR 'b' OR 'c'. --- 'a&!b&c' - Matches entities with an 'a' AND NOT 'b' AND 'c'. --- 'a|(b&c&d)|e - Matches 'a' OR ('b' AND 'c' AND 'd') OR 'e' --- @param pattern -function tiny.filter(pattern) - local state, value = pcall(filterBuildString, pattern) - if state then - return value - else - return nil, value - end -end - ---- System functions. --- A System is a wrapper around function callbacks for manipulating Entities. --- Systems are implemented as tables that contain at least one method; --- an update function that takes parameters like so: --- --- * `function system:update(dt)`. --- --- There are also a few other optional callbacks: --- --- * `function system:filter(entity)` - Returns true if this System should --- include this Entity, otherwise should return false. If this isn't specified, --- no Entities are included in the System. --- * `function system:onAdd(entity)` - Called when an Entity is added to the --- System. --- * `function system:onRemove(entity)` - Called when an Entity is removed --- from the System. --- * `function system:onModify(dt)` - Called when the System is modified by --- adding or removing Entities from the System. --- * `function system:onAddToWorld(world)` - Called when the System is added --- to the World, before any entities are added to the system. --- * `function system:onRemoveFromWorld(world)` - Called when the System is --- removed from the world, after all Entities are removed from the System. --- * `function system:preWrap(dt)` - Called on each system before update is --- called on any system. --- * `function system:postWrap(dt)` - Called on each system in reverse order --- after update is called on each system. The idea behind `preWrap` and --- `postWrap` is to allow for systems that modify the behavior of other systems. --- Say there is a DrawingSystem, which draws sprites to the screen, and a --- PostProcessingSystem, that adds some blur and bloom effects. In the preWrap --- method of the PostProcessingSystem, the System could set the drawing target --- for the DrawingSystem to a special buffer instead the screen. In the postWrap --- method, the PostProcessingSystem could then modify the buffer and render it --- to the screen. In this setup, the PostProcessingSystem would be added to the --- World after the drawingSystem (A similar but less flexible behavior could --- be accomplished with a single custom update function in the DrawingSystem). --- --- For Filters, it is convenient to use `tiny.requireAll` or `tiny.requireAny`, --- but one can write their own filters as well. Set the Filter of a System like --- so: --- system.filter = tiny.requireAll("a", "b", "c") --- or --- function system:filter(entity) --- return entity.myRequiredComponentName ~= nil --- end --- --- All Systems also have a few important fields that are initialized when the --- system is added to the World. A few are important, and few should be less --- commonly used. --- --- * The `world` field points to the World that the System belongs to. Useful --- for adding and removing Entities from the world dynamically via the System. --- * The `active` flag is whether or not the System is updated automatically. --- Inactive Systems should be updated manually or not at all via --- `system:update(dt)`. Defaults to true. --- * The `entities` field is an ordered list of Entities in the System. This --- list can be used to quickly iterate through all Entities in a System. --- * The `interval` field is an optional field that makes Systems update at --- certain intervals using buffered time, regardless of World update frequency. --- For example, to make a System update once a second, set the System's interval --- to 1. --- * The `index` field is the System's index in the World. Lower indexed --- Systems are processed before higher indices. The `index` is a read only --- field; to set the `index`, use `tiny.setSystemIndex(world, system)`. --- * The `indices` field is a table of Entity keys to their indices in the --- `entities` list. Most Systems can ignore this. --- * The `modified` flag is an indicator if the System has been modified in --- the last update. If so, the `onModify` callback will be called on the System --- in the next update, if it has one. This is usually managed by tiny-ecs, so --- users should mostly ignore this, too. --- --- There is another option to (hopefully) increase performance in systems that --- have items added to or removed from them often, and have lots of entities in --- them. Setting the `nocache` field of the system might improve performance. --- It is still experimental. There are some restriction to systems without --- caching, however. --- --- * There is no `entities` table. --- * Callbacks such onAdd, onRemove, and onModify will never be called --- * Noncached systems cannot be sorted (There is no entities list to sort). --- --- @section System - --- Use an empty table as a key for identifying Systems. Any table that contains --- this key is considered a System rather than an Entity. -local systemTableKey = {"SYSTEM_TABLE_KEY"} - --- Checks if a table is a System. -local function isSystem(table) - return table[systemTableKey] -end - --- Update function for all Processing Systems. -local function processingSystemUpdate(system, dt) - local preProcess = system.preProcess - local process = system.process - local postProcess = system.postProcess - - if preProcess then - preProcess(system, dt) - end - - if process then - if system.nocache then - local entities = system.world.entities - local filter = system.filter - if filter then - for i = 1, #entities do - local entity = entities[i] - if filter(system, entity) then - process(system, entity, dt) - end - end - end - else - local entities = system.entities - for i = 1, #entities do - process(system, entities[i], dt) - end - end - end - - if postProcess then - postProcess(system, dt) - end -end - --- Sorts Systems by a function system.sortDelegate(entity1, entity2) on modify. -local function sortedSystemOnModify(system) - local entities = system.entities - local indices = system.indices - local sortDelegate = system.sortDelegate - if not sortDelegate then - local compare = system.compare - sortDelegate = function(e1, e2) - return compare(system, e1, e2) - end - system.sortDelegate = sortDelegate - end - tsort(entities, sortDelegate) - for i = 1, #entities do - indices[entities[i]] = i - end -end - ---- Creates a new System or System class from the supplied table. If `table` is --- nil, creates a new table. -function tiny.system(table) - table = table or {} - table[systemTableKey] = true - return table -end - ---- Creates a new Processing System or Processing System class. Processing --- Systems process each entity individual, and are usually what is needed. --- Processing Systems have three extra callbacks besides those inheritted from --- vanilla Systems. --- --- function system:preProcess(dt) -- Called before iteration. --- function system:process(entity, dt) -- Process each entity. --- function system:postProcess(dt) -- Called after iteration. --- --- Processing Systems have their own `update` method, so don't implement a --- a custom `update` callback for Processing Systems. --- @see system -function tiny.processingSystem(table) - table = table or {} - table[systemTableKey] = true - table.update = processingSystemUpdate - return table -end - ---- Creates a new Sorted System or Sorted System class. Sorted Systems sort --- their Entities according to a user-defined method, `system:compare(e1, e2)`, --- which should return true if `e1` should come before `e2` and false otherwise. --- Sorted Systems also override the default System's `onModify` callback, so be --- careful if defining a custom callback. However, for processing the sorted --- entities, consider `tiny.sortedProcessingSystem(table)`. --- @see system -function tiny.sortedSystem(table) - table = table or {} - table[systemTableKey] = true - table.onModify = sortedSystemOnModify - return table -end - ---- Creates a new Sorted Processing System or Sorted Processing System class. --- Sorted Processing Systems have both the aspects of Processing Systems and --- Sorted Systems. --- @see system --- @see processingSystem --- @see sortedSystem -function tiny.sortedProcessingSystem(table) - table = table or {} - table[systemTableKey] = true - table.update = processingSystemUpdate - table.onModify = sortedSystemOnModify - return table -end - ---- World functions. --- A World is a container that manages Entities and Systems. Typically, a --- program uses one World at a time. --- --- For all World functions except `tiny.world(...)`, object-oriented syntax can --- be used instead of the documented syntax. For example, --- `tiny.add(world, e1, e2, e3)` is the same as `world:add(e1, e2, e3)`. --- @section World - --- Forward declaration -local worldMetaTable - ---- Creates a new World. --- Can optionally add default Systems and Entities. Returns the new World along --- with default Entities and Systems. -function tiny.world(...) - local ret = - setmetatable( - { - -- List of Entities to remove - entitiesToRemove = {}, - -- List of Entities to change - entitiesToChange = {}, - -- List of Entities to add - systemsToAdd = {}, - -- List of Entities to remove - systemsToRemove = {}, - -- Set of Entities - entities = {}, - -- List of Systems - systems = {} - }, - worldMetaTable - ) - - tiny_add(ret, ...) - tiny_manageSystems(ret) - tiny_manageEntities(ret) - - return ret, ... -end - ---- Adds an Entity to the world. --- Also call this on Entities that have changed Components such that they --- match different Filters. Returns the Entity. -function tiny.addEntity(world, entity) - local e2c = world.entitiesToChange - e2c[#e2c + 1] = entity - return entity -end -tiny_addEntity = tiny.addEntity - ---- Adds a System to the world. Returns the System. -function tiny.addSystem(world, system) - assert(system.world == nil, "System already belongs to a World.") - local s2a = world.systemsToAdd - s2a[#s2a + 1] = system - system.world = world - return system -end -tiny_addSystem = tiny.addSystem - ---- Shortcut for adding multiple Entities and Systems to the World. Returns all --- added Entities and Systems. -function tiny.add(world, ...) - for i = 1, select("#", ...) do - local obj = select(i, ...) - if obj then - if isSystem(obj) then - tiny_addSystem(world, obj) - else -- Assume obj is an Entity - tiny_addEntity(world, obj) - end - end - end - return ... -end -tiny_add = tiny.add - ---- Removes an Entity from the World. Returns the Entity. -function tiny.removeEntity(world, entity) - local e2r = world.entitiesToRemove - e2r[#e2r + 1] = entity - return entity -end -tiny_removeEntity = tiny.removeEntity - ---- Removes a System from the world. Returns the System. -function tiny.removeSystem(world, system) - assert(system.world == world, "System does not belong to this World.") - local s2r = world.systemsToRemove - s2r[#s2r + 1] = system - return system -end -tiny_removeSystem = tiny.removeSystem - ---- Shortcut for removing multiple Entities and Systems from the World. Returns --- all removed Systems and Entities -function tiny.remove(world, ...) - for i = 1, select("#", ...) do - local obj = select(i, ...) - if obj then - if isSystem(obj) then - tiny_removeSystem(world, obj) - else -- Assume obj is an Entity - tiny_removeEntity(world, obj) - end - end - end - return ... -end - --- Adds and removes Systems that have been marked from the World. -function tiny_manageSystems(world) - local s2a, s2r = world.systemsToAdd, world.systemsToRemove - - -- Early exit - if #s2a == 0 and #s2r == 0 then - return - end - - world.systemsToAdd = {} - world.systemsToRemove = {} - - local worldEntityList = world.entities - local systems = world.systems - - -- Remove Systems - for i = 1, #s2r do - local system = s2r[i] - local index = system.index - local onRemove = system.onRemove - if onRemove and not system.nocache then - local entityList = system.entities - for j = 1, #entityList do - onRemove(system, entityList[j]) - end - end - tremove(systems, index) - for j = index, #systems do - systems[j].index = j - end - local onRemoveFromWorld = system.onRemoveFromWorld - if onRemoveFromWorld then - onRemoveFromWorld(system, world) - end - s2r[i] = nil - - -- Clean up System - system.world = nil - system.entities = nil - system.indices = nil - system.index = nil - end - - -- Add Systems - for i = 1, #s2a do - local system = s2a[i] - if systems[system.index or 0] ~= system then - if not system.nocache then - system.entities = {} - system.indices = {} - end - if system.active == nil then - system.active = true - end - system.modified = true - system.world = world - local index = #systems + 1 - system.index = index - systems[index] = system - local onAddToWorld = system.onAddToWorld - if onAddToWorld then - onAddToWorld(system, world) - end - - -- Try to add Entities - if not system.nocache then - local entityList = system.entities - local entityIndices = system.indices - local onAdd = system.onAdd - local filter = system.filter - if filter then - for j = 1, #worldEntityList do - local entity = worldEntityList[j] - if filter(system, entity) then - local entityIndex = #entityList + 1 - entityList[entityIndex] = entity - entityIndices[entity] = entityIndex - if onAdd then - onAdd(system, entity) - end - end - end - end - end - end - s2a[i] = nil - end -end - --- Adds, removes, and changes Entities that have been marked. -function tiny_manageEntities(world) - local e2r = world.entitiesToRemove - local e2c = world.entitiesToChange - - -- Early exit - if #e2r == 0 and #e2c == 0 then - return - end - - world.entitiesToChange = {} - world.entitiesToRemove = {} - - local entities = world.entities - local systems = world.systems - - -- Change Entities - for i = 1, #e2c do - local entity = e2c[i] - -- Add if needed - if not entities[entity] then - local index = #entities + 1 - entities[entity] = index - entities[index] = entity - end - for j = 1, #systems do - local system = systems[j] - if not system.nocache then - local ses = system.entities - local seis = system.indices - local index = seis[entity] - local filter = system.filter - if filter and filter(system, entity) then - if not index then - system.modified = true - index = #ses + 1 - ses[index] = entity - seis[entity] = index - local onAdd = system.onAdd - if onAdd then - onAdd(system, entity) - end - end - elseif index then - system.modified = true - local tmpEntity = ses[#ses] - ses[index] = tmpEntity - seis[tmpEntity] = index - seis[entity] = nil - ses[#ses] = nil - local onRemove = system.onRemove - if onRemove then - onRemove(system, entity) - end - end - end - end - e2c[i] = nil - end - - -- Remove Entities - for i = 1, #e2r do - local entity = e2r[i] - e2r[i] = nil - local listIndex = entities[entity] - if listIndex then - -- Remove Entity from world state - local lastEntity = entities[#entities] - entities[lastEntity] = listIndex - entities[entity] = nil - entities[listIndex] = lastEntity - entities[#entities] = nil - -- Remove from cached systems - for j = 1, #systems do - local system = systems[j] - if not system.nocache then - local ses = system.entities - local seis = system.indices - local index = seis[entity] - if index then - system.modified = true - local tmpEntity = ses[#ses] - ses[index] = tmpEntity - seis[tmpEntity] = index - seis[entity] = nil - ses[#ses] = nil - local onRemove = system.onRemove - if onRemove then - onRemove(system, entity) - end - end - end - end - end - end -end - ---- Manages Entities and Systems marked for deletion or addition. Call this --- before modifying Systems and Entities outside of a call to `tiny.update`. --- Do not call this within a call to `tiny.update`. -function tiny.refresh(world) - tiny_manageSystems(world) - tiny_manageEntities(world) - local systems = world.systems - for i = #systems, 1, -1 do - local system = systems[i] - if system.active then - local onModify = system.onModify - if onModify and system.modified then - onModify(system, 0) - end - system.modified = false - end - end -end - ---- Updates the World by dt (delta time). Takes an optional parameter, `filter`, --- which is a Filter that selects Systems from the World, and updates only those --- Systems. If `filter` is not supplied, all Systems are updated. Put this --- function in your main loop. -function tiny.update(world, dt, filter) - tiny_manageSystems(world) - tiny_manageEntities(world) - - local systems = world.systems - - -- Iterate through Systems IN REVERSE ORDER - for i = #systems, 1, -1 do - local system = systems[i] - if system.active then - -- Call the modify callback on Systems that have been modified. - local onModify = system.onModify - if onModify and system.modified then - onModify(system, dt) - end - local preWrap = system.preWrap - if preWrap and ((not filter) or filter(world, system)) then - preWrap(system, dt) - end - end - end - - -- Iterate through Systems IN ORDER - for i = 1, #systems do - local system = systems[i] - if system.active and ((not filter) or filter(world, system)) then - -- Update Systems that have an update method (most Systems) - local update = system.update - if update then - local interval = system.interval - if interval then - local bufferedTime = (system.bufferedTime or 0) + dt - while bufferedTime >= interval do - bufferedTime = bufferedTime - interval - update(system, interval) - end - system.bufferedTime = bufferedTime - else - update(system, dt) - end - end - - system.modified = false - end - end - - -- Iterate through Systems IN ORDER AGAIN - for i = 1, #systems do - local system = systems[i] - local postWrap = system.postWrap - if postWrap and system.active and ((not filter) or filter(world, system)) then - postWrap(system, dt) - end - end -end - ---- Removes all Entities from the World. -function tiny.clearEntities(world) - local el = world.entities - for i = 1, #el do - tiny_removeEntity(world, el[i]) - end -end - ---- Removes all Systems from the World. -function tiny.clearSystems(world) - local systems = world.systems - for i = #systems, 1, -1 do - tiny_removeSystem(world, systems[i]) - end -end - ---- Gets number of Entities in the World. -function tiny.getEntityCount(world) - return #world.entities -end - ---- Gets number of Systems in World. -function tiny.getSystemCount(world) - return #world.systems -end - ---- Sets the index of a System in the World, and returns the old index. Changes --- the order in which they Systems processed, because lower indexed Systems are --- processed first. Returns the old system.index. -function tiny.setSystemIndex(world, system, index) - tiny_manageSystems(world) - local oldIndex = system.index - local systems = world.systems - - if index < 0 then - index = tiny.getSystemCount(world) + 1 + index - end - - tremove(systems, oldIndex) - tinsert(systems, index, system) - - for i = oldIndex, index, index >= oldIndex and 1 or -1 do - systems[i].index = i - end - - return oldIndex -end - --- Construct world metatable. -worldMetaTable = { - __index = { - add = tiny.add, - addEntity = tiny.addEntity, - addSystem = tiny.addSystem, - remove = tiny.remove, - removeEntity = tiny.removeEntity, - removeSystem = tiny.removeSystem, - refresh = tiny.refresh, - update = tiny.update, - clearEntities = tiny.clearEntities, - clearSystems = tiny.clearSystems, - getEntityCount = tiny.getEntityCount, - getSystemCount = tiny.getSystemCount, - setSystemIndex = tiny.setSystemIndex - }, - __tostring = function() - return "" - end -} - -return tiny diff --git a/framework/lualib/3rd/lecs/world.lua b/framework/lualib/3rd/lecs/world.lua deleted file mode 100755 index 52dd960..0000000 --- a/framework/lualib/3rd/lecs/world.lua +++ /dev/null @@ -1,137 +0,0 @@ -local Global = require("lecs.global") -local TinyECS = require("lecs.tiny_ecs") - ----@class World ----@field private _world table ----@field private _eid number ----@field private _singletonEntity table ----@field private _changedEntityCache Entity -local World = class("World") - -function World:ctor() - self._world = TinyECS.world() - self._eid = 0 - self._singletonEntity = {} - - self._changedEntityCache = nil -end - --- region Public - ----@public ----@param dt number -function World:Update(dt) - if self._changedEntityCache then - self._changedEntityCache = nil - end - - self._world:update(dt) -end - ----@public ----@param system System -function World:AddSystem(system) - self._world:addSystem(system._system) - system._world = self -end - ----@public ----@param system System -function World:RemoveSystem(system) - self._world:removeSystem(system._system) - system._world = nil -end - ----@public ----@return Entity -function World:CreateEntity() - local e = Global:GetEntity(self, self:GetEid()) - self:AddEntity(e) - return e -end - ----@public ----@param name string SingletonEntity Identifier ----@return Entity -function World:CreateSingletonEntity(name) - if self._singletonEntity[name] then - return self._singletonEntity[name] - end - - local e = Global:GetEntity(self, self:GetEid(), name) - self:AddEntity(e) - return e -end - ----@public ----@param entity Entity -function World:RemoveEntity(entity) - if entity._singletonName then - self:removeSingletonEntity(entity) - end - - self._world:removeEntity(entity) - - if self._changedEntityCache == entity then - self._changedEntityCache = nil - end - - Global:RecycleEntity(entity) -end - ----@public ----@param singletonName string ----@return Entity -function World:GetSingletonEntity(singletonName) - return self._singletonEntity[singletonName] -end - --- endregion - --- region Private - ----@private -function World:GetEid() - self._eid = self._eid + 1 - return self._eid -end - ----@private ----@param entity Entity -function World:AddEntity(entity) - if entity ~= self._changedEntityCache then - self._changedEntityCache = entity - self._world:addEntity(entity) - end -end - ----@private ----@param entity Entity -function World:AddSingletonEntity(entity) - if self._singletonEntity[entity._singletonName] then - print(string.format("Singleton entity %s already in this world"), entity._singletonName) - return - end - - self._singletonEntity[entity._singletonName] = entity -end - ----@private ----@param entity Entity | string -function World:RemoveSingletonEntity(entity) - local singletonName = entity - if not type(entity) == "string" then - singletonName = entity._singletonName - end - - if not self._singletonEntity[singletonName] then - print(string.format("Entity %s isn't a singleton"), singletonName) - return - end - - self._singletonEntity[singletonName] = nil -end - --- endregion - -return World diff --git a/framework/lualib/3rd/termfx/blittest.lua b/framework/lualib/3rd/termfx/blittest.lua new file mode 100755 index 0000000..259ccc1 --- /dev/null +++ b/framework/lualib/3rd/termfx/blittest.lua @@ -0,0 +1,81 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +tfx = require "termfx" + +tfx.init() + +function makespr(s, fg, bg) + local c = "-:+=%ZXH#" + local spr = tfx.newbuffer(s, s/2) + spr:attributes(fg, bg) + for y=1, s/2 do + for x=1, s do + local lx, ly = (x-0.5)/s, (2*y-1)/s + local v = math.floor((math.sin(lx*math.pi) + math.sin(ly*math.pi)) / 2 * #c) + local ch = string.sub(c, v, v) + spr:setcell(x, y, ch) + end + end + return spr +end + +tfx.outputmode(tfx.output.NORMAL) + +ok, err = pcall(function() + local sprites = {} + local blit2screen = true + + for i=1, 5 do + sprites[i] = makespr(2^(i+2), i+1, tfx.color.BLACK) + end + + local snum = 1 + local spr = sprites[snum] + local sw, sh = spr:width(), spr:height() + local x, y = 1-sw, 1-sh + local xo, yo = 1, 1 + local fw, fh = tfx.width(), tfx.height() + local target = tfx.newbuffer(fw - 2, fh - 2) + local w, h = target:width(), target:height() + + repeat + + if blit2screen then + tfx.clear(tfx.color.WHITE, tfx.color.BLACK) + tfx.blit(x, y, spr) + else + tfx.clear(tfx.color.WHITE, tfx.color.RED) + target:clear(tfx.color.WHITE, tfx.color.BLACK) + target:blit(x, y, spr) + tfx.blit(2, 2, target) + end + + x = x + xo + if x > w or x < 1-sw then xo = -xo end + y = y + yo + if y > h or y < 1-sh then yo = -yo end + + tfx.printat(1, tfx.height(), "print 1.."..#sprites.." for sprite size, t to toggle blit to screen or buffer, q to quit") + tfx.printat(1, 1, "Current size: "..snum.." ("..spr:width().."x"..spr:height()..")") + + tfx.present() + evt = tfx.pollevent(333) + snum = evt and tonumber(evt.char) or snum + if snum >= 1 and snum <= #sprites then + spr = sprites[snum] + sw, sh = spr:width(), spr:height() + if x < 1-sw then x = 1-sw xo = 1 end + if y < 1-sh then y = 1-sh yo = 1 end + end + + if evt and evt.char == 't' then + blit2screen = not blit2screen + end + + until evt and evt.type == "key" and evt.char == "q" +end) + +tfx.shutdown() +if not ok then print("Error: "..err) end diff --git a/framework/lualib/3rd/termfx/colortest.lua b/framework/lualib/3rd/termfx/colortest.lua new file mode 100755 index 0000000..08efeeb --- /dev/null +++ b/framework/lualib/3rd/termfx/colortest.lua @@ -0,0 +1,115 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +package.path = "samples/?.lua;"..package.path + +tfx = require "termfx" +ui = require "simpleui" + +tfx.init() + +function find_name(tbl, val) + for k, v in pairs(tbl) do + if val == v then return k end + end + return nil +end + +function tbl_keys(tbl) + local res = {} + for k, v in pairs(tbl) do + res[#res+1] = k + end + table.sort(res, function(i, k) return tbl[i] < tbl[k] end) + return res +end + +function select_outputmode() + local which = ui.select("select output mode", tbl_keys(tfx.output)) + if which then tfx.outputmode(tfx.output[which]) end +end + +function pr_colormap(xofs, yofs) + xofs = xofs or 0 + if 36 - math.floor(tfx.width() / 7) < xofs then + xofs = 36 - math.floor(tfx.width() / 7) + elseif xofs < 0 then + xofs = 0 + end + local omode = tfx.outputmode() + if omode ~= tfx.output.COL256 and omode ~= tfx.output.COL216 then + return xofs + end + local r, g, b + for r = 0, 5 do + for g = 0, 5 do + for b = 0, 5 do + local col = tfx.rgb2color(r, g, b) + local fgcol = tfx.rgb2color(5-r, 5-g, 5-b) + local x = (g * 6 + r - xofs) * 7 + 1 + local y = b * 2 + 1 + yofs + tfx.attributes(fgcol, col) + local value = tfx.colorinfo(col) + tfx.printat(x, y, r..":"..g..":"..b.."=") + tfx.printat(x, y+1, string.sub(value, 2)) + end + end + end + return xofs +end + +function pr_greymap(yofs) + local omode = tfx.outputmode() + if omode ~= tfx.output.COL256 and omode ~= tfx.output.GRAYSCALE then + return + end + local val + for val = 0, 25 do + local col = tfx.grey2color(val) + local fgcol = tfx.grey2color(25-val) + local x = 10 * (val % 8) + 1 + local y = math.floor(val / 8) + 1 + yofs + tfx.attributes(fgcol, col) + local value = tfx.colorinfo(col) + tfx.printat(x, y, string.format("%02d=%s", val, string.sub(value, 2))) + end +end + +ok, err = pcall(function() + + tfx.outputmode(tfx.output.COL256) + + local quit = false + local xofs = 0 + local evt, om + repeat + + tfx.clear(tfx.color.WHITE, tfx.color.BLACK) + tfx.printat(1, tfx.height(), "press O to select output mode, LEFT and RIGHT to scroll color table, Q to quit") + + tfx.printat(1, 1, _VERSION) + om = find_name(tfx.output, tfx.outputmode()) + tfx.printat(tfx.width() - #om, 1, om) + xofs = pr_colormap(xofs, 2) + pr_greymap(16) + + tfx.present() + evt = tfx.pollevent() + + tfx.attributes(tfx.color.WHITE, tfx.color.BLUE) + if evt.char == "q" or evt.char == "Q" then + quit = ui.ask("Really quit?") + elseif evt.char == "o" or evt.char == "O" then + select_outputmode() + elseif evt.key == tfx.key.ARROW_RIGHT then + xofs = xofs + 1 + elseif evt.key == tfx.key.ARROW_LEFT then + xofs = xofs - 1 + end + + until quit + +end) +tfx.shutdown() +if not ok then print("Error: "..err) end diff --git a/framework/lualib/3rd/termfx/copyregiontest.lua b/framework/lualib/3rd/termfx/copyregiontest.lua new file mode 100755 index 0000000..a1066dd --- /dev/null +++ b/framework/lualib/3rd/termfx/copyregiontest.lua @@ -0,0 +1,55 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +package.path = "samples/?.lua;"..package.path + +tfx = require "termfx" +ui = require "simpleui" + +tfx.init() + +local w, h = 16, 12 + +ok, err = pcall(function() + + tfx.outputmode(tfx.output.COL256) + + local sx = math.floor(tfx.width() / 2) - 4 + local sy = math.floor(tfx.height() / 2) - 4 + local tx, ty = sx, sy + local x, y + + local quit = false + local evt + repeat + tfx.attributes(tfx.color.WHITE, tfx.color.BLACK) + tfx.clear() + + for x = 1, w do + for y = 1, h do + tfx.setcell(sx - 1 + x, sy - 1 + y, string.format("%X", math.max(x, y) - 1), tfx.color.RED, tfx.color.BLUE) + end + end + + tfx.copyregion(tx, ty, sx, sy, w, h) + + tfx.present() + evt = tfx.pollevent() + if evt.char == "q" or evt.char == "Q" then + tfx.attributes(tfx.color.WHITE, tfx.color.BLUE) + quit = ui.ask("Really quit?") + elseif evt.key == tfx.key.ARROW_LEFT and tx > 1 - w then + tx = tx - 1 + elseif evt.key == tfx.key.ARROW_RIGHT and tx <= tfx.width() then + tx = tx + 1 + elseif evt.key == tfx.key.ARROW_UP and ty > 1 - h then + ty = ty - 1 + elseif evt.key == tfx.key.ARROW_DOWN and ty <= tfx.height() then + ty = ty + 1 + end + until quit + +end) +tfx.shutdown() +if not ok then print("Error: "..err) end diff --git a/framework/lualib/3rd/termfx/printtest.lua b/framework/lualib/3rd/termfx/printtest.lua new file mode 100755 index 0000000..cf7511c --- /dev/null +++ b/framework/lualib/3rd/termfx/printtest.lua @@ -0,0 +1,61 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +tfx = require "termfx" + +tfx.init() + +ok, err = pcall(function() +---------- vvv draw here vvv ---------- + +tfx.outputmode(tfx.output.COL256) +tfx.clear(tfx.color.WHITE, tfx.color.BLACK) + +buf = tfx.newbuffer(16, 15) +buf:clear(tfx.color.BLACK, tfx.color.WHITE) + +str = "Hallodradihödel" + +-- fixed length for above string, in the absence of utf8 libs +for w = 15, 1, -1 do + tfx.printat(1, w, str, w) + tfx.printat(tfx.width()-w+1, w, str) + + buf:printat(1, w, str, w) +end + +tbl = { + tfx.newcell('H', 1, 0), + tfx.newcell('a', 2, 0), + tfx.newcell('l', 3, 0), + tfx.newcell('l', 4, 0), + tfx.newcell('o', 5, 0), + tfx.newcell('d', 6, 0), + tfx.newcell('r', 7, 0), + tfx.newcell('a', 8, 0), + tfx.newcell('d', 9, 0), + tfx.newcell('i', 10, 0), + tfx.newcell('h', 11, 0), + tfx.newcell('ö', 12, 0), + tfx.newcell('d', 13, 0), + tfx.newcell('e', 14, 0), + tfx.newcell('l', 15, 0), +} + +for w = 1, #tbl do + tfx.printat(1, #str+w, tbl, w) + tfx.printat(tfx.width()-w+1, #str+w, tbl, w) + + buf:printat(1+w, w, tbl, #tbl - w + 1) +end + +tfx.blit(math.floor((tfx.width() - buf:width()) / 2), math.floor((30 - buf:height()) / 2), buf) + +---------- ^^^ draw here ^^^ ---------- +tfx.present() +tfx.pollevent() +end) + +tfx.shutdown() +if not ok then print("Error: "..err) end diff --git a/framework/lualib/3rd/termfx/screenshot.lua b/framework/lualib/3rd/termfx/screenshot.lua new file mode 100755 index 0000000..819c8d5 --- /dev/null +++ b/framework/lualib/3rd/termfx/screenshot.lua @@ -0,0 +1,88 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +--[[ screenshot.lua + a simple screenshot facility for termfx programs, outputs html in + a string. Cell colors and attributes are preserved. Note, this only + creates what is needed for a dump of the terminal/buffer's contents, + nothing else. Just "
...data...
" + + use: + + screenshot = require "screenshot" + + -- ... draw stuff ... + + html = screenshot() + + -- or to dump the contents of a buffer instead of the terminal: + + html = screenshot(buf) + + -- then write it to a file, surrounded by a html template as necessary. +--]] + +local tfx = require "termfx" + +local function to_html(scr) + local fg, bg + local res = { "
" }
+	for y=1, scr.h do
+		for x=1, scr.w do
+			local cel = scr[y][x]
+			if fg ~= cel.fg or bg ~= cel.bg then
+				if fg then
+					res[#res+1] = ""
+				end
+				local fgcol, fgattr = tfx.colorinfo(cel.fg % 256), math.floor(cel.fg / 256)
+				local bgcol = tfx.colorinfo(cel.bg % 256)
+				local style, weight = "", ""
+				if fgattr % 2 == 1 then
+					weight = "; font-weight: bold"
+				end
+				fgattr = fgattr / 2
+				if fgattr % 2 == 1 then
+					style = "; text-decoration: underline"
+				end
+				fgattr = fgattr / 2
+				if fgattr % 2 == 1 then
+					fgcol, bgcol = bgcol, fgcol
+				end
+
+				res[#res+1] = string.format(''
+				fg = cel.fg
+				bg = cel.bg
+			end
+			res[#res+1] = string.format('%c', cel.ch)
+		end
+		res[#res+1] = "
" + end + res[#res+1] = "
" + return table.concat(res) +end + +local function screenshot(buf) + local w, h, getcell + if buf then + w, h = buf:size() + getcell = function(x, y) return buf:getcell(x, y) end + else + w, h = tfx.size() + getcell = tfx.getcell + end + + local res = {w = w, h = h} + for y=1, h do + res[y] = {} + for x=1, w do + res[y][x] = getcell(x, y) + end + end + + return to_html(res) +end + +return screenshot \ No newline at end of file diff --git a/framework/lualib/3rd/termfx/simpleui.lua b/framework/lualib/3rd/termfx/simpleui.lua new file mode 100755 index 0000000..46b54ce --- /dev/null +++ b/framework/lualib/3rd/termfx/simpleui.lua @@ -0,0 +1,133 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +--[[ + simpleui.lua + + very simple ui elements for termfx samples: + + ui = require "simpleui" + + ui.box(x, y, w, h) + draws a box with a frame + + ui.ask(msg) + prints a message and gives the option to select Yes or No. + Returns true if Yes was selected, false otherwise + + ui.message(msg) + prints a message in a box and returns on ESC, Return or Space. + + ui.select(msg, tbl) + presents a list of up to 9 items with a header, allows to select + an item by number, or ESC, Return or Space to exit without + selecting. Returns the selected number, or false if ESC, Return + or Space was pressed. +--]] +local _M = {} + +local tfx = require "termfx" + +local function draw_box(x, y, w, h) + local ccell = tfx.newcell('+') + local hcell = tfx.newcell('-') + local vcell = tfx.newcell('|') + + for i = x, x+w do + tfx.setcell(i, y-1, hcell) + tfx.setcell(i, y+h, hcell) + end + for i = y, y+h do + tfx.setcell(x-1, i, vcell) + tfx.setcell(x+w, i, vcell) + end + tfx.setcell(x-1, y-1, ccell) + tfx.setcell(x-1, y+h, ccell) + tfx.setcell(x+w, y-1, ccell) + tfx.setcell(x+w, y+h, ccell) + + tfx.rect(x, y, w, h, ' ', fg, bg) +end + +_M.box = draw_box + +local function frame(w, h) + local tw, th = tfx.size() + if w + 2 > tw then w = tw - 2 end + if h + 2 > th then h = th - 2 end + local x = math.floor((tw - w) / 2) + local y = math.floor((th - h) / 2) + + draw_box(x, y, w, h) + + return x, y, w, h +end + +function _M.ask(msg) + local mw = #msg + if mw < 6 then mw = 6 end + local x, y, w, h = frame(mw, 3) + tfx.printat(x, y, msg, w) + local p = x + math.floor((w - 6) / 2) + tfx.attributes(tfx.color.BLACK, tfx.color.GREEN) + tfx.printat(p, y+2, "Yes") + tfx.attributes(tfx.color.BLACK, tfx.color.RED) + tfx.printat(p+4, y+2, "No") + tfx.present() + + local answer = nil + while answer == nil do + local evt = tfx.pollevent() + if evt.char == 'y' or evt.char == 'Y' then + answer = true + elseif evt.char == 'n' or evt.char == 'N' then + answer = false + end + end + return answer +end + +function _M.message(msg) + local mw = #msg + local x, y, w, h = frame(mw, 3) + tfx.printat(x, y, msg, w) + local p = x + math.floor((w - 2) / 2) + tfx.attributes(tfx.color.BLACK, tfx.color.GREEN) + tfx.printat(p, y+2, "Ok") + tfx.present() + + local evt + repeat + evt = tfx.pollevent() + until evt.key == tfx.key.ENTER or evt.key == tfx.key.SPACE or evt.key == tfx.key.ESC +end + +function _M.select(msg, tbl) + local mw = #msg + local mh = #tbl + if mh > 9 then mh = 9 end + for i=1, mh do + if mw < #tbl[i] + 2 then mw = #tbl[i] + 2 end + end + + local x, y, w, h = frame(mw, mh+2) + tfx.printat(x, y, msg, w) + for i=1, mh do + tfx.printat(x, y+1+i, i.." "..tbl[i], w) + end + tfx.present() + + local answer = nil + while answer == nil do + local evt = tfx.pollevent() + if evt.char >= '1' and evt.char <= tostring(mh) then + answer = tbl[tonumber(evt.char)] + elseif evt.key == tfx.key.ENTER or evt.key == tfx.key.SPACE or evt.key == tfx.key.ESC then + answer = false + end + end + return answer +end + +return _M diff --git a/framework/lualib/3rd/termfx/termfxtest.lua b/framework/lualib/3rd/termfx/termfxtest.lua new file mode 100755 index 0000000..50fe4ba --- /dev/null +++ b/framework/lualib/3rd/termfx/termfxtest.lua @@ -0,0 +1,254 @@ +-- sample for termfx +-- Gunnar Zötl , 2014-2015 +-- Released under the terms of the MIT license. See file LICENSE for details. + +package.path = "samples/?.lua;"..package.path + +tfx = require "termfx" +ui = require "simpleui" +screenshot = require "screenshot" + + +tfx.init() +tfx.inputmode(tfx.input.ALT + tfx.input.MOUSE) +tfx.outputmode(tfx.output.COL256) + +rev_keys = {} +for k, v in pairs(tfx.key) do + if rev_keys[v] then + rev_keys[v] = rev_keys[v] .. ','..k + else + rev_keys[v] = k + end +end + +function find_name(tbl, val) + for k, v in pairs(tbl) do + if val == v then return k end + end + return nil +end + +function tbl_keys(tbl) + local res = {} + for k, v in pairs(tbl) do + res[#res+1] = k + end + table.sort(res, function(i, k) return tbl[i] < tbl[k] end) + return res +end + +function pr_event(x, y, evt) + evt = evt or {} + + tfx.attributes(tfx.color.BLUE, tfx.color.WHITE) + tfx.printat(x, y, "Event:") + tfx.printat(x+9, y, evt.type) + + tfx.attributes(tfx.color.WHITE, tfx.color.BLACK) + + if evt and evt.type then + tfx.printat(x, y+1, "elapsed") + tfx.printat(x+8, y+1, evt.elapsed) + end + + if evt.type == "key" then + tfx.printat(x, y+2, "mod") + tfx.printat(x+8, y+2, evt.mod) + tfx.printat(x, y+3, "key") + tfx.printat(x+8, y+3, rev_keys[evt.key] or evt.key) + tfx.printat(x, y+4, "ch") + tfx.printat(x+8, y+4, evt.ch) + tfx.printat(x, y+5, "char") + tfx.printat(x+8, y+5, evt.char) + elseif evt.type == "resize" then + tfx.printat(x, y+2, "w") + tfx.printat(x+8, y+2, evt.w) + tfx.printat(x, y+3, "h") + tfx.printat(x+8, y+3, evt.h) + elseif evt.type == "mouse" then + tfx.printat(x, y+2, "x") + tfx.printat(x+8, y+2, evt.x) + tfx.printat(x, y+3, "y") + tfx.printat(x+8, y+3, evt.y) + tfx.printat(x, y+4, "key") + tfx.printat(x+8, y+4, rev_keys[evt.key] or evt.key) + end +end + +function pr_colors(x, y, w) + tfx.attributes(tfx.color.WHITE, tfx.color.BLACK) + tfx.printat(x, y, "BLACK", w) + tfx.attributes(tfx.color.WHITE, tfx.color.RED) + tfx.printat(x, y+1, "RED", w) + tfx.attributes(tfx.color.WHITE, tfx.color.GREEN) + tfx.printat(x, y+2, "GREEN", w) + tfx.attributes(tfx.color.BLACK, tfx.color.YELLOW) + tfx.printat(x, y+3, "YELLOW", w) + tfx.attributes(tfx.color.WHITE, tfx.color.BLUE) + tfx.printat(x, y+4, "BLUE", w) + tfx.attributes(tfx.color.BLACK, tfx.color.MAGENTA) + tfx.printat(x, y+5, "MAGENTA", w) + tfx.attributes(tfx.color.BLACK, tfx.color.CYAN) + tfx.printat(x, y+6, "CYAN", w) + tfx.attributes(tfx.color.BLACK, tfx.color.WHITE) + tfx.printat(x, y+7, "WHITE", w) +end + +function pr_stats(x, y) + local tw, th = tfx.size() + local im = tfx.inputmode() + local om = tfx.outputmode() + + tfx.attributes(tfx.color.WHITE, tfx.color.BLACK) + tfx.printat(x, y, "Size:") + tfx.printat(x+8, y, tw .. " x " .. th) + tfx.printat(x, y+1, "Input: ") + tfx.printat(x+8, y+1, find_name(tfx.input, im)) + tfx.printat(x, y+2, "Output: ") + tfx.printat(x+8, y+2, find_name(tfx.output, om)) +end + +function pr_coltbl(x, y) + local i = 0 + local om = tfx.outputmode() + + if om == tfx.output.NORMAL or om == tfx.output.COL256 then + for j=i, i+7 do + tfx.attributes(tfx.color.WHITE, j) + tfx.printat(x, y+j, string.format("%02X", j), 2) + tfx.attributes(tfx.color.WHITE, j+8) + tfx.printat(x+3, y+j, string.format("%02X", j+8), 2) + end + i = 16 + x = x+6 + end + + if om == tfx.output.COL216 or om == tfx.output.COL256 then + for j=0, 11 do + for k=0, 15 do + local col = k*12+j+i + tfx.attributes(tfx.color.WHITE, col) + tfx.printat(x+k*3, y+j, string.format("%02X", col), 2) + end + end + x = x+48 + i=i+216 + end + + if om == tfx.output.GRAYSCALE or om == tfx.output.COL256 then + for j=0, 11 do + for k=0, 1 do + local col = k*12+j+i + tfx.attributes(tfx.color.WHITE, col) + tfx.printat(x+k*3, y+j, string.format("%02X", col), 2) + end + end + end +end + +function pr_formats(x, y) + local fg, bg = tfx.attributes() + + tfx.printat(x, y, "Normal") + x = x + 7 + tfx.attributes(fg + tfx.format.BOLD, bg) + tfx.printat(x, y, "Bold") + x = x + 5 + tfx.attributes(fg + tfx.format.UNDERLINE, bg) + tfx.printat(x, y, "Under") + x = x + 6 + tfx.attributes(fg + tfx.format.REVERSE, bg) + tfx.printat(x, y, "Reverse") + + tfx.attributes(fg, bg) +end + +function blit_a_bit(x, y, w, h) + tfx.attributes(tfx.color.WHITE, tfx.color.BLACK) + ui.box(x, y, w, h) + + local buf = tfx.newbuffer(8, 6) + buf:clear(tfx.color.WHITE, tfx.color.BLACK) + + local cell = tfx.newcell('#', tfx.color.YELLOW, tfx.color.GREEN) + local eye = tfx.newcell('O', tfx.color.BLACK, tfx.color.GREEN) + local mouth = tfx.newcell('X', tfx.color.BLACK, tfx.color.GREEN) + + for i=3, 6 do + buf:setcell(i, 1, cell) + buf:setcell(i, 6, cell) + end + buf:rect(1, 2, 8, 4, cell) + + buf:setcell(3, 3, eye) + buf:setcell(6, 3, eye) + buf:setcell(2, 4, mouth) + buf:setcell(7, 4, mouth) + buf:printat(3, 5, { mouth, mouth, mouth, mouth}) + + tfx.blit(x, y, buf) + tfx.blit(x+w-8, y+6, buf) + tfx.blit(x+w-8, y+h-6, buf) + tfx.blit(x, y+h-12, buf) +end + +function select_inputmode() + local which = ui.select("select input mode", tbl_keys(tfx.input)) + if which then tfx.inputmode(tfx.input[which]) end +end + +function select_outputmode() + local which = ui.select("select output mode", tbl_keys(tfx.output)) + if which then tfx.outputmode(tfx.output[which]) end +end + +ok, err = pcall(function() + + local quit = false + local evt + repeat + + tfx.clear(tfx.color.WHITE, tfx.color.BLACK) + tfx.printat(1, tfx.height(), "press I for input mode, O for output mode, S for screenshot, Q to quit") + + tfx.printat(1, 1, _VERSION) + pr_event(1, 3, evt) + pr_stats(25, 1) + pr_formats(1, 10) + pr_colors(50, 1, 10) + pr_coltbl(1, 12) + blit_a_bit(62, 2, 18, 21) + + tfx.present() + evt = tfx.pollevent() + + tfx.attributes(tfx.color.WHITE, tfx.color.BLUE) + if evt.char == "q" or evt.char == "Q" then + quit = ui.ask("Really quit?") + evt = {} + elseif evt.char == "i" or evt.char == "I" then + select_inputmode() + evt = {} + elseif evt.char == "o" or evt.char == "O" then + select_outputmode() + evt = {} + elseif evt.char == "s" or evt.char == "S" then + local f = io.open("screenshot.html", "w") + if f then + f:write("") + f:write(screenshot()) + f:write("") + f:close() + ui.message("Screenshot saved to screenshot.html") + else + ui.message("Could not save screenshot.") + end + evt = {} + end + + until quit + +end) +tfx.shutdown() +if not ok then print("Error: "..err) end diff --git a/framework/lualib/3rd/zeus/ecs.lua b/framework/lualib/3rd/zeus/ecs.lua new file mode 100755 index 0000000..3df92f4 --- /dev/null +++ b/framework/lualib/3rd/zeus/ecs.lua @@ -0,0 +1,169 @@ +-- https://github.com/cloudwu/luaecs + +local ecs = require "ecs.core" + +local function cache_world(obj, k) + local c = { + typenames = {}, + id = 0, + each = {}, + } + + local function cache_each(each, key) + local tc = assert(c.typenames[key]) + local type_desc = { + id = tc.id, + } + for i, _ in ipairs(tc) do + type_desc[i] = tc[i] + end + each[key] = k:_simpleiter(type_desc) + return each[key] + end + + setmetatable(c.each, { + __mode = "kv", + __index = cache_each, + }) + obj[k] = c + return c +end + +local context = setmetatable({}, { + __index = cache_world, +}) +local typeid = { + int = assert(ecs._TYPEINT), + float = assert(ecs._TYPEFLOAT), + bool = assert(ecs._TYPEBOOL), +} +local typesize = { + [typeid.int] = 4, + [typeid.float] = 4, + [typeid.bool] = 1, +} + +local typepack = { + [typeid.int] = 'i4', + [typeid.float] = 'f', + [typeid.bool] = 'B', +} + +local M = ecs._METHODS + +do -- newtype + local function parse(s) + -- s is "name:typename" + local name, typename = s:match "([%w_]+):(%l+)" + local tid = assert(typeid[typename]) + return {tid, name} + end + + local function align(c, field) + local t = field[1] + local size = c.size + if t == typeid.int or t == typeid.float then + local offset = ((size + 3) & ~3) + c.size = offset + 4 + field[3] = offset + elseif t == typeid.bool then + c.size = size + 1 + field[3] = size + else + error("Invalid type " .. t) + end + return field + end + + local function align_struct(c, t) + if t == typeid.int or t == typeid.float then + c.size = ((c.size + 3) & ~3) + end + end + + function M:register(typeclass) + local name = assert(typeclass.name) + local ctx = context[self] + local typenames = ctx.typenames + local id = ctx.id + 1 + assert(typenames[name] == nil and id <= ecs._MAXTYPE) + ctx.id = id + local c = { + id = id, + name = name, + size = 0, + } + for i, v in ipairs(typeclass) do + c[i] = align(c, parse(v)) + end + if c.size > 0 then + align_struct(c, typeclass[1][1]) + local pack = "!4=" + for i = 1, #c do + pack = pack .. typepack[c[i][1]] + end + c.pack = pack + else + -- size == 0, one value + if typeclass.type then + local t = assert(typeid[typeclass.type]) + c.type = t + c.size = typesize[t] + c.pack = typepack[t] + else + c.tag = true + end + end + typenames[name] = c + self:_newtype(id, c.size) + end +end + +local mapbool = { + [true] = 1, + [false] = 0, +} + +function M:new(obj) + local eid = self:_newentity() + local typenames = context[self].typenames + for k, v in pairs(obj) do + local tc = typenames[k] + if not tc then + error("Invalid key : " .. k) + end + if tc.tag then + self:_addcomponent(eid, tc.id) + elseif tc.type then + self:_addcomponent(eid, tc.id, string.pack(tc.pack, mapbool[v] or v)) + else + local tmp = {} + for i, f in ipairs(tc) do + tmp[i] = v[f[2]] + end + self:_addcomponent(eid, tc.id, string.pack(tc.pack, table.unpack(tmp))) + end + end +end + +function M:context(t) + local typenames = context[self].typenames + local id = {} + for i, name in ipairs(t) do + local tc = typenames[name] + if not tc then + error("Invalid component name " .. name) + end + id[i] = tc.id + end + return self:_context(id) +end + +function M:each(name) + local ctx = context[self] + local typenames = ctx.typenames + local c = assert(typenames[name]) + return ctx.each[name]() +end + +return ecs diff --git a/framework/service/ws_agent.lua b/framework/service/ws_agent.lua deleted file mode 100755 index 472584f..0000000 --- a/framework/service/ws_agent.lua +++ /dev/null @@ -1,45 +0,0 @@ -local skynet = require "skynet" -local socket = require "skynet.socket" - -local WATCHDOG -local host -local send_request - -local CMD = {} -local client_fd -local gate - -skynet.register_protocol { - name = "client", - id = skynet.PTYPE_CLIENT, - unpack = skynet.tostring, - dispatch = function(fd, address, msg) - assert(fd == client_fd) -- You can use fd to reply message - skynet.ignoreret() -- session is fd, don't call skynet.ret - -- skynet.trace() - -- echo simple - skynet.send(gate, "lua", "response", fd, msg) - skynet.error(address, msg) - end, -} - -function CMD.start(conf) - local fd = conf.client - gate = conf.gate - WATCHDOG = conf.watchdog - client_fd = fd - skynet.call(gate, "lua", "forward", fd) -end - -function CMD.disconnect() - -- todo: do something before exit - skynet.exit() -end - -skynet.start(function() - skynet.dispatch("lua", function(_, _, command, ...) - -- skynet.trace() - local f = CMD[command] - skynet.ret(skynet.pack(f(...))) - end) -end) diff --git a/framework/service/ws_gate.lua b/framework/service/ws_gate.lua deleted file mode 100755 index df1b090..0000000 --- a/framework/service/ws_gate.lua +++ /dev/null @@ -1,230 +0,0 @@ -local skynet = require "skynet" -require "skynet.manager" -local socket = require "skynet.socket" -local websocket = require "http.websocket" -local socketdriver = require "skynet.socketdriver" - -local watchdog -local connection = {} -- fd -> connection : { fd , client, agent , ip, mode } -local forwarding = {} -- agent -> connection - -local client_number = 0 -local maxclient -- max client -local nodelay -local protocol - -local master_name, slave_name = ... -master_name = master_name or ".ws_gate" - -skynet.register_protocol { - name = "client", - id = skynet.PTYPE_CLIENT, -} - -local function launch_master() - local CMD_MASTER = {} - local slave = {} - - function CMD_MASTER.open(source, conf) - local instance = conf.instance or 8 - assert(instance > 0) - local balance = 1 - - for i = 1, instance do - local _slave_name = string.format("%s-slave-%d", master_name, i) - table.insert(slave, skynet.newservice(SERVICE_NAME, master_name, _slave_name)) - end - - conf.watchdog = conf.watchdog or source - for i = 1, instance do - local s = slave[i] - skynet.call(s, "lua", "open", conf) - end - - local address = conf.address or "0.0.0.0" - local port = assert(conf.port) - protocol = conf.protocol or "ws" - - local fd = socket.listen(address, port) - skynet.error(string.format("Listen websocket port:%s protocol:%s", port, protocol)) - socket.start(fd, function(fd, addr) - skynet.error(string.format("accept client socket_fd: %s addr:%s", fd, addr)) - - local s = slave[balance] - balance = balance + 1 - if balance > #slave then - balance = 1 - end - local ok, err = skynet.call(s, "lua", "accept", fd, addr) - if not ok then - skynet.error(string.format("invalid client (fd = %d) error = %s", fd, err)) - end - end) - end - - skynet.dispatch("lua", function(session, source, cmd, ...) - local f = CMD_MASTER[cmd] - if not f then - skynet.error("ws gate master can't dispatch cmd " .. (cmd or nil)) - skynet.ret(skynet.pack({ - ok = false, - })) - return - end - if session == 0 then - f(source, ...) - else - skynet.ret(skynet.pack(f(source, ...))) - end - end) -end - -local function launch_slave() - - local function unforward(c) - if c.agent then - forwarding[c.agent] = nil - c.agent = nil - c.client = nil - end - end - - local function close_fd(fd) - local c = connection[fd] - if c then - unforward(c) - connection[fd] = nil - client_number = client_number - 1 - end - end - - local handler = {} - - function handler.connect(fd) - skynet.error("ws connect from: " .. tostring(fd)) - if client_number >= maxclient then - socketdriver.close(fd) - return - end - if nodelay then - socketdriver.nodelay(fd) - end - - client_number = client_number + 1 - local addr = websocket.addrinfo(fd) - local c = { - fd = fd, - ip = addr, - } - connection[fd] = c - - skynet.send(watchdog, "lua", "socket", "open", fd, addr, skynet.self()) - end - - function handler.handshake(fd, header, url) - local addr = websocket.addrinfo(fd) - skynet.error("ws handshake from: " .. tostring(fd), "url", url, "addr:", addr) - skynet.error("----header-----") - for k, v in pairs(header) do - skynet.error(k, v) - end - skynet.error("--------------") - end - - function handler.message(fd, msg) - skynet.error("ws ping from: " .. tostring(fd), msg .. "\n") - -- recv a package, forward it - local c = connection[fd] - local agent = c and c.agent - -- msg is string - if agent then - skynet.redirect(agent, c.client, "client", fd, msg) - else - skynet.send(watchdog, "lua", "socket", "data", fd, msg) - end - end - - function handler.ping(fd) - skynet.error("ws ping from: " .. tostring(fd) .. "\n") - end - - function handler.pong(fd) - skynet.error("ws pong from: " .. tostring(fd)) - end - - function handler.close(fd, code, reason) - skynet.error("ws close from: " .. tostring(fd), code, reason) - close_fd(fd) - skynet.send(watchdog, "lua", "socket", "close", fd) - end - - function handler.error(fd) - skynet.error("ws error from: " .. tostring(fd)) - close_fd(fd) - skynet.send(watchdog, "lua", "socket", "error", fd, msg) - end - - function handler.warning(fd, size) - skynet.send(watchdog, "lua", "socket", "warning", fd, size) - end - - local CMD_SLAVE = {} - function CMD_SLAVE.forward(source, fd, client, address) - local c = assert(connection[fd]) - unforward(c) - c.client = client or 0 - c.agent = address or source - forwarding[c.agent] = c - end - - function CMD_SLAVE.response(source, fd, msg) - skynet.error("ws response: " .. tostring(fd), msg .. "\n") - -- forward msg - websocket.write(fd, msg) - end - - function CMD_SLAVE.kick(source, fd) - websocket.close(fd) - end - - function CMD_SLAVE.accept(source, fd, addr) - return websocket.accept(fd, handler, protocol, addr) - end - - function CMD_SLAVE.open(source, conf) - maxclient = conf.maxclient or 1024 - nodelay = conf.nodelay - protocol = conf.protocol or "ws" - watchdog = conf.watchdog - skynet.error("open", watchdog, source) - end - - skynet.dispatch("lua", function(session, source, cmd, ...) - local f = CMD_SLAVE[cmd] - if not f then - skynet.error("ws gate slave can't dispatch cmd " .. (cmd or nil)) - skynet.ret(skynet.pack({ - ok = false, - })) - return - end - if session == 0 then - f(source, ...) - else - skynet.ret(skynet.pack(f(source, ...))) - end - end) -end - -skynet.start(function() - if slave_name then - skynet.error("start ws_gate slave:", slave_name) - skynet.register(slave_name) - launch_slave() - else - skynet.error("start ws_gate master:", master_name) - skynet.register(master_name) - launch_master() - end -end) - diff --git a/framework/service/ws_watchdog.lua b/framework/service/ws_watchdog.lua deleted file mode 100755 index 1fada2b..0000000 --- a/framework/service/ws_watchdog.lua +++ /dev/null @@ -1,79 +0,0 @@ -local skynet = require "skynet" - -local CMD = {} -local SOCKET = {} -local master_gate -local agent = {} -local protocol -local fd2gate = {} - -function SOCKET.open(fd, addr, gate) - skynet.error("New client from : " .. addr) - fd2gate[fd] = gate - agent[fd] = skynet.newservice("ws_agent") - skynet.call(agent[fd], "lua", "start", { - gate = gate, - client = fd, - watchdog = skynet.self(), - protocol = protocol, - addr = addr, - }) -end - -local function close_agent(fd) - local a = agent[fd] - agent[fd] = nil - if a then - local gate = fd2gate[fd] - if gate then - skynet.call(gate, "lua", "kick", fd) - fd2gate[fd] = nil - end - -- disconnect never return - skynet.send(a, "lua", "disconnect") - end -end - -function SOCKET.close(fd) - print("socket close",fd) - close_agent(fd) -end - -function SOCKET.error(fd, msg) - print("socket error",fd, msg) - close_agent(fd) -end - -function SOCKET.warning(fd, size) - -- size K bytes havn't send out in fd - print("socket warning", fd, size) -end - -function SOCKET.data(fd, msg) - print("socket data", fd, msg) -end - -function CMD.start(conf) - protocol = conf.protocol - skynet.call(master_gate, "lua", "open" , conf) -end - -function CMD.close(fd) - close_agent(fd) -end - -skynet.start(function() - skynet.dispatch("lua", function(session, source, cmd, subcmd, ...) - if cmd == "socket" then - local f = SOCKET[subcmd] - f(...) - -- socket api don't need return - else - local f = assert(CMD[cmd]) - skynet.ret(skynet.pack(f(subcmd, ...))) - end - end) - - master_gate = skynet.newservice("ws_gate") -end) - diff --git a/framework/simulation/buffer_queue.lua b/framework/simulation/buffer_queue.lua new file mode 100644 index 0000000..8b57806 --- /dev/null +++ b/framework/simulation/buffer_queue.lua @@ -0,0 +1,256 @@ +local mt = {} + +local function _new_block(v) + local block = { + value = v, + next = false, + prev = false, + } + return block +end + +local endian_fmt = { + ["little"] = "<", + ["big"] = ">", +} +local function pack_data(data, header_len, endian) + local len = #data + local fmt = endian_fmt[endian] .. "I" .. header_len .. "c" .. (len) + return string.pack(fmt, len, data) +end + +local function insert_free_list(self, block) + block.prev = false + block.next = false + local count = self.v_free_list_count + count = count + 1 + self.v_free_list_count = count + self.v_free_list[count] = block +end + +local function get_block(self) + local count = self.v_free_list_count + if count > 0 then + local block = self.v_free_list[count] + self.v_free_list[count] = nil + self.v_free_list_count = count - 1 + return block + else + return _new_block() + end +end + +local function free_block(self, block) + local next = block.next + local prev = block.prev + + if next then + next.prev = prev + end + + if prev then + prev.next = next + end + insert_free_list(self, block) +end + +local function init_buffer(self) + local DEF_FREE_BLOCK = 3 + + for _ = 1, DEF_FREE_BLOCK do + insert_free_list(self, _new_block()) + end +end + +local function create() + local raw = { + v_free_list = {}, + v_free_list_count = 0, + + v_block_head = false, + v_block_tail = false, + v_size = 0, + } + + init_buffer(raw) + setmetatable(raw, { + __index = mt, + }) + return raw +end + +function mt:push(data) + local block = get_block(self) + block.value = data + local size = self.v_size + self.v_size = size + #data + + if not self.v_block_head then + assert(self.v_block_tail == false) + self.v_block_head = block + end + + local tail = self.v_block_tail + if tail then + tail.next = block + block.prev = tail + block.next = false + end + + self.v_block_tail = block +end + +local buff = {} +function mt:look(nbytes) + local head = self.v_block_head + local size = self.v_size + if head and nbytes > 0 and size >= nbytes then + local count = 0 + local index = 0 + while head and count ~= nbytes do + local value = head.value + local len = #(value) + if count + len > nbytes then + local sub = nbytes - count + value = string.sub(value, 1, sub) + count = count + sub + else + count = count + len + end + index = index + 1 + buff[index] = value + head = head.next + end + return table.concat(buff, "", 1, index) + end + return false +end + +function mt:pop(nbytes) + local head = self.v_block_head + nbytes = nbytes or (head and #(head.value)) + if head and nbytes > 0 and self.v_size >= nbytes then + local count = 0 + local index = 0 + while head and count ~= nbytes do + local len = #(head.value) + index = index + 1 + if count + len > nbytes then + local sub = nbytes - count + local value = head.value + local sub_value = string.sub(value, 1, sub) + buff[index] = sub_value + count = count + sub + head.value = string.sub(value, sub + 1) + else + count = count + len + buff[index] = head.value + local next = head.next + free_block(self, head) + head = next + end + end + + self.v_block_head = head + self.v_size = self.v_size - count + if not head then + self.v_block_tail = false + end + local ret = table.concat(buff, "", 1, index) + assert(#ret == nbytes) + return ret + end + return false +end + +function mt:pop_all(out) + local v = self:pop() + local count = 0 + + while v do + count = count + 1 + out[count] = v + v = self:pop() + end + return count +end + +function mt:push_block(data, header_len, endian) + data = pack_data(data, header_len, endian) + self:push(data) +end + +function mt:pop_block(header_len, endian) + if self.v_size > header_len then + local header = self:look(header_len) + local fmt = endian_fmt[endian] .. "I" .. header_len + -- local fmt = endian_fmt[endian].."H" + local len = string.unpack(fmt, header) + if self.v_size >= len + header_len then + self:pop(header_len) + return self:pop(len) + end + end + return false +end + +function mt:pop_all_block(out, header_len, endian) + local v = self:pop_block(header_len, endian) + local count = 0 + + while v do + count = count + 1 + out[count] = v + v = self:pop_block(header_len, endian) + end + return count +end + +function mt:clear() + local head = self.v_block_head + while head do + insert_free_list(self, head) + head = head.next + end + self.v_block_head = false + self.v_block_tail = false + self.v_size = 0 +end + +function mt:get_head_data() + local head = self.v_block_head + return head and head.value or false +end + +---- for test +local function _dump_list(list) + while list do + print(" node = ", list) + for k, v in pairs(list) do + print(" ", k, v) + end + print("----------") + list = list.next + end +end + +function mt:dump() + print("==== meta info ====") + for k, v in pairs(self) do + print(k, v) + end + + print("===== free list ====", self.v_free_list_count) + local free_list = self.v_free_list + for k, v in pairs(free_list) do + print(k, v) + end + + print("===== head list ====") + _dump_list(self.v_block_head) +end + +return { + create = create, + pack_data = pack_data, +} diff --git a/framework/simulation/conn.lua b/framework/simulation/conn.lua new file mode 100644 index 0000000..1e45ef0 --- /dev/null +++ b/framework/simulation/conn.lua @@ -0,0 +1,256 @@ +local buffer_queue = require "buffer_queue" +local socket = require "socket" + +local DEF_MSG_HEADER_LEN = 2 +local DEF_MSG_ENDIAN = "big" + +local mt = {} + +local function resolve(host) + local addr_tbl, err = socket.dns.getaddrinfo(host) + if not addr_tbl then + return false, err .. "[" .. tostring(host) .. "]" + end + return assert(addr_tbl[1]) +end + +local function connect(addr, port) + -- just support tcp now + if not (addr.family == "inet" or addr.family == "inet6") then + return nil, "just support tcp now" + end + local c, e = socket.tcp() + if not c then + return nil, e + end + local fd, e = c:connect(addr.addr, port) + if not fd then + c:close() + return nil, e + else + c:settimeout(0) -- non blocking + local raw = { + v_send_buf = buffer_queue.create(), + v_recv_buf = buffer_queue.create(), + v_fd = c, + + o_host_addr = addr, + o_port = port, + v_check_connect = true, + } + return setmetatable(raw, { + __index = mt, + }) + end +end + +local function connect_host(host, port) + local addr, err = resolve(host) + if not addr then + return false, err + end + return connect(addr, port) +end + +local function _flush_send(self) + local send_buf = self.v_send_buf + local v = send_buf:get_head_data() + local fd = self.v_fd + local count = 0 + + while v do + local len = #v + local n, err, last_sent = fd:send(v) + if not n then + if last_sent and last_sent > 0 then + send_buf:pop(last_sent) + end + return false, err + else + count = count + n + send_buf:pop(n) + if n < len then + break + end + end + v = send_buf:get_head_data() + end + return count +end + +local function _flush_recv(self) + local recv_buf = self.v_recv_buf + local fd = self.v_fd + local data, err, piece_data = fd:receive("*a") + if not data then + if piece_data and #piece_data > 0 then + recv_buf:push(piece_data) + end + if err == "timeout" then + return true + end + return false, err + else + local count = 0 + local len = #data + count = count + len + recv_buf:push(data) + return count + end +end + +local function _check_connect(self) + local fd = self.v_fd + if not fd then + return false, 'fd is nil' + end + + if self.v_check_connect then + local _, wfd, err = socket.select(nil, {fd}, 0) + if wfd and wfd[fd] then + self.v_check_connect = false + return true + else + if err == "timeout" then + return false, "connecting" + end + return false, err + end + else + return true + end +end + +-- ====================== 供sconn 调用 ====================== +function mt:new_connect(addr, port) + -- just support tcp now + if not (addr.family == "inet" or addr.family == "inet6") then + return nil, "just support tcp now" + end + local c, e = socket.tcp() + if not c then + return nil, e + end + local fd, e = c:connect(addr.addr, port) + if not fd then + c:close() + return false, e + else + c:settimeout(0) -- non blocking + self.v_fd:close() + self.v_send_buf:clear() + self.v_recv_buf:clear() + self.v_fd = c + self.o_host_addr = addr + self.o_port = port + self.v_check_connect = true + return true + end +end + +function mt:pop_msg(header_len, endian) + local recv_buf = self.v_recv_buf + header_len = header_len or DEF_MSG_HEADER_LEN + endian = endian or DEF_MSG_ENDIAN + + return recv_buf:pop_block(header_len, endian) +end + +function mt:send(data) + self.v_send_buf:push(data) +end + +function mt:recv(out) + local recv_buf = self.v_recv_buf + return recv_buf:pop_all(out) +end +-- ====================== 供sconn 调用 ====================== + +function mt:send_msg(data, header_len, endian) + local send_buf = self.v_send_buf + header_len = header_len or DEF_MSG_HEADER_LEN + endian = endian or DEF_MSG_ENDIAN + + send_buf:push_block(data, header_len, endian) +end + +function mt:recv_msg(out_msg, header_len, endian) + local recv_buf = self.v_recv_buf + header_len = header_len or DEF_MSG_HEADER_LEN + endian = endian or DEF_MSG_ENDIAN + + return recv_buf:pop_all_block(out_msg, header_len, endian) +end + +--[[ +update 接口现在会返回三个参数 success, err, status + +success: boolean类型 表示当前status是否正常 + true: err返回值为nil + false: err返回值为string,描述错误信息 + +err: string类型 表示当前status的错误信息,在success 为false才会有效 + +status: string类型 当前conn所在的状态,状态只能是: + "connect": 连接状态 + "forward": 连接成功状态 + "recv": 接受状态数据状态 + "send": 发送数据状态 + "close": 关闭状态 +]] + +function mt:update() + local fd = self.v_fd + if not fd then + return false, "fd is nil", "close" + end + + local success, err = _check_connect(self) + if not success then + if err == "connecting" then + return true, nil, "connect" + else + return false, err, "connect" + end + end + + success, err = _flush_send(self) + if not success then + return false, err, "send" + end + + success, err = _flush_recv(self) + if not success then + if err == "connect_break" then + return false, "connect break", "connect_break" + else + return false, err, "recv" + end + end + + return true, nil, "forward" +end + +local function flush_send(self) + local count = false + repeat + count = _flush_send(self) + until not count or count == 0 +end + +-- function mt:getsockname() +-- return self.v_fd:getsockname() +-- end + +function mt:close() + if self.v_fd then + flush_send(self) + self.v_fd:close() + end + self.v_fd = nil + self.v_check_connect = true +end + +return { + connect_host = connect_host, +} diff --git a/framework/simulation/network.lua b/framework/simulation/network.lua new file mode 100644 index 0000000..e01c2ce --- /dev/null +++ b/framework/simulation/network.lua @@ -0,0 +1,154 @@ +local sproto = require "sproto.sproto" +local conn = require "conn" +local sconn = require "sconn" + +local mt = {} + +local function new(client_pbin, server_pbin) + local client_proto = sproto.new(client_pbin) + local server_proto = sproto.new(server_pbin) + + local client = client_proto:host "package" + -- local server = server_proto:host "package" + + local client_request = client:attach(server_proto) + + local raw = { + v_session_index = 1, + v_request_session = {}, + v_response_handle = {}, + v_out = {}, + v_conn = false, + + v_client = client, + v_client_request = client_request, + } + + return setmetatable(raw, { + __index = mt, + }) +end + +function mt:connect(host, port, stable) + self.v_request_session = {} + local conn_module = stable and sconn or conn + local obj, err = conn_module.connect_host(host, port) + if not obj then + return false, err + else + self.v_conn = obj + return true + end +end + +function mt:check_connected() + return self.v_conn +end + +local function dispatch(self, resp) + local client = self.v_client + local _type, v1, v2, v3 = client:dispatch(resp) + if _type == "RESPONSE" then + local session, response = v1, v2 + local session_item = self.v_request_session[session] + local handle = session_item.handle + local tt = type(handle) + if tt == "function" then + handle(response) + elseif tt == "thread" then + local success, err = coroutine.resume(handle, response) + if not success then + error(err) + end + else + error("error handle type:" .. tt .. " from msg:" .. tostring(session_item.name)) + end + self.v_request_session[session] = nil + + elseif _type == "REQUEST" then + local name, request, response = v1, v2, v3 + local handle = self.v_response_handle[name] + local data = handle(request) + if response then + data = response(data) + self.v_conn:send_msg(data) + end + else + error("error dispatch type: " .. tostring(_type)) + end +end + +function mt:update() + local success, err, status = self.v_conn:update() + if success then + local out = self.v_out + local count = self.v_conn:recv_msg(out) + for i = 1, count do + local resp = out[i] + local ok, oerr = xpcall(dispatch, debug.traceback, self, resp) + if not ok then + error("error dispatch: " .. oerr) + end + end + end + + return success, err, status +end + +local function request(self, name, t, session_index) + local req = self.v_client_request(name, t, session_index) + return self.v_conn:send_msg(req) +end + +function mt:call(name, t, cb) + local session_index = self.v_session_index + self.v_session_index = session_index + 1 + + assert(self.v_request_session[session_index] == nil, session_index) + local session_item = { + name = name, + handle = false, + } + self.v_request_session[session_index] = session_item + + if cb then + session_item.handle = cb + request(self, name, t, session_index) + elseif coroutine.isyieldable() then + session_item.handle = coroutine.running() + request(self, name, t, session_index) + return coroutine.yield() + else + assert(cb) + end +end + +function mt:invoke(name, t) + return request(self, name, t) +end + +function mt:register(name, cb) + assert(cb) + assert(self.v_response_handle[name] == nil) + self.v_response_handle[name] = cb +end + +function mt:close() + if self.v_conn then + self.v_conn:close() + end + self.v_conn = false +end + +function mt:reconnect(cb) + local conn_module = self.v_conn + if conn_module.reconnect then + local ok, err = conn_module:reconnect(function(success) + cb(success) -- success 表示 是否重连成功 + print("end reconnect", success) + end) + print("begin reconnect", ok, err) + end +end + +return new diff --git a/framework/simulation/sconn.lua b/framework/simulation/sconn.lua new file mode 100644 index 0000000..afb3738 --- /dev/null +++ b/framework/simulation/sconn.lua @@ -0,0 +1,485 @@ +local buffer_queue = require "buffer_queue" +local conn = require "conn" +local crypt = require "crypt" +local rc4 = require "rc4.c" + +local pack_data = buffer_queue.pack_data + +local DEF_MSG_HEADER_LEN = 2 +local DEF_MSG_ENDIAN = "big" +local CACHE_MAX_COUNT = 100 + +local mt = {} +local cache_mt = {} + +-------------- cache ------------------ +local function cache_create() + local raw = { + size = 0, + + top = 0, + cache = {}, + } + return setmetatable(raw, { + __index = cache_mt, + }) +end + +function cache_mt:insert(data) + local cache = self.cache + self.top = self.top + 1 + cache[self.top] = data + self.size = self.size + #data + local remove_cache_value = cache[self.top - CACHE_MAX_COUNT] + cache[self.top - CACHE_MAX_COUNT] = nil -- 只缓存最近CACHE_MAX_COUNT个包 + if remove_cache_value then + self.size = self.size - #remove_cache_value + end +end + +function cache_mt:get(nbytes) + if self.size < nbytes then + return false + end + + local cache = self.cache + local i = self.top + local count = 0 + local ret = {} + while count < nbytes do + local v = cache[i] + local len = #v + local n = len + local vv = v + if count + len > nbytes then + local sub_n = nbytes - count + local pos = len - sub_n + local sub_v = string.sub(v, pos + 1) + n = sub_n + vv = sub_v + end + table.insert(ret, 1, vv) + count = count + n + i = i - 1 + end + + return table.concat(ret) +end + +function cache_mt:clear() + self.size = 0 + self.top = 0 +end + +local function dummy(...) + print("send dummy", ...) +end + +local function dispose_error(state_self, _success, _err, _status) + local success = false + local err = state_self.name + local status = "reconnect_error" + return success, err, status +end + +-------------- state ------------------ + +local state = { + newconnect = { + name = "newconnect", + request = false, + dispatch = false, + send = false, + dispose = false, + }, + + reconnect = { + name = "reconnect", + request = false, + dispatch = false, + send = false, + dispose = false, + }, + + forward = { + name = "forward", + request = false, + dispatch = false, + send = false, + dispose = false, + }, + + reconnect_error = { + name = "reconnect_error", + send = dummy, + dispose = dispose_error, + }, + + reconnect_match_error = { + name = "reconnect_match_error", + send = dummy, + dispose = dispose_error, + }, + + reconnect_cache_error = { + name = "reconnect_cache_error", + send = dummy, + dispose = dispose_error, + }, + + close = { + name = "close", + send = dummy, + dispose = false, + }, +} + +local function switch_state(self, s, ...) + local v = state[s] + assert(v) + print(">>>>>>>>>>>>>switch_state:", s, ...) + self.v_state = v + if v.request then + v.request(self, ...) + end +end + +local out = {} + +-------------- new connect state ------------------ +function state.newconnect.request(self, target_server, flag) + -- 0\n + -- base64(DH_key)\n + -- targetServer\n + -- flag + + target_server = target_server or "" + flag = flag or 0 + local clientkey = crypt.randomkey() + local data = string.format("0\n%s\n%s\n%d", crypt.base64encode(crypt.dhexchange(clientkey)), target_server, flag) + + data = pack_data(data, 2, "big") + self.v_sock:send(data) + self.v_clientkey = clientkey + print("request:", data) + self.v_send_buf_top = 0 +end + +function state.newconnect.send(self, data) + self.v_send_buf_top = self.v_send_buf_top + 1 + self.v_send_buf[self.v_send_buf_top] = data +end + +local function send(self, data) + local _send = self.v_state.send + _send(self, data) + return true +end + +function state.newconnect.dispatch(self) + local data = self.v_sock:pop_msg(2, "big") + + if not data then + return + end + + print("dispatch:", data) + local id, key = data:match("([^\n]*)\n([^\n]*)") + + self.v_id = tonumber(id) + key = crypt.base64decode(key) + + local secret = crypt.dhsecret(key, self.v_clientkey) + + local rc4_key = crypt.hmac64_md5(secret, "\0\0\0\0\0\0\0\0") .. crypt.hmac64_md5(secret, "\1\0\0\0\0\0\0\0") .. + crypt.hmac64_md5(secret, "\2\0\0\0\0\0\0\0") .. crypt.hmac64_md5(secret, "\3\0\0\0\0\0\0\0") + + self.v_secret = secret + self.v_rc4_c2s = rc4.rc4(rc4_key) + self.v_rc4_s2c = rc4.rc4(rc4_key) + + switch_state(self, "forward") + + -- 发送在新连接建立中间缓存的数据包 + for i = 1, self.v_send_buf_top do + send(self, self.v_send_buf[i]) + end + self.v_send_buf_top = 0 + self.v_send_buf = {} +end + +function state.newconnect.dispose(state_self, success, err, status) + if success then + return true, nil, "connect" + else + err = string.format("sock_error:%s sock_status:%s sconn_state:newconnect", err, status) + return false, err, "connect" + end +end + +-------------- reconnect state ------------------ +function state.reconnect.request(self) + -- id\n + -- index\n + -- recvnumber\n + -- base64(HMAC_CODE)\n + + self.v_reconnect_index = self.v_reconnect_index + 1 + + local content = string.format("%d\n%d\n%d\n", self.v_id, self.v_reconnect_index, self.v_recvnumber) + + local hmac = crypt.base64encode(crypt.hmac64_md5(crypt.hashkey(content), self.v_secret)) + local data = string.format("%s%s\n", content, hmac) + data = pack_data(data, 2, "big") + + print("request:", data) + + self.v_sock:send(data) +end + +-- 在断线重连期间,仅仅是把数据插入到cache中 +function state.reconnect.send(self, data) + local rc4_c2s = self.v_rc4_c2s + local cache = self.v_cache + data = rc4_c2s:crypt(data) + + self.v_sendnumber = self.v_sendnumber + #data + cache:insert(data) +end + +function state.reconnect.dispatch(self) + local data = self.v_sock:pop_msg(2, "big") + + if not data then + return + end + + print("dispatch:", data) + local recv, msg = data:match "([^\n]*)\n([^\n]*)" + recv = tonumber(recv) + + local sendnumber = self.v_sendnumber + + local cb = self.v_reconnect_cb + self.v_reconnect_cb = nil + + -- 重连失败 + if msg ~= "200" then + print("msg:", msg) + if cb then + cb(false) + end + switch_state(self, "reconnect_error") + return + end + + -- 服务器接受的数据要比客户端记录的发送的数据还要多 + if recv > sendnumber then + if cb then + cb(false) + end + switch_state(self, "reconnect_match_error") + return + end + + -- 需要补发的数据 + local nbytes = 0 + if recv < sendnumber then + nbytes = sendnumber - recv + local more_data = self.v_cache:get(nbytes) + -- 缓存的数据不足 + if not more_data then + if cb then + cb(false) + end + switch_state(self, "reconnect_cache_error") + return + end + + -- 发送补发数据 + assert(#more_data == nbytes) + self.v_sock:send(more_data) + end + + -- 重连成功 + if cb then + cb(true) + end + switch_state(self, "forward") +end + +function state.reconnect.dispose(state_self, success, err, status) + if success then + return true, nil, "reconnect" + else + err = string.format("sock_error:%s sock_status:%s sconn_state:reconnect", err, status) + return false, err, "reconnect" + end +end + +-------------- forward ------------------ +function state.forward.dispatch(self) + local recv_buf = self.v_recv_buf + local rc4_s2c = self.v_rc4_s2c + local sock = self.v_sock + local count = sock:recv(out) + + for i = 1, count do + local v = out[i] + self.v_recvnumber = self.v_recvnumber + #v + v = rc4_s2c:crypt(v) + recv_buf:push(v) + end +end + +function state.forward.send(self, data) + local sock = self.v_sock + local rc4_c2s = self.v_rc4_c2s + local cache = self.v_cache + data = rc4_c2s:crypt(data) + + sock:send(data) + + self.v_sendnumber = self.v_sendnumber + #data + cache:insert(data) +end + +function state.forward.dispose(state_self, success, err, status) + if success then + if status ~= "forward" then + error(string.format("invalid sock_status:%s", status)) + end + return true, nil, "forward" + else + err = string.format("sock_error:%s sock_status:%s sconn_state:forward", err, status) + return false, err, "forward" + end +end + +-------------- close ------------------ +function state.close.dispose(state_self, success, err, status) + err = string.format("sock_error:%s sock_status:%s sconn_state:close", err, status) + return false, err, "close" +end + +local function connect(host, port, targetserver, flag) + local raw = { + v_state = false, + v_sock = false, + + v_clientkey = false, + v_secret = false, + v_id = false, + + v_rc4_c2s = false, + v_rc4_s2c = false, + + v_sendnumber = 0, + v_recvnumber = 0, + v_reconnect_index = 0, + v_cache = cache_create(), + + v_send_buf = {}, + v_send_buf_top = 0, + + v_recv_buf = buffer_queue.create(), + } + + local sock, err = conn.connect_host(host, port) + if not sock then + return nil, err + end + + raw.v_sock = sock + local self = setmetatable(raw, { + __index = mt, + }) + switch_state(self, "newconnect", targetserver, flag) + return self +end + +function mt:reconnect(cb) + local state_name = self.v_state.name + if state_name ~= "forward" and state_name ~= "reconnect" then + return false, string.format("error state switch `%s` to reconnect", state_name) + end + + local addr = self.v_sock.o_host_addr + local port = self.v_sock.o_port + + local success, err = self.v_sock:new_connect(addr, port) + if not success then + return false, err + end + + self.v_reconnect_cb = cb + switch_state(self, "reconnect") + return true +end + +--[[ +update 接口现在会返回三个参数 success, err, status + +success: boolean类型 表示当前status是否正常 + true: err返回值为nil + false: err返回值为string,描述错误信息 + +err: string类型 表示当前status的错误信息,在success 为false才会有效 + +status: string类型 当前sconn所在的状态,状态只能是: + "connect": 连接状态 + "forward": 连接成功状态 + "reconnect": 断线重连状态 + "connect_break": 断开连接状态 + "close": 关闭状态 +]] +function mt:update() + local sock = self.v_sock + local cur_state = self.v_state + local success, err, status = sock:update() + local dispatch = cur_state.dispatch + if success and dispatch then + dispatch(self) + end + + -- 网络连接主动断开 + if status == "connect_break" then + return success, err, status + end + + -- 处理返回状态值 + success, err, status = cur_state.dispose(cur_state, success, err, status) + return success, err, status +end + +function mt:send_msg(data, header_len, endian) + local _send = self.v_state.send + header_len = header_len or DEF_MSG_HEADER_LEN + endian = endian or DEF_MSG_ENDIAN + + data = pack_data(data, header_len, endian) + _send(self, data) + return true +end + +function mt:recv_msg(out_msg, header_len, endian) + header_len = header_len or DEF_MSG_HEADER_LEN + endian = endian or DEF_MSG_ENDIAN + + local recv_buf = self.v_recv_buf + return recv_buf:pop_all_block(out_msg, header_len, endian) +end + +function mt:close() + if self.v_sock then + self.v_sock:close() + end + self.v_sock = nil + self.v_recv_buf:clear() + switch_state(self, "close") +end + +return { + connect_host = connect, +} + diff --git a/tools/LuaMacro/config.ld b/tools/LuaMacro/config.ld deleted file mode 100644 index 40e3744..0000000 --- a/tools/LuaMacro/config.ld +++ /dev/null @@ -1,8 +0,0 @@ -project = 'LuaMacro' -format = 'markdown' -new_type("macro","Macros") -output = 'api' -alias("p","param") -file = { - 'macro.lua','macro','luam.lua', -} diff --git a/tools/LuaMacro/docgen b/tools/LuaMacro/docgen deleted file mode 100644 index f8765d3..0000000 --- a/tools/LuaMacro/docgen +++ /dev/null @@ -1,3 +0,0 @@ -ldoc . -lua markdown.lua readme.md -cp readme.html docs/index.html diff --git a/tools/LuaMacro/docgen.bat b/tools/LuaMacro/docgen.bat deleted file mode 100644 index 509bc1f..0000000 --- a/tools/LuaMacro/docgen.bat +++ /dev/null @@ -1 +0,0 @@ -ldoc . && lua ..\winapi\markdown.lua readme.md && copy readme.html docs\index.html diff --git a/tools/LuaMacro/luam b/tools/LuaMacro/luam deleted file mode 100755 index bd02269..0000000 --- a/tools/LuaMacro/luam +++ /dev/null @@ -1,280 +0,0 @@ -#!/usr/bin/env lua ---[[-- -Front end for LuaMacro, a Lua macro preprocessor. - -The default action is to preprocess and run a Lua file. To just dump -the preprocessed output, use the `-d` flag. Like `lua`, the `-l` flag can -be used to load a library first, but you need to explicitly say `-i` to -get an interactive prompt. - -The package loader is modified so that `require 'mod'` will preprocess `mod` if it is found as `mod.m.lua`. - -Dumping is the only action available when preprocessing C code with `-C`. - -@script luam -]] - --- adjust the path so that this script can see the macro package -local path = arg[0]:gsub('[^/\\]+$','') -package.path = package.path .. ';' .. path .. '?.lua;'..path .. 'macro/?.lua' -local macro = require 'macro' -require 'macro.builtin' - ---- Using luam. --- @usage follows -local usage = [[ -LuaMacro 2.5.0, a Lua macro preprocessor and runner - -l require a library - -e statement to be executed - -V set a variable (-VX or -VY=1) - -c error context to be shown (default 2) - -d dump preprocessed output to stdout - -o write to this file - -C C lexer - -N No #line directives when generating C - -i interactive prompt - -v verbose error trace - Lua source file -]] - --- parsing the args, the hard way: -local takes_value = {l = '', e = '', c = 2, o = '',V = ';'} - -local args = {} -local idx,i = 1,1 -while i <= #arg do - local a = arg[i] - local flag = a:match '^%-(.+)' - local val - if flag then - if #flag > 1 then -- allow for -lmod, like Lua - val = flag:sub(2) - flag = flag:sub(1,1) - end - -- grab the next argument if we need a value - if takes_value[flag] and not val then - i = i + 1 - val = arg[i] - end - -- convert the argument, if required - local def = takes_value[flag] - if type(def) == 'number' then - val = tonumber(val) - elseif def == ';' and args[flag] then - val = args[flag]..';'..val - end - args[flag] = val or true - else - args[idx] = a - idx = idx + 1 - end - i = i + 1 -end - -if not args[1] and not args.i then - print(usage) - os.exit() -elseif args[1] then - args.input_name = args[1] - args.input,err = io.open(args[1],'r') - if err then return print(err) end - table.remove(args,1) -end --- set defaults, if flags not specified -for k,v in pairs(takes_value) do - if not args[k] then - args[k] = v - end -end - ----------- compiling and running the output ------ --- the tricky bit here is presenting the errors so that they refer to the --- original line numbers. In addition, we also present a few lines of context --- in the output. - -local function lookup_line (lno,li) - for i = 1,#li-1 do - --print(li[i].il,li[i].ol,lno,'match') - if lno < li[i+1].ol then - return li[i].il + (lno - li[i].ol) - 1 - end - end - return li[#li].il + (lno - li[#li].ol) - 1 -end - --- iterating over all lines in a string can be awkward; --- gmatch doesn't handle the empty-line cases properly. -local function split_nl (t) - local k1 = 1 - local k2 = t:find ('[\r\n]',k1) - return function() - if not k2 then return nil end - local res = t:sub(k1,k2-1) - k1 = k2+1 - k2 = t:find('[\r\n]',k1) - return res - end -end - -local function fix_error_trace (err,li) - local strname,lno = err:match '%[string "(%S+)"%]:(%d+)' - local ino - if strname then - lno = tonumber(lno) - if li then - ino = lookup_line(lno,li) - err = err:gsub('%[string "%S+"%]:'..(lno or '?')..':',strname..':'..(ino or '?')) - end - end - return err,lno,ino -end - -local function runstring (code,name,li,...) - local res,err = loadstring(code,name) - local lno,ok - if not res then - err,lno,ino = fix_error_trace(err,li) - if ino then - print 'preprocessed context of error:' - local l1,l2 = lno-args.c,lno+args.c - local l = 1 - for line in split_nl(code) do - if l >= l1 and l <= l2 then - if l == lno then io.write('*') else io.write(' ') end - print(l,line) - end - l = l + 1 - end - end - io.stderr:write(err,'\n') - os.exit(1) - end - ok,err = xpcall(function(...) return res(...) end, debug.traceback) - if not ok then - err = err:gsub("%[C%]: in function 'xpcall'.+",'') - if li then - repeat - err,lno = fix_error_trace(err,li) - until not lno - end - io.stderr:write(err,'\n') - end - return ok -end - -local function subst (ins,name) - local C - if args.C then - C = args.N and true or 'line' - end - return macro.substitute_tostring(ins,name,C,args.v) -end - -local function subst_runstring (ins,name,...) - local buf,li = subst(ins,name) - if not buf then - io.stderr:write(li,'\n') - os.exit(1) - end - if args.d or args.C or args.o ~= '' then - if args.o == '' then - print(buf) - else - local f = io.open(args.o,'w') - f:write(buf) - f:close() - end - else - return runstring(buf,name,li,...) - end -end - --- Lua 5.1/5.2 compatibility -local pack = table.pack -if not pack then - function pack(...) - return {n=select('#',...),...} - end -end -if not unpack then unpack = table.unpack end - -local function eval(code) - local status,val,f,err,rcnt - code,rcnt = code:gsub('^%s*=','return ') - f,err = loadstring(code,'TMP') - if f then - res = pack(pcall(f)) - if not res[1] then err = res[2] - else - return res - end - end - if err then - err = tostring(err):gsub('^%[string "TMP"%]:1:','') - return {nil,err} - end -end - -local function interactive_loop () - os.execute(arg[-1]..' -v') -- for the Lua copyright - print 'Lua Macro 2.5.0 Copyright (C) 2007-2011 Steve Donovan' - - local function readline() - io.write(_PROMPT or '> ') - return io.read() - end - - require 'macro.all' - _G.macro = macro - macro.define 'quit os.exit()' - macro._interactive = true - - local line = readline() - while line do - local s,err = subst(line..'\n') - if not s then - err = err:gsub('.-:%d+:','') - print('macro error: '..err) - elseif not s:match '^%s*$' then - if args.d then print(s) end - local res = eval(s) - if not res[1] then - print('expanded: '..s) - print('error: '..res[2]) - elseif res[2] ~= nil then - print(unpack(res,2)) - end - end - line = readline() - end -end - -macro.set_package_loader() - -if args.l ~= '' then require(args.l) end - -if args.V ~= ';' then - for varset in args.V:gmatch '([^;]+)' do - local sym,val = varset:match '([^=]+)=(.+)' - if not sym then - sym = varset - val = true - end - _G[sym] = val - end -end - -require 'macro.ifelse' - -if args.e ~= '' then - subst_runstring(args.e,"") -else - if args.input then - arg = args - arg[0] = args.input_name - arg[-1] = 'luam' - subst_runstring(args.input,args.input_name,unpack(args)) - elseif args.i then - interactive_loop() - end -end diff --git a/tools/LuaMacro/macro.lua b/tools/LuaMacro/macro.lua deleted file mode 100644 index 6c1a38d..0000000 --- a/tools/LuaMacro/macro.lua +++ /dev/null @@ -1,713 +0,0 @@ ----------------------------------------------- --- LuaMacro 2, a macro-preprocessor for Lua. --- Unlike LuaMacro 1.x, it does not depend on the token-filter patch and generates --- Lua code which can be printed out or compiled directly. C-style macros are easy, but LM2 --- allows for macros that can read their own input and generate output using Lua code. --- New in this release are lexically-scoped macros. --- The Lua Lpeg Lexer is by Peter Odding. --- --- Examples: --- --- macro.define 'sqr(x) ((x)*(x))' --- macro.define 'assert_(expr) assert(expr,_STR_(expr))' --- macro.define('R(n)',function(n) --- n = n:get_number() --- return ('-'):rep(n) --- end --- macro.define('lazy',function(get) --- get() -- skip space --- local expr,endt = get:upto(function(t,v) --- return t == ',' or t == ')' or t == ';' --- or (t=='space' and v:match '\n') --- end) --- return 'function(_) return '..tostring(expr)..' end'..tostring(endt) --- end) --- --- --- @author Steve Donovan --- @copyright 2011 --- @license MIT/X11 --- @module macro --- @alias M - -local macro = {} -local M = macro -local lexer = require 'macro.lexer' -local Getter = require 'macro.Getter' -local TokenList = require 'macro.TokenList' -local scan_code = lexer.scan_lua -local append = table.insert -local setmetatable = setmetatable - ---local tdump = require 'pl.pretty'.dump - -local scan_iter, tnext = Getter.scan_iter, Getter.next - - -M.upto_keywords = Getter.upto_keywords -M.Putter = TokenList.new - --- given a token list, a set of formal arguments and the actual arguments, --- return a new token list where the formal arguments have been replaced --- by the actual arguments -local function substitute_tokenlist (tl,parms,args) - local append,put_tokens = table.insert,TokenList.tokens - local parm_map = {} - for i,name in ipairs(parms) do - parm_map[name] = args[i] - end - local res = {} - for _,tv in ipairs(tl) do - local t,v = tv[1],tv[2] - if t == 'iden' then - local pval = parm_map[v] - if pval then - put_tokens(res,pval) - else - append(res,tv) - end - else - append(res,tv) - end - end - return res -end - ----------------- --- Defining and Working with Macros. --- @section macros - ---- make a copy of a list of tokens. --- @param tok the list --- @param pred copy up to this condition; if defined, must be a function --- of two arguments, the token type and the token value. --- @return the copy --- @return the token that matched the predicate -function M.copy_tokens(tok,pred) - local res = {} - local t,v = tok() - while t and not (pred and pred(t,v)) do - append(res,{t,v}) - t,v = tok() - end - return res,{t,v} -end - ----- define new lexical tokens. --- @param extra a list of strings defining the new tokens --- @usage macro.define_tokens{'{|'} -function M.define_tokens(extra) - lexer.add_extra_tokens(extra) -end - -local imacros,smacros = {},{} - -M.macro_table = imacros - ---- define a macro using a specification string and optional function. --- The specification looks very much like a C preprocessor macro: the name, --- followed by an optional formal argument list (_no_ space after name!) and --- the substitution. e.g `answer 42` or `sqr(x) ((x)*(x))` --- --- If there is no substitution, then the second argument must be a function which --- will be evaluated for the actual substitution. If there are explicit parameters, then they will be passed as token lists. Otherwise, the function is passed a `get` and a `put` argument, which are `Getter` and `TokenList` objects. --- --- The substitution function may return a `TokenList` object, or a string. --- @param macstr --- @param subst_fn the optional substitution function --- @see macro.Getter, macro.TokenList -function M.define(macstr,subst_fn) - local tok,t,macname,parms,parm_map - local mtbl - tok = scan_code(macstr) - t,macname = tok() - if t == 'iden' then mtbl = imacros - elseif t ~= 'string' and t ~= 'number' and t ~= 'keyword' then - mtbl = smacros - else - error("a macro cannot be of type "..t) - end - t = tok() - if t == '(' then - parms = Getter.new(tok):idens() - end - mtbl[macname] = { - name = macname, - subst = subst_fn or M.copy_tokens(tok), - parms = parms - } -end - ---- define a macro using a function and a parameter list. --- @param name either an identifier or an operator. --- @param subst a function --- @param parms a list of parameter names --- @return the existing value of this macro, if any -function M.set_macro(name,subst,parms) - local macros - if name:match '^[_%a][_%w]*$' then - macros = imacros - else - macros = smacros - end - if subst == nil then - macros[name] = nil - return - end - local last = macros[name] - if type(subst) ~= 'table' or not subst.name then - subst = { - name = name, - subst = subst, - parms = parms - } - end - macros[name] = subst - return last -end - ---- defined a scoped macro. Like define except this macro will not --- be visible outside the current scope. --- @param name either an identifier or an operator. --- @param subst a function --- @param parms a list of parameter names --- @see set_macro -function M.define_scoped (name,subst,parms) - local old_value = M.set_macro(name,subst,parms) - M.block_handler(-1,function() - M.set_macro(name,old_value) - end) -end - ---- get the value of a macro. The macro substitution must either be a --- a string or a single token. --- @param name existing macro name --- @return a string value, or nil if the macro does not exist. -function M.get_macro_value(name) - local mac = imacros[name] - if not mac then return nil end - if type(mac.subst) == 'table' then - return mac.subst[1][2] - else - return mac.subst - end -end - -local function get_macro (mac, no_error) - local macro = imacros[mac] - if not macro and not no_error then - M.error("macro "..mac.." is not defined") - end - return macro -end - -local push,pop = table.insert,table.remove - ---- push a value on the stack associated with a macro. --- @param name macro name --- @param value any value -function M.push_macro_stack (name,value) - local macro = get_macro(name) - macro.stack = macro.stack or {} - push(macro.stack,value) -end - ---- pop a value from a macro stack. --- @param name macro name --- @return any value, if defined -function M.pop_macro_stack (name) - local macro = get_macro(name) - if macro.stack and #macro.stack > 0 then - return pop(macro.stack) - end -end - ---- value of top of macro stack. --- @param name macro name --- @return any value, if defined -function M.value_of_macro_stack (name) - local macro = get_macro(name,true) - if not macro then return nil end - if macro.stack and #macro.stack > 0 then - return macro.stack[#macro.stack] - end -end - -local lua_keywords = { - ['do'] = 'open', ['then'] = 'open', ['else'] = 'open', ['function'] = 'open', - ['repeat'] = 'open'; - ['end'] = 'close', ['until'] = 'close',['elseif'] = 'close' -} - -local c_keywords = {} -local keywords = lua_keywords - -local block_handlers,keyword_handlers = {},{} -local level = 1 - ---- specify a block handler at a given level. --- a block handler may indicate with an extra true return --- that it wants to persist; it is passed the getter and the keyword --- so we can get more specific end-of-block handlers. --- @param lev relative block level --- @param action will be called when the block reaches the level -function M.block_handler (lev,action) - lev = lev + level - if not block_handlers[lev] then - block_handlers[lev] = {} - end - append(block_handlers[lev],action) -end - -local function process_block_handlers(level,get,v) - local persist,result - for _,bh in pairs(block_handlers[level]) do - local res,keep = bh(get,v) - if not keep then - if res then result = res end - else - persist = persist or {} - append(persist,bh) - end - end - block_handlers[level] = persist - return result -end - - ---- set a keyword handler. Unlike macros, the keyword itself is always --- passed through, but the handler may add some output afterwards. --- If the action is nil, then the handler for that keyword is removed. --- @param word keyword --- @param action function to be called when keyword is encountered --- @return previous handler associated with this keyword -function M.keyword_handler (word,action) - if word == 'BEGIN' or word == 'END' then - keyword_handlers[word] = action - return - end - if action then - local last = keyword_handlers[word] - keyword_handlers[word] = action - return last - else - keyword_handlers[word] = nil - end -end - ---- set a scoped keyword handler. Like keyword_handler, except --- it restores the original keyword handler (if any) at the end --- of the current block. --- @param word keyword --- @param action to be called when keyword is encountered --- @see keyword_handler -function M.scoped_keyword_handler (keyword, action) - local last = M.keyword_handler(keyword,action) - M.block_handler(-1,function() - M.keyword_handler(keyword,last) - end) -end - --- a convenient way to use keyword handlers. This sets a handler and restores --- the old handler at the end of the current block. --- @param word keyword --- @param action to be called when keyword is encountered --- @return a function that creates a scoped keyword handler -function M.make_scoped_handler(keyword,handler) - return function() M.scoped_keyword_handler(keyword, action) end -end - -M.please_throw = false - ---- macro error messages. --- @param msg the message: will also have file:line. -function M.error(msg) - msg = M.filename..':'..lexer.line..': '..msg - if M.please_throw then - error(msg,2) - else - io.stderr:write(msg,'\n') - os.exit(1) - end -end - -M.define ('debug_',function() - M.DEBUG = true -end) - ---- macro error assert. --- @param expr an expression. --- @param msg a message -function M.assert(expr,msg) - if not expr then M.error(msg or 'internal error') end - return expr -end - -Getter.error = M.error -Getter.assert = M.assert -TokenList.assert = M.assert - -local line_updater, line_table, last_name, last_lang - -local function lua_line_updater (iline,oline) - if not line_table then line_table = {} end - append(line_table,{il=iline,ol=oline}) -end - -local function c_line_updater (iline,oline,last_t,last_v) - local endt = last_t == 'space' and last_v or '\n' - return '#line '..iline..' "'..M.filename..'"'..endt -end - -local make_putter = TokenList.new - ---- do a macro substitution on Lua source. --- @param src Lua source (either string or file-like reader) --- @param out output (a file-like writer) --- @param name input file name --- @param use_c nil for Lua; if 'line', then output #line directives; if true, then don't --- @return the result as table of strings --- @return line number information -function M.substitute(src,name, use_c) - local out, ii = {}, 1 - local subparse - if name then - last_name = name - last_lang = use_c - else - name = last_name - use_c = last_lang and true - subparse = true - end - M.filename = name - if use_c then - lexer = require 'macro.clexer' - scan_code = lexer.scan_c - keywords = c_keywords - if use_c == 'line' then - line_updater = c_line_updater - else - line_updater = function() end - end - else - lexer = require 'macro.lexer' - scan_code = lexer.scan_lua - keywords = lua_keywords - line_updater = lua_line_updater - end - local tok = scan_code(src,name) - local iline,iline_changed = 0 - local last_t,last_v = 'space','\n' - local do_action - - - local t,v = tok() - - -- this function get() is always used, so that we can handle end-of-stream properly. - -- The substitution mechanism pushes a new stream on the tstack, which is popped - -- when empty. - local tstack = {} - local push,pop = table.insert,table.remove - - local function get () - last_t,last_v = t,v - local t,v = tok() - while not t do - tok = pop(tstack) - if tok == nil then - if not subparse and keyword_handlers.END then - do_action(keyword_handlers.END) - keyword_handlers.END = nil - end - if tok == nil then -- END action might have inserted some tokens - return nil - end - end -- finally finished - t,v = tok() - end - if name == lexer.name and iline ~= lexer.line then - iline = lexer.line -- input line has changed - iline_changed = last_v - end - return t,v - end - - local getter = Getter.new(get) - - --- get a list of consecutive matching tokens. - -- @param get token fetching function - -- @param accept set of token types (default: `{space=true,comment=true}`) - function getter.matching (get, accept) - accept = accept or {space=true, comment=true} - local tl = TokenList.new() - local t,v = get:peek(1, true) - while accept[t] do - t,v = get () - append(tl, {t, v}) - t,v = get:peek(1, true) - end - return tl - end - - function getter:peek (offset,dont_skip) - local step = offset < 0 and -1 or 1 -- passing offset 0 is undefined - local k = 0 - local token, t, v - repeat - while true do - token = tok (k) - if not token then return nil, 'EOS' end - t,v = token[1], token[2] - if dont_skip or (t ~= 'space' and t ~= 'comment') then break end - k = k + 1 - end - offset = offset - step - k = k + step - until offset == 0 - return t,v,k+1 - end - - function getter:peek2 () - local t1,v1,k1 = self:peek(1) - local t2,v2 = self:peek(k1+1) - return t1,v1,t2,v2 - end - - function getter:patch (idx,text) - out[idx] = text - end - - function getter:placeholder (put) - put:iden '/MARK?/' - return ii - end - - function getter:copy_from (pos,clear) - local res = {} - for i = pos, ii do - if out[i] and not out[i]:match '^#line' then - append(res,out[i]) - end - end - if clear then - for i = pos, ii do - table.remove(out,pos) - ii = ii - 1 - end - end - return table.concat(res) - end - - -- this feeds the results of a substitution into the token stream. - -- substitutions may be token lists, Lua strings or nil, in which case - -- the substitution is ignored. The result is to push a new token stream - -- onto the tstack, so it can be fetched using get() above - local function push_substitution (subst) - if subst == nil then return end - local st = type(subst) - push(tstack,tok) - if st == 'table' then - subst = scan_iter(subst) - elseif st == 'string' then - subst = scan_code(subst) - end - tok = subst - end - M.push_substitution = push_substitution - - -- a macro object consists of a subst object and (optional) parameters. - -- If there are parms, then a macro argument list must follow. - -- The subst object is either a token list or a function; if a token list we - -- substitute the actual parameters for the formal parameters; if a function - -- then we call it with the actual parameters. - -- Without parameters, it may be a simple substitution (TL or Lua string) or - -- may be a function. In the latter case we call it passing the token getter, - -- assuming that it will grab anything it needs from the token stream. - local function expand_macro(get,mac) - local pass_through - local subst = mac.subst - local fun = type(subst)=='function' - if mac.parms then - t = tnext(get); - if t ~= '(' then - M.error('macro '..mac.name..' expects parameters') - end - local args,err = Getter.list(get) - M.assert(args,'no end of argument list') - if fun then - subst = subst(unpack(args)) - else - if #mac.parms ~= #args then - M.error(mac.name.." takes "..#mac.parms.." arguments") - end - subst = substitute_tokenlist(subst,mac.parms,args) - end - elseif fun then - subst,pass_through = subst(getter,make_putter()) - end - push_substitution(subst) - return pass_through - end - - local multiline_tokens,sync = lexer.multiline_tokens,lexer.sync - local line,last_diff = 0,0 - - function do_action (action) - push_substitution(action(getter,make_putter())) - end - - if not subparse and keyword_handlers.BEGIN then - do_action(keyword_handlers.BEGIN) - end - - while t do - --print('tv',t,v) - local dump = true - if t == 'iden' then -- classic name macro - local mac = imacros[v] - if mac then - dump = expand_macro(get,mac) - end - elseif t == 'keyword' then - -- important to track block level for lexical scoping and block handlers - local class = keywords[v] - if class == 'open' then - if v ~= 'else' then level = level + 1 end - elseif class == 'close' then - level = level - 1 - if block_handlers[level] then - local res = process_block_handlers(level,get,v) - if res then push_substitution(res) end - end - --* elseif class == 'hook' then - end - local action = keyword_handlers[v] - if action then do_action(action) end - else -- any unused 'operator' token (like @, \, #) can be used as a macro - if use_c then - if v == '{' then - level = level + 1 - elseif v == '}' then - level = level - 1 - if block_handlers[level] then - local res = process_block_handlers(level,get,v) - if res then push_substitution(res) end - end - end - end - local mac = smacros[v] - if mac then - dump = expand_macro(get,mac) - end - end - if dump then - if multiline_tokens[t] then -- track output line - line = sync(line, v) - end - if iline_changed then - local diff = line - iline - if diff ~= last_diff then - local ldir = line_updater(iline,line,last_t,last_v) - if ldir then out[ii] = ldir; ii=ii+1 end - last_diff = diff - end - iline_changed = nil - end - out[ii] = v - ii = ii + 1 - end - t,v = get() - end - - return out,line_table -end - ---- take some Lua source and return the result of the substitution. --- Does not raise any errors. --- @param src either a string or a readable file object --- @param name optional name for the chunk --- @return the result or nil --- @return the error, if error -function M.substitute_tostring(src,name,use_c,throw) - M.please_throw = true - local ok,out,li - if throw then - out,li = M.substitute(src,name,use_c) - else - ok,out,li = pcall(M.substitute,src,name,use_c) - end - if type(src) ~= 'string' and src.close then src:close() end - if not ok then return nil, out - else - return table.concat(out), li - end -end - -local lua52 = _VERSION:match '5.2' -local load, searchpath = load, package.searchpath - -if not lua52 then -- Lua 5.1 - function load (env,src,name) - local chunk,err = loadstring(src,name) - if chunk and env then - setfenv(chunk,env) - end - return chunk,err - end -end - -if not searchpath then - local sep = package.config:sub(1,1) - searchpath = function (mod,path) - mod = mod:gsub('%.',sep) - for m in path:gmatch('[^;]+') do - local nm = m:gsub('?',mod) - local f = io.open(nm,'r') - if f then f:close(); return nm end - end - end -end - ---- load Lua code in a given envrionment after passing --- through the macro preprocessor. --- @param src either a string or a readable file object --- @param name optional name for the chunk --- @param env the environment (may be nil) --- @return the cnunk, or nil --- @return the error, if no chunk -function M.load(src,name,env) - local res,err = M.substitute_tostring(src,'tmp') - if not res then return nil,err end - return loadin(env,res,name) -end - ---- evaluate Lua macro code in a given environment. --- @param src either a string or a readable file object --- @param env the environment (can be nil) --- @return true if succeeded --- @return result(s) -function M.eval(src,env) - local chunk,err = M.loadin(src,'(tmp)',env) - if not chunk then return nil,err end - return pcall(chunk) -end - -package.mpath = './?.m.lua' - ---- Make `require` use macro expansion. --- This is controlled by package.mpath, which is initially './?.m.lua' -function M.set_package_loader() - -- directly inspired by https://github.com/bartbes/Meta/blob/master/meta.lua#L32, - -- after a suggestion by Alexander Gladysh - table.insert(package.loaders, function(name) - local fname = searchpath(name,package.mpath) - if not fname then return nil,"cannot find "..name end - local res,err = M.load(io.open(fname),lname) - if not res then - error (err) - end - return res - end) -end - -return macro diff --git a/tools/LuaMacro/macro/Getter.lua b/tools/LuaMacro/macro/Getter.lua deleted file mode 100644 index af58b3c..0000000 --- a/tools/LuaMacro/macro/Getter.lua +++ /dev/null @@ -1,320 +0,0 @@ ---- Getter class. Used to get values from the token stream. The first --- argument `get` of a macro substitution function is of this type. --- --- M.define ('\\',function(get,put) --- local args, body = get:idens('('), get:list() --- return put:keyword 'function' '(' : idens(args) ')' : --- keyword 'return' : list(body) : space() : keyword 'end' --- end) --- --- The second argument `put` is a `TokenList` object. --- @see macro.TokenList --- @module macro.Getter - -local TokenList = require 'macro.TokenList' -local append = table.insert -local setmetatable = setmetatable - -local Getter = { - __call = function(self) - return self.fun() - end -} -local M = Getter - -Getter.__index = Getter; - -local scan_iter - -function Getter.new (get) - return setmetatable({fun=get},Getter) -end - -function Getter.from_tl(tl) - return Getter.new(scan_iter(tl)) -end - -local Tok = { - __tostring = function(self) - return self[2] - end -} - -local function tok_new (t) - return setmetatable(t,Tok) -end - --- create a token iterator out of a token list -function Getter.scan_iter (tlist) - local i,n = 1,#tlist - return function(k) - if k ~= nil then - k = i + k - if k < 1 or k > n then return nil end - return tlist[k] - end - local tv = tlist[i] - if tv == nil then return nil end - i = i + 1 - return tv[1],tv[2] - end -end - -scan_iter = Getter.scan_iter - ---- get the next non-whitespace token. --- @return token type --- @return token value --- @function Getter.next -function Getter.next(get) - local t,v = get() - while t == 'space' or t == 'comment' do - t,v = get() - end - return t,v -end - -local TL,LTL = TokenList.new, TokenList.new_list - - -local function tappend (tl,t,val) - val = val or t - append(tl,{t,val}) -end - ---- get a balanced block. --- Typically for grabbing up to an `end` keyword including any nested --- `if`, `do` or `function` blocks with their own `end` keywords. --- @param tok the token stream --- @param begintokens set of tokens requiring their own nested *endtokens* --- (default: `{['do']=true,['function']=true,['if']=true}`) --- @param endtokens set of tokens ending a block (default:`{['end']=true}`) --- @return list of tokens --- @return block end token in form `{type,value}` --- @usage --- -- copy a balanced table constructor --- get:expecting '{' --- put '{':tokens (get:block ({['{']=true}, {['}']=true}) '}') -function Getter.block(tok,begintokens,endtokens) - begintokens = begintokens or {['do']=true,['function']=true,['if']=true} - endtokens = endtokens or {['end']=true} - local level = 1 -- used to count expected matching `endtokens` - local tl = TL() - local token,value - repeat - token,value = tok() - if not token then return nil,'unexpected end of block' end - if begintokens[value] then - level = level + 1 - elseif endtokens[value] then - level = level - 1 - end - if level > 0 then -- final end token is returned separately - tappend(tl,token,value) - end - until level == 0 - return tl,tok_new{token,value} -end - ---- get a delimited list of token lists. --- Typically used for grabbing argument lists like ('hello',a+1,fred(c,d)); will count parens --- so that the delimiter (usually a comma) is ignored inside sub-expressions. You must have --- already read the start token of the list, e.g. open parentheses. It will eat the end token --- and return the list of TLs, plus the end token. Based on similar code in Penlight's --- `pl.lexer` module. --- @param tok the token stream --- @param endt the end token (default ')') --- @param delim the delimiter (default ',') --- @return list of token lists --- @return end token in form {type,value} -function Getter.list(tok,endtoken,delim) - endtoken = endtoken or ')' - delim = delim or ',' - local parm_values = LTL() - local level = 1 -- used to count ( and ) - local tl = TL() - local is_end - if type(endtoken) == 'function' then - is_end = endtoken - elseif endtoken == '\n' then - is_end = function(t,val) - return t == 'space' and val:find '\n' - end - else - is_end = function (t) - return t == endtoken - end - end - local token,value = tok() - if is_end(token,value) then return parm_values end - if token == 'space' then - token,value = tok() - end - while true do - if not token then return nil,'unexpected end of list' end -- end of stream is an error! - if is_end(token,value) and level == 1 then - append(parm_values,tl) - break - elseif token == '(' then - level = level + 1 - tappend(tl,'(') - elseif token == ')' then - level = level - 1 - if level == 0 then -- finished with parm list - append(parm_values,tl) - break - else - tappend(tl,')') - end - elseif token == '{' then - level = level + 1 - tappend(tl,'{') - elseif token == '}' then - level = level - 1 - tappend(tl,'}') - elseif token == delim and level == 1 then - append(parm_values,tl) -- a new parm - tl = TL() - else - tappend(tl,token,value) - end - token,value=tok() - end - return parm_values,tok_new{token,value} -end - -function Getter.upto_keywords (k1,k2) - return function(t,v) - return t == 'keyword' and (v == k1 or v == k2) - end,'' -end - -local tnext = Getter.next - - -function Getter.upto(tok,k1,k2) - local endt = k1 - if type(k1) == 'string' and k1:match '^%a+$' then - endt = Getter.upto_keywords(k1,k2) - end - local ltl,tok = tok:list(endt,'') - M.assert(ltl ~= nil and #ltl > 0,'failed to grab tokens') - return ltl[1],tok -end - -function Getter.line(tok) - return tok:upto(function(t,v) - return (t=='space' and v:match '\n') or t == 'comment' - end) -end - - -local function prettyprint (t, v) - v = v:gsub ("\n", "\\n") - if t == "string" then - if #v > 16 then v = v:sub(1,12).."..."..v:sub(1,1) end - return t.." "..v - end - if #v > 16 then v = v:sub(1,12).."..." end - if t == "space" or t == "comment" or t == "keyword" then - return t.." '"..v.."'" - elseif t == v then - return "'"..v.."'" - else - return t.." "..v - end -end - ---- get the next identifier token. --- (will be an error if the token has wrong type) --- @return identifier name -function Getter.iden(tok) - local t,v = tnext(tok) - M.assert(t == 'iden','expecting identifier, got '..prettyprint(t,v)) - return v -end - -Getter.name = Getter.iden -- backwards compatibility! - ---- get the next number token. --- (will be an error if the token has wrong type) --- @return converted number -function Getter.number(tok) - local t,v = tnext(tok) - M.assert(t == 'number','expecting number, got '..prettyprint(t,v)) - return tonumber(v) -end - ---- get a delimited list of identifiers. --- works like list. --- @param tok the token stream --- @param endt the end token (default ')') --- @param delim the delimiter (default ',') --- @see list -function Getter.idens(tok,endt,delim) - local ltl,err = tok:list(endt,delim) - if not ltl then error('idens: '..err) end - local names = {} - -- list() will return {{}} for an empty list of tlists - for i = 1,#ltl do - local tl = ltl[i] - local tv = tl[1] - if tv then - if tv[1] == 'space' then tv = tl[2] end - names[i] = tv[2] - end - end - return names, err -end - -Getter.names = Getter.idens -- backwards compatibility! - ---- get the next string token. --- (will be an error if the token has wrong type) --- @return string value (without quotes) -function Getter.string(tok) - local t,v = tok:expecting("string") - return v:sub(2,-2) -end - ---- assert that the next token has the given type. This will throw an --- error if the next non-whitespace token does not match. --- @param type a token type ('iden','string',etc) --- @param value a token value (optional) --- @usage get:expecting '(' --- @usage get:expecting ('iden','bonzo') -function Getter.expecting (tok,type,value) - local t,v = tnext(tok) - if t ~= type then M.error ("expected "..type.." got "..prettyprint(t,v)) end - if value then - if v ~= value then M.error("expected "..value.." got "..prettyprint(t,v)) end - end - return t,v -end - ---- peek ahead or before in the token stream. --- @param k positive delta for looking ahead, negative for looking behind. --- @param dont_skip true if you want to check for whitespace --- @return the token type --- @return the token value --- @return the token offset --- @function Getter.peek - ---- peek ahead two tokens. --- @return first token type --- @return first token value --- @return second token type --- @return second token value --- @function Getter.peek2 - ---- patch the token stream at the end. --- @param idx index in output table --- @param text to replace value at that index --- @function Getter.patch - ---- put out a placeholder for later patching. --- @param put a putter object --- @return an index into the output table --- @function Getter.placeholder - -return Getter diff --git a/tools/LuaMacro/macro/TokenList.lua b/tools/LuaMacro/macro/TokenList.lua deleted file mode 100644 index a18ac67..0000000 --- a/tools/LuaMacro/macro/TokenList.lua +++ /dev/null @@ -1,201 +0,0 @@ ---------------- --- A TokenList class for generating token lists. --- --- There are also useful `get_` methods for extracting values from --- the first token. --- --- @module macro.TokenList - -local TokenList = {} -local M = TokenList -TokenList.__index = TokenList - -local append = table.insert - -function TokenList.new (tl) - return setmetatable(tl or {},TokenList) -end - -local TokenListList = {} - -function TokenList.new_list (ltl) - return setmetatable(ltl or {},TokenListList) -end - -TokenListList.__index = function(self,key) - local m = TokenList[key] - return function(self,...) - local res = {} - for i = 1,#self do res[i] = m(self[i],...) end - return TokenList.new_list(res) - end -end - --- token-getting helpers - - -local function extract (tl) - local tk = tl[1] - if tk[1] == 'space' then - tk = tl[2] - end - return tk -end - ---- get an identifier from front of a token list. --- @return identifier name -function TokenList.get_iden (tl) - local tk = extract(tl) - M.assert(tk[1]=='iden','expecting identifier') - return tk[2] -end - ---- get an number from front of a token list. --- @return number -function TokenList.get_number(tl) - local tk = extract(tl) - M.assert(tk[1]=='number','expecting number') - return tonumber(tk[2]) -end - ---- get a string from front of a token list. --- @return string value (without quotes) -function TokenList.get_string(tl) - local tk = extract(tl) - M.assert(tk[1]=='string') - return tk[2]:sub(2,-2) -- watch out! what about long string literals?? -end - ---- takes a token list and strips spaces and comments. --- @return new tokenlist -function TokenList.strip_spaces (tl) - local out = TokenList.new() - for _,t in ipairs(tl) do - if t[1] ~= 'comment' and t[1] ~= 'space' then - append(out,t) - end - end - return out -end - ---- pick the n-th token from this tokenlist. --- Note that it returns the value and type, not the type and value. --- @param n (1 to #self) --- @return token value --- @return token type -function TokenList.pick (tl,n) - local t = tl[n] - return t[2],t[1] -end - --- token-putting helpers -local comma,space = {',',','},{'space',' '} - ---- append an identifier. --- @param name the identifier --- @param no_space true if you don't want a space after the iden --- @return self -function TokenList.iden(res,name,no_space) - append(res,{'iden',name}) - if not no_space then - append(res,space) - end - return res -end - -TokenList.name = TokenList.iden -- backwards compatibility! - ---- append a string. --- @param s the string --- @return self -function TokenList.string(res,s) - append(res,{'string','"'..s..'"'}) - return res -end - ---- append a number. --- @param val the number --- @return self -function TokenList.number(res,val) - append(res,{'number',val}) - return res -end - ---- put out a list of identifiers, separated by commas. --- @param res output token list --- @param names a list of identifiers --- @return self -function TokenList.idens(res,names) - for i = 1,#names do - res:iden(names[i],true) - if i ~= #names then append(res,comma) end - end - return res -end - -TokenList.names = TokenList.idens -- backwards compatibility! - ---- put out a token list. --- @param res output token list --- @param tl a token list --- @return self -function TokenList.tokens(res,tl) - for j = 1,#tl do - append(res,tl[j]) - end - return res -end - ---- put out a list of token lists, separated by commas. --- @param res output token list --- @param ltl a list of token lists --- @return self -function TokenList.list(res,ltl) - for i = 1,#ltl do - res:tokens(ltl[i]) - if i ~= #ltl then append(res,comma) end - end - return res -end - ---- put out a space token. --- @param res output token list --- @param space a string containing only whitespace (default ' ') --- @return self -function TokenList.space(res,space) - append(res,{'space',space or ' '}) - return res -end - ---- put out a keyword token. --- @param res output token list --- @param keyw a Lua keyword --- @param no_space true if you don't want a space after the iden --- @return self -function TokenList.keyword(res,keyw,no_space) - append(res,{'keyword',keyw}) - if not no_space then - append(res,space) - end - return res -end - ---- convert this tokenlist into a string. -function TokenList.__tostring(tl) - local res = {} - for j = 1,#tl do - append(res,tl[j][2]) - end - return table.concat(res) -end - ---- put out a operator token. This is the overloaded call operator --- for token lists. --- @param res output token list --- @param keyw an operator string -function TokenList.__call(res,t,v) - append(res,{t,v or t}) - return res -end - -return TokenList diff --git a/tools/LuaMacro/macro/all.lua b/tools/LuaMacro/macro/all.lua deleted file mode 100644 index 0d7c098..0000000 --- a/tools/LuaMacro/macro/all.lua +++ /dev/null @@ -1,5 +0,0 @@ -require 'macro.forall' -require 'macro.lambda' -require 'macro.try' -require 'macro.do' - diff --git a/tools/LuaMacro/macro/assert.lua b/tools/LuaMacro/macro/assert.lua deleted file mode 100644 index b25daaf..0000000 --- a/tools/LuaMacro/macro/assert.lua +++ /dev/null @@ -1,74 +0,0 @@ ---- a simple testing framework. --- Defines a single statment macro assert_ which has the following syntax: --- --- - assert_ val1 == val2 --- - assert_ val1 > val2 --- - assert_ val1 < val2 --- - assert_ val1 matches val2 (using string matching) --- - assert_ val1 throws val2 (ditto, on exception string) --- --- The `==` case has some special forms. If `val2` is `(v1,v2,..)` then --- it's assumed that the expression `val1` returns multiple values. `==` will --- also do value equality for plain tables. If `val2` is a number given in --- %f format (such as 3.14) then it will match `vall` up to that specified --- number of digits. --- --- assert_ {one=1,two=2} == {two=2,one=1} --- assert_ 'hello' matches '^hell' --- assert_ 2 > 1 --- assert_ ('hello'):find 'll' == (3,4) --- assert_ a.x throws 'attempt to index global' --- @module macro.assert - -local M = require 'macro' -local relop = { - ['=='] = 'eq', - ['<'] = 'lt', - ['>'] = 'gt' -} - -local function numfmt (x) - local int,frac = x:match('(%d+)%.(%d+)') - if not frac then return nil end - return '%'..#x..'.'..#frac..'f', x -end - ---- assert that two values match the desired relation. --- @macro assert_ -M.define('assert_',function(get,put) - local testx,tok = get:upto(function(t,v) - return relop[t] or (t == 'iden' and (v == 'matches' or v == 'throws')) - end) - local testy,eos = get:line() - local otesty = testy - testx = tostring(testx) - testy = tostring(testy) - local t,v,op = tok[1],tok[2] - if relop[t] then - op = relop[t] - if t == '==' then - if testy:match '^%(.+%)$' then - testx = 'T_.tuple('..testx..')' - testy = 'T_.tuple'..testy - elseif #otesty == 1 and otesty[1][1] == 'number' then - local num = otesty[1][2] - local fmt,num = numfmt(num) - if fmt then -- explicit floating-point literal - testy = '"'..num..'"' - testx = '("'..fmt..'"):format('..testx..')' - op = 'match' - end - end - end - elseif v == 'matches' then - op = 'match' - elseif v == 'throws' then - op = 'match' - testx = 'T_.pcall_no(function() return '..testx..' end)' - end - return ('T_.assert_%s(%s,%s)%s'):format(op,testx,testy,tostring(eos)) -end) - -return function() - return "T_ = require 'macro.lib.test'" -end diff --git a/tools/LuaMacro/macro/builtin.lua b/tools/LuaMacro/macro/builtin.lua deleted file mode 100644 index 12c8b38..0000000 --- a/tools/LuaMacro/macro/builtin.lua +++ /dev/null @@ -1,161 +0,0 @@ -------- --- LuaMacro built-in macros. --- @module macro.builtin - -local M = require 'macro' - -local function macro_def (scoped) - return function (get) - local t,name,parms,openp - local t,name = get:next() - local upto,ret - if t == '(' then - t,name = get:next() - upto = function(t,v) return t == ')' end - else - upto = function(t,v) - return t == 'space' and v:find '\n' - end - -- return space following (returned by copy_tokens) - ret = true - end - -- might be immediately followed by a parm list - t,openp = get() - if openp == '(' then - parms = get:names() - end - -- the actual substitution is up to the end of the line - local args, space = M.copy_tokens(get,upto) - if scoped then - M.define_scoped(name,args,parms) - else - M.set_macro(name,args,parms) - end - return ret and space[2] - end -end - ---- a macro for defining lexically scoped simple macros. --- def_ may be followed by an arglist, and the substitution is the --- rest of the line. --- @usage def_ block (function() _END_CLOSE_ --- @usage def_ sqr(x) ((x)*(x)) --- @macro def_ -M.define ('def_',macro_def(true)) - ---- a global version of `def_`. --- @see def_ --- @macro define_ -M.define ('define_',macro_def(false)) - ---- set the value of an existing macro. --- the name and value follows immediately, and the value must be --- a single token --- @usage set_ T 'string' --- @usage set_ F function --- @macro set_ -M.define('set_',function(get) - local name = get:name() - local t,v = get:next() - M.set_macro(name,{{t,v}}) -end) - ---- undefining identifier macros. --- @macro undef_ -M.define('undef_',function(get) - M.set_macro(get:name()) -end) - ---- Insert text after current block end. `_END_` is followed by a quoted string --- and is used to insert that string after the current block closes. --- @macro _END_ -M.define ('_END_',function(get) - local str = get:string() - M.block_handler(-1,function(get,word) - if word ~= 'end' then return nil,true end - return str - end) -end) - ---- insert an end after the next closing block. --- @macro _END_END_ --- @see _END_ -M.define '_END_END_ _END_ " end"' - ---- insert a closing parens after next closing block. --- @usage def_ begin (function() _END_CLOSE_ --- fun begin ... end --> fun (function() ... end) --- @macro _END_CLOSE_ --- @see _END_ -M.define '_END_CLOSE_ _END_ ")"' - ---- 'stringizing' macro. --- Will convert its argument into a string. --- @usage def_ _assert(x) assert(x,_STR_(x)) --- @macro _STR_ -M.define('_STR_(x)',function(x) - x = tostring(x) - local put = M.Putter() - return put '"':name(x) '"' -end) - --- macro stack manipulation - - ---- push a value onto a given macro' stack. --- @macro _PUSH_ --- @param mac existing macro name --- @param V a string -M.define('_PUSH_(mac,V)',function(mac,V) - M.push_macro_stack(mac:get_string(),V:get_string()) -end) - ---- pop a value from a macro's stack. --- @macro _POP_ --- @param mac existing macro name --- @return a string --- @see _PUSH_ -M.define('_POP_',function(get,put) - local val = M.pop_macro_stack(get:string()) - if val then - return put(val) - end -end) - ---- drop the top of a macro's stack. --- Like `_POP_`, except that it does not return the value --- @macro _DROP_ --- @return existing macro name --- @see _POP_ -M.define('_DROP_',function(get) - M.pop_macro_stack(get:string()) -end) - ---- Load a Lua module immediately. This allows macro definitions to --- to be loaded before the rest of the file is parsed. --- If the module returns a function, then this is assumed to be a --- substitution function, allowing macro modules to insert code --- at this point. --- @macro require_ -M.define('require_',function(get,put) - local name = get:string() - local ok,fn = pcall(require,name) - if not ok then - fn = require('macro.'..name) - end - if type(fn) == 'function' then - return fn(get,put) - end -end) - ---- Include the contents of a file. This inserts the file directly --- into the token stream, and is equivalent to cpp's `#include` directive. --- @macro include_ -M.define('include_',function(get) - local str = get:string() - local f = M.assert(io.open(str)) - local txt = f:read '*a' - f:close() - M.push_substitution(txt) -end) - diff --git a/tools/LuaMacro/macro/clexer.lua b/tools/LuaMacro/macro/clexer.lua deleted file mode 100644 index fd859a8..0000000 --- a/tools/LuaMacro/macro/clexer.lua +++ /dev/null @@ -1,169 +0,0 @@ ---[[--- A C lexical scanner using LPeg. -= CREDITS -= based on the C lexer in Peter Odding's lua-lxsh -@module macro.clexer ---]] - -local clexer = {} -local lpeg = require 'lpeg' -local P, R, S, C, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cc, lpeg.Ct - --- create a pattern which captures the lua value [id] and the input matching --- [patt] in a table -local function token(id, patt) return Ct(Cc(id) * C(patt)) end - --- private interface -local table_of_tokens -local extra_tokens - -function clexer.add_extra_tokens(extra) - extra_tokens = extra_tokens or {} - for _,t in ipairs(extra) do - table.insert(extra_tokens,t) - end - table_of_tokens = nil -- re-initialize -end - -function clexer.init () - local digit = R('09') - - local upp, low = R'AZ', R'az' - local oct, dec = R'07', R'09' - local hex = dec + R'AF' + R'af' - local letter = upp + low - local alnum = letter + dec + '_' - local endline = S'\r\n\f' - local newline = '\r\n' + endline - local escape = '\\' * ( newline - + S'\\"\'?abfnrtv' - + (#oct * oct^-3) - + ('x' * #hex * hex^-2)) - - - -- range of valid characters after first character of identifier - local idsafe = R('AZ', 'az', '\127\255') + P '_' - - -- operators - local OT = P '==' - if extra_tokens then - for _,ex in ipairs(extra_tokens) do - OT = OT + P(ex) - end - end - local operator = token('operator', OT + P '.' + P'>>=' + '<<=' + '--' + '>>' + '>=' + '/=' + '==' + '<=' - + '+=' + '<<' + '*=' + '++' + '&&' + '|=' + '||' + '!=' + '&=' + '-=' - + '^=' + '%=' + '->' + S',)*%+&(-~/^]{}|.[>!?:=<;') - -- identifiers - local ident = token('iden', idsafe * (idsafe + digit) ^ 0) - - -- keywords - local keyword = token('keyword', (P 'auto' + P 'break' + P 'case' + P'char' + - P 'const' + P 'continue' + P 'default' + - P 'do' + P 'double' + P 'else' + P 'enum' + P 'extern' + P 'float' + - P 'for' + P 'goto' + P 'if' + P 'int' + P 'long' + P 'register' + - P 'return' + P 'short' + P 'signed' + P 'sizeof' + P 'static' + - P 'struct' + P 'switch' + P 'typedef' + P 'union' + P 'void' + - P 'volatile' + P 'while') * -(idsafe + digit)) - - -- numbers - local number_sign = S'+-'^-1 - local number_decimal = digit ^ 1 - local number_hexadecimal = P '0' * S 'xX' * R('09', 'AF', 'af') ^ 1 - local number_float = (digit^1 * P'.' * digit^0 + P'.' * digit^1) * - (S'eE' * number_sign * digit^1)^-1 - local number = token('number', number_hexadecimal + - number_float + - number_decimal) - - - local string = token('string', '"' * ((1 - S'\\\r\n\f"') + escape)^0 * '"') - local char = token('char',"'" * ((1 - S"\\\r\n\f'") + escape) * "'") - - -- comments - local singleline_comment = P '//' * (1 - S '\r\n\f') ^ 0 - local multiline_comment = '/*' * (1 - P'*/')^0 * '*/' - local comment = token('comment', multiline_comment + singleline_comment) - local prepro = token('prepro',P '#' * (1 - S '\r\n\f') ^ 0) - - -- whitespace - local whitespace = token('space', S('\r\n\f\t ')^1) - - -- ordered choice of all tokens and last-resort error which consumes one character - local any_token = whitespace + number + keyword + ident + - string + char + comment + prepro + operator + token('error', 1) - - - table_of_tokens = Ct(any_token ^ 0) -end - --- increment [line] by the number of line-ends in [text] -local function sync(line, text) - local index, limit = 1, #text - while index <= limit do - local start, stop = text:find('\r\n', index, true) - if not start then - start, stop = text:find('[\r\n\f]', index) - if not start then break end - end - index = stop + 1 - line = line + 1 - end - return line -end -clexer.sync = sync - -clexer.line = 0 - --- we only need to synchronize the line-counter for these token types -local multiline_tokens = { comment = true, space = true } -clexer.multiline_tokens = multiline_tokens - -function clexer.scan_c_tokenlist(input) - if not table_of_tokens then - clexer.init() - end - assert(type(input) == 'string', 'bad argument #1 (expected string)') - local line = 1 - local tokens = lpeg.match(table_of_tokens, input) - for i, token in pairs(tokens) do - local t = token[1] - if t == 'operator' or t == 'error' then - token[1] = token[2] - end - token[3] = line - if multiline_tokens[t] then - line = sync(line, token[2]) - end - end - return tokens -end - ---- get a token iterator from a source containing Lua code. --- S is the source - can be a string or a file-like object (i.e. read() returns line) --- Note that this token iterator includes spaces and comments, and does not convert --- string and number tokens - so e.g. a string token is quoted and a number token is --- an unconverted string. -function clexer.scan_c(input,name) - if type(input) ~= 'string' and input.read then - input = input:read('*a') - end - local tokens = clexer.scan_c_tokenlist(input) - local i, n = 1, #tokens - return function(k) - if k ~= nil then - k = i + k - if k < 1 or k > n then return nil end - return tokens[k] - end - local tok = tokens[i] - i = i + 1 - if tok then - clexer.line = tok[3] - clexer.name = name - return tok[1],tok[2] - end - end - -end - -return clexer diff --git a/tools/LuaMacro/macro/do.lua b/tools/LuaMacro/macro/do.lua deleted file mode 100644 index 45cf84c..0000000 --- a/tools/LuaMacro/macro/do.lua +++ /dev/null @@ -1,75 +0,0 @@ ---- An intelligent 'loop-unrolling' macro. --- `do_` defines a named scoped macro `var` which is the loop iterator. --- --- For example, --- --- y = 0 --- do_(i,1,10 --- y = y + i --- ) --- assert(y == 55) --- --- `tuple` is an example of how the expansion of a macro can be --- controlled by its context. Normally a tuple `A` expands to --- `A_1,A_2,A_3` but inside `do_` it works element-wise: --- --- tuple(3) A,B --- def_ do3(stmt) do_(k,1,3,stmt) --- do3(A = B/2) --- --- This expands as --- --- A_1 = B_1/2 --- A_2 = B_2/2 --- A_3 = B_3/2 --- --- @module macro.do -local M = require 'macro' - ---- Expand a loop inline. --- @p var the loop variable --- @p start initial value of `var` --- @p finish final value of `var` --- @p stat the statement containing `var` --- @macro do_ -M.define('do_(v,s,f,stat)',function(var,start,finish,statements) - -- macros with specified formal args have to make their own putter, - -- and convert the actual arguments to the type they expect. - local put = M.Putter() - var,start,finish = var:get_iden(),start:get_number(),finish:get_number() - M.push_macro_stack('do_',var) - -- 'do_' works by setting the variable macro for each value - for i = start, finish do - put:name 'set_':name(var):number(i):space() - put:tokens(statements) - end - put:name 'undef_':name(var) - put:name '_DROP_':string 'do_':space() - return put -end) - ---- an example of conditional expansion. --- `tuple` takes a list of variable names, like a declaration list except that it --- must end with a line end. --- @macro tuple -M.define('tuple',function(get) - get:expecting '(' - local N = get:number() - get:expecting ')' - local names = get:names '\n' - for _,name in ipairs(names) do - M.define(name,function(get,put) - local loop_var = M.value_of_macro_stack 'do_' - if loop_var then - local loop_idx = tonumber(M.get_macro_value(loop_var)) - return put:name (name..'_'..loop_idx) - else - local out = {} - for i = 1,N do - out[i] = name..'_'..i - end - return put:names(out) - end - end) - end -end) diff --git a/tools/LuaMacro/macro/forall.lua b/tools/LuaMacro/macro/forall.lua deleted file mode 100644 index 8ec5c68..0000000 --- a/tools/LuaMacro/macro/forall.lua +++ /dev/null @@ -1,70 +0,0 @@ --------------------- --- `forall` statement. --- The syntax is `forall VAR SELECT [if CONDN] do` where --- `SELECT` is either `in TBL` or `= START,FINISH` --- --- For example, --- --- forall name in {'one','two'} do print(name) end --- --- forall obj in get_objects() if obj:alive() then --- obj:action() --- end --- --- Using `forall`, we also define _list comprehensions_ like --- `L{s:upper() | s in names if s:match '%S+'}` --- --- @module macro.forall - -local M = require 'macro' - ---- extended for statement. --- @macro forall -M.define('forall',function(get,put) - local var = get:iden() - local t,v = get:next() - local rest,endt = get:list(M.upto_keywords('do','if')) - put:keyword 'for' - if v == 'in' then - put:iden '_' ',' :iden(var):keyword 'in' - put:iden 'ipairs' '(' :list(rest) ')' - elseif v == '=' then - put:iden(var) '=' :list(rest) - else - M.error("expecting in or =") - end - put:keyword 'do' - if endt[2] == 'if' then - rest,endt = get:list(M.upto_keywords('do')) - put:keyword 'if':list(rest):keyword 'then':iden '_END_END_' - end - return put -end) - ---- list comprehension. --- Syntax is `L{expr | select}` where `select` is as in `forall`, --- or `L{expr for select}` where `select` is as in the regular `for` statement. --- @macro L --- @return a list of values --- @usage L{2*x | x in {1,2,3}} == {1,4,9} --- @usage L{2*x|x = 1,3} == {1,4,9} --- @usage L{{k,v} for k,v in pairs(t)} --- @see forall -M.define('L',function(get,put) - local t,v = get:next() -- must be '{' - local expr,endt = get:list(function(t,v) - return t == '|' or t == 'keyword' and v == 'for' - end,'') - local select = get:list('}','') - put '(' : keyword 'function' '(' ')' :keyword 'local':iden 'res' '=' '{' '}' - if endt[2] == '|' then - put:iden'forall' - else - put:keyword 'for' - end - put:list(select):space():keyword'do' - put:iden'res' '[' '#' :iden'res' '+' :number(1) ']' '=' :list(expr):space() - put:keyword 'end' :keyword 'return' : iden 'res' :keyword 'end' ')' '(' ')' - put:iden '_POP_':string'L' - return put -end) diff --git a/tools/LuaMacro/macro/ifelse.lua b/tools/LuaMacro/macro/ifelse.lua deleted file mode 100644 index 3d5a0df..0000000 --- a/tools/LuaMacro/macro/ifelse.lua +++ /dev/null @@ -1,90 +0,0 @@ -local M = require 'macro' - -local function eval (expr,was_expr) - expr = tostring(expr) - if was_expr then expr = "return "..expr end - local chunk = M.assert(loadstring(expr)) - local ok, res = pcall(chunk) - if not ok then M.error("error evaluating "..res) end - return res -end - -local function eval_line (get,was_expr) - local args = get:line() - return eval(args,was_expr) -end - -local function grab (get) - local ilevel = 0 - while true do - local t,v = get() - while t ~= '@' do t = get() end - t,v = get() - if v == 'if' then - ilevel = ilevel + 1 - else -- 'end','elseif','else' - if ilevel > 0 and v == 'end' then - ilevel = ilevel - 1 - elseif ilevel == 0 then return '@'..v end - end - end -end - -M.define('@',function(get,put) - local t,v = get() ---~ print('got',t,v) - return put:iden(v..'_') -end) - -local ifstack,push,pop = {},table.insert,table.remove - -local function push_if (res) ---~ print 'push' - push(ifstack, not (res==false or res==nil)) -end - -local function pop_if () ---~ print 'pop' - pop(ifstack) -end - -M.define('if_',function(get) - local res = eval_line(get,true) - push_if(res) - if not res then - return grab(get) - end -end) - -M.define('elseif_',function(get) - local res - if ifstack[#ifstack] then - res = false - else - res = eval_line(get,true) - pop_if() - push_if(res) - end - if not res then - return grab(get) - end -end) - -M.define('else_',function(get) - if #ifstack == 0 then M.error("mismatched else") end - if ifstack[#ifstack] then - return grab(get) - end -end) - -M.define('end_',function(get) - pop_if() -end) - -M.define('let_',function(get) - eval_line(get) -end) - -M.define('eval_(X)',function(X) - return tostring(eval(X,true)) -end) diff --git a/tools/LuaMacro/macro/lambda.lua b/tools/LuaMacro/macro/lambda.lua deleted file mode 100644 index 677f997..0000000 --- a/tools/LuaMacro/macro/lambda.lua +++ /dev/null @@ -1,22 +0,0 @@ ---- Short anonymous functions (lambdas). --- This syntax is suited --- to any naive token-processor because the payload is always inside parens. --- It is an example of a macro associated with a 'operator' character. --- --- Syntax is `\()` --- --- `\x(x+10)` is short for --- `function(x) return x+10 end`. There may be a number of formal argumets, --- e.g. `\x,y(x+y)` or there may be none, e.g. `\(somefun())`. Such functions --- may return multiple values, e.g `\x(x+1,x-1)`. --- --- @module macro.lambda - -local M = require 'macro' - -M.define ('\\',function(get,put) - local args, body = get:idens('('), get:list() - return put:keyword 'function' '(' : idens(args) ')' : - keyword 'return' : list(body) : space() : keyword 'end' -end) - diff --git a/tools/LuaMacro/macro/lc.lua b/tools/LuaMacro/macro/lc.lua deleted file mode 100644 index 0d2968d..0000000 --- a/tools/LuaMacro/macro/lc.lua +++ /dev/null @@ -1,343 +0,0 @@ --- Simplifying writing C extensions for Lua --- Adds new module and class constructs; --- see class1.lc and str.lc for examples. -local M = require 'macro' - -function dollar_subst(s,tbl) - return (s:gsub('%$%((%a+)%)',tbl)) -end - --- reuse some machinery from the C-skin experiments -local push,pop = table.insert,table.remove -local bstack,btop = {},{} - -local function push_brace_stack (newv) - newv = newv or {} - newv.lev = 0 - push(bstack,btop) - btop = newv -end - -M.define('{',function() - if btop.lev then - btop.lev = btop.lev + 1 - end - return nil,true --> pass-through macro -end) - -M.define('}',function(get,put) - if not btop.lev then - return nil,true - elseif btop.lev == 0 then - local res - if btop.handler then res = btop.handler(get,put) end - if not res then res = put:space() '}' end - btop = pop(bstack) - return res - else - btop.lev = btop.lev - 1 - return nil,true --> pass-through macro - end -end) - ---------- actual implementation begins ------- - -local append = table.insert -local module - -local function register_functions (names,cnames) - local out = {} - for i = 1,#names do - append(out,(' {"%s",l_%s},'):format(names[i],cnames[i])) - end - return table.concat(out,'\n') -end - -local function finalizers (names) - local out = {} - for i = 1,#names do - append(out,names[i].."(L);") - end - return table.concat(out,'\n') -end - -local typedefs - -local preamble = [[ -#include -#include -#include -#ifdef WIN32 -#define EXPORT __declspec(dllexport) -#else -#define EXPORT -#endif -#if LUA_VERSION_NUM > 501 -#define lua_objlen lua_rawlen -#endif -]] - -local finis = [[ -static const luaL_Reg $(cname)_funs[] = { - $(funs) - {NULL,NULL} -}; - -EXPORT int luaopen_$(cname) (lua_State *L) { -#if LUA_VERSION_NUM > 501 - lua_newtable(L); - luaL_setfuncs (L,$(cname)_funs,0); - lua_pushvalue(L,-1); - lua_setglobal(L,"$(cname)"); -#else - luaL_register(L,"$(cname)",$(cname)_funs); -#endif - $(finalizers) - return 1; -} -]] - -M.define('module',function(get) - local name = get:string() - local cname = name:gsub('%.','_') - get:expecting '{' - local out = preamble .. typedefs - push_brace_stack{ - name = name, cname = cname, - names = {}, cnames = {}, finalizers = {}, - handler = function() - local out = {} - local funs = register_functions(btop.names,btop.cnames) - local final = finalizers(btop.finalizers) - append(out,dollar_subst(finis, { - cname = cname, - name = name, - funs = funs, - finalizers = final - })) - return table.concat(out,'\n') - end } - module = btop - return out -end) - - -M.define('def',function(get) - local fname = get:name() - local cname = (btop.ns and btop.ns..'_' or '')..fname - append(btop.names,fname) - append(btop.cnames,cname) - get:expecting '(' - local args = get:list():strip_spaces() - get:expecting '{' - local t,space = get() - indent = space:gsub('^%s*[\n\r]',''):gsub('%s$','') - local out = {"static int l_"..cname.."(lua_State *L) {"} - if btop.massage_arg then - btop.massage_arg(args) - end - for i,arg in ipairs(args) do - local mac = arg[1][2]..'_init' - if arg[3] and arg[3][1] == '=' then - mac = mac .. 'o' - i = i .. ',' .. arg[4][2] - end - if not arg[2] then M.error("parameter must be TYPE NAME [= VALUE]") end - append(out,indent..mac..'('..arg[2][2]..','..i..');') - end - --append(out,space) - return table.concat(out,'\n')..space -end) - -M.define('constants',function(get,put) - get:expecting '{' - local consts = get:list '}' :strip_spaces() - --for k,v in pairs(btop) do io.stderr:write(k,'=',tostring(v),'\n') end - -- os.exit() - local fname = 'set_'..btop.cname..'_constants' - local out = { 'static void '..fname..'(lua_State *L) {'} - if not btop.finalizers then M.error("not inside a module") end - append(btop.finalizers,fname) - for _,c in ipairs(consts) do - local type,value,name - if #c == 1 then -- a simple int constant: CONST - name = c:pick(1) - type = 'Int' - value = name - else -- Type CONST [ = VALUE ] - type = c:pick(1) - name = c:pick(2) - if #c == 2 then - value = name - else - value = c:pick(4) - end - end - append(out,('%s_set("%s",%s);'):format(type,name,value )) - end - append(out,'}') - return table.concat(out,'\n') -end) - -M.define('assign',function(get) - get:expecting '{' - local asses = get:list '}' :strip_spaces() - local out = {} - for _,c in ipairs(asses) do - append(out,('%s_set("%s",%s);\n'):format(c:pick(1),c:pick(2),c:pick(4)) ) - end - return table.concat(out,'\n') -end) - -local load_lua = [[ -static void load_lua_code (lua_State *L) { - luaL_dostring(L,lua_code_block); -} -]] - -M.define('lua',function(get) - get:expecting '{' - local block = tostring(get:upto '}') - local code_name = 'lua_code_block' - local out = {'static const char *'.. code_name .. ' = ""\\'} - for line in block:gmatch('([^\r\n]+)') do - line = line:gsub('\\','\\\\'):gsub('"','\\"') - append(out,' "'..line..'\\n"\\') - end - append(out,';') - append(out,load_lua) - out = table.concat(out,'\n') - append(module.finalizers,'load_lua_code') - return out -end) - -typedefs = [[ -typedef const char *Str; -typedef const char *StrNil; -typedef int Int; -typedef double Number; -typedef int Boolean; -]] - - -M.define 'Str_init(var,idx) const char *var = luaL_checklstring(L,idx,NULL)' -M.define 'Str_inito(var,idx,val) const char *var = luaL_optlstring(L,idx,val,NULL)' -M.define 'Str_set(name,value) lua_pushstring(L,value); lua_setfield(L,-2,name)' -M.define 'Str_get(var,name) lua_getfield(L,-1,name); var=lua_tostring(L,-1); lua_pop(L,1)' -M.define 'Str_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tostring(L,-1); lua_pop(L,1)' - -M.define 'StrNil_init(var,idx) const char *var = lua_tostring(L,idx)' - -M.define 'Int_init(var,idx) int var = luaL_checkinteger(L,idx)' -M.define 'Int_inito(var,idx,val) int var = luaL_optinteger(L,idx,val)' -M.define 'Int_set(name,value) lua_pushinteger(L,value); lua_setfield(L,-2,name)' -M.define 'Int_get(var,name) lua_getfield(L,-1,name); var=lua_tointeger(L,-1); lua_pop(L,1)' -M.define 'Int_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tointeger(L,-1); lua_pop(L,1)' - -M.define 'Number_init(var,idx) double var = luaL_checknumber(L,idx)' -M.define 'Number_inito(var,idx,val) double var = luaL_optnumber(L,idx,val)' -M.define 'NUmber_set(name,value) lua_pushnumber(L,value); lua_setfield(L,-2,name)' -M.define 'Number_get(var,name) lua_getfield(L,-1,name); var=lua_tonumber(L,-1); lua_pop(L,1)' -M.define 'Number_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tonumber(L,-1); lua_pop(L,1)' - -M.define 'Boolean_init(var,idx) int var = lua_toboolean(L,idx)' -M.define 'Boolean_set(name,value) lua_pushboolean(L,value); lua_setfield(L,-2,name)' -M.define 'Boolean_get(var,name) lua_getfield(L,-1,name); var=lua_toboolean(L,-1); lua_pop(L,1)' -M.define 'Boolean_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_toboolean(L,-1); lua_pop(L,1)' - -M.define 'Value_init(var,idx) int var = idx' - -M.define('lua_tests',function(get) - get:expecting '{' - local body = get:upto '}' - local f = io.open(M.filename..'.lua','w') - f:write(tostring(body)) - f:close() -end) - ------- class support ---------------------- - -local klass_ctor = "static void $(klass)_ctor(lua_State *L, $(klass) *this, $(fargs))" - -local begin_klass = [[ - -typedef struct { - $(fields) -} $(klass); - -define_ $(klass)_init(var,idx) $(klass) *var = $(klass)_arg(L,idx) - -#define $(klass)_MT "$(klass)" - -$(klass) * $(klass)_arg(lua_State *L,int idx) { - $(klass) *this = ($(klass) *)luaL_checkudata(L,idx,$(klass)_MT); - luaL_argcheck(L, this != NULL, idx, "$(klass) expected"); - return this; -} - -$(ctor); - -static int push_new_$(klass)(lua_State *L,$(fargs)) { - $(klass) *this = ($(klass) *)lua_newuserdata(L,sizeof($(klass))); - luaL_getmetatable(L,$(klass)_MT); - lua_setmetatable(L,-2); - $(klass)_ctor(L,this,$(aargs)); - return 1; -} - -]] - -local end_klass = [[ - -static const struct luaL_Reg $(klass)_methods [] = { - $(methods) - {NULL, NULL} /* sentinel */ -}; - -static void $(klass)_register (lua_State *L) { - luaL_newmetatable(L,$(klass)_MT); -#if LUA_VERSION_NUM > 501 - luaL_setfuncs(L,$(klass)_methods,0); -#else - luaL_register(L,NULL,$(klass)_methods); -#endif - lua_pushvalue(L,-1); - lua_setfield(L,-2,"__index"); - lua_pop(L,1); -} -]] - -M.define('class',function(get) - local name = get:iden() - get:expecting '{' - local fields = get:upto (function(t,v) - return t == 'iden' and v == 'constructor' - end) - fields = tostring(fields):gsub('%s+$','\n') - get:expecting '(' - local out = {} - local args = get:list() - local f_args = args:strip_spaces() - local a_args = f_args:pick(2) - f_args = table.concat(args:__tostring(),',') - a_args = table.concat(a_args,',') - local subst = {klass=name,fields=fields,fargs=f_args,aargs=a_args } - local proto = dollar_subst(klass_ctor,subst) - subst.ctor = proto - append(out,dollar_subst(begin_klass,subst)) - append(out,proto) - local pp = {{'iden',name},{'iden','this'}} - push_brace_stack{ - names = {}, cnames = {}, ns = name, cname = name, - massage_arg = function(args) - table.insert(args,1,pp) - end, - handler = function(get,put) - append(module.finalizers,name.."_register") - local methods = register_functions(btop.names,btop.cnames) - return dollar_subst(end_klass,{methods=methods,klass=name,fargs=f_args,aargs=a_args}) - end - } - return table.concat(out,'\n') -end) - diff --git a/tools/LuaMacro/macro/lexer.lua b/tools/LuaMacro/macro/lexer.lua deleted file mode 100644 index 58ab53a..0000000 --- a/tools/LuaMacro/macro/lexer.lua +++ /dev/null @@ -1,179 +0,0 @@ ---[[--- A Lua lexical scanner using LPeg. -= CREDITS -Written by Peter Odding, 2007/04/04 - -= THANKS TO -- the Lua authors for a wonderful language; -- Roberto for LPeg; -- caffeine for keeping me awake :) - -= LICENSE -Shamelessly ripped from the SQLite[3] project: - - The author disclaims copyright to this source code. In place of a legal - notice, here is a blessing: - - May you do good and not evil. - May you find forgiveness for yourself and forgive others. - May you share freely, never taking more than you give. - -@module macro.lexer ---]] - -local lexer = {} -local lpeg = require 'lpeg' -local P, R, S, C, Cb, Cc, Cg, Cmt, Ct = - lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cb, lpeg.Cc, lpeg.Cg, lpeg.Cmt, lpeg.Ct - --- create a pattern which captures the lua value [id] and the input matching --- [patt] in a table -local function token(id, patt) return Ct(Cc(id) * C(patt)) end - --- private interface -local table_of_tokens -local extra_tokens - -function lexer.add_extra_tokens(extra) - extra_tokens = extra_tokens or {} - for _,t in ipairs(extra) do - table.insert(extra_tokens,t) - end - table_of_tokens = nil -- re-initialize -end - -function lexer.init () - local digit = R('09') - - -- range of valid characters after first character of identifier - --local idsafe = R('AZ', 'az', '\127\255') + P '_' - local idsafe = R('AZ', 'az') + P '_' + R '\206\223' * R '\128\255' - -- operators - local OT = P '==' - if extra_tokens then - for _,ex in ipairs(extra_tokens) do - OT = OT + P(ex) - end - end - local operator = token('operator', OT + P '.' + P '~=' + P '<=' + P '>=' + P '...' - + P '..' + S '+-*/%^#=<>;:,.{}[]()') - -- identifiers - local ident = token('iden', idsafe * (idsafe + digit) ^ 0) - - -- keywords - local keyword = token('keyword', (P 'and' + P 'break' + P 'do' + P 'elseif' + - P 'else' + P 'end' + P 'false' + P 'for' + P 'function' + P 'if' + - P 'in' + P 'local' + P 'nil' + P 'not' + P 'or' + P 'repeat' + P 'return' + - P 'then' + P 'true' + P 'until' + P 'while') * -(idsafe + digit)) - - -- numbers - local number_sign = S'+-'^-1 - local number_decimal = digit ^ 1 - local number_hexadecimal = P '0' * S 'xX' * R('09', 'AF', 'af') ^ 1 - local number_float = (digit^1 * P'.' * digit^0 + P'.' * digit^1) * - (S'eE' * number_sign * digit^1)^-1 - local number = token('number', number_hexadecimal + - number_float + - number_decimal) - - -- callback for [=[ long strings ]=] - -- ps. LPeg is for Lua what regex is for Perl, which makes me smile :) - local equals = P '=' ^ 0 - local open = P '[' * Cg(equals, "init") * P '[' * P '\n' ^ -1 - local close = P ']' * C(equals) * P ']' - local closeeq = Cmt(close * Cb "init", function (s, i, a, b) return a == b end) - local longstring = open * C((P(1) - closeeq)^0) * close --/ 1 - - -- strings - local singlequoted_string = P "'" * ((1 - S "'\r\n\f\\") + (P '\\' * 1)) ^ 0 * "'" - local doublequoted_string = P '"' * ((1 - S '"\r\n\f\\') + (P '\\' * 1)) ^ 0 * '"' - local string = token('string', singlequoted_string + - doublequoted_string + - longstring) - - -- comments - local singleline_comment = P '--' * (1 - S '\r\n\f') ^ 0 - local multiline_comment = P '--' * longstring - local comment = token('comment', multiline_comment + singleline_comment) - - -- whitespace - local whitespace = token('space', S('\r\n\f\t ')^1) - - -- ordered choice of all tokens and last-resort error which consumes one character - local any_token = whitespace + number + keyword + ident + - string + comment + operator + token('error', 1) - - - table_of_tokens = Ct(any_token ^ 0) -end - --- increment [line] by the number of line-ends in [text] -local function sync(line, text) - local index, limit = 1, #text - while index <= limit do - local start, stop = text:find('\r\n', index, true) - if not start then - start, stop = text:find('[\r\n\f]', index) - if not start then break end - end - index = stop + 1 - line = line + 1 - end - return line -end -lexer.sync = sync - -lexer.line = 0 - --- we only need to synchronize the line-counter for these token types -local multiline_tokens = { comment = true, string = true, space = true } -lexer.multiline_tokens = multiline_tokens - -function lexer.scan_lua_tokenlist(input) - if not table_of_tokens then - lexer.init() - end - assert(type(input) == 'string', 'bad argument #1 (expected string)') - local line = 1 - local tokens = lpeg.match(table_of_tokens, input) - for i, token in pairs(tokens) do - local t = token[1] - if t == 'operator' or t == 'error' then - token[1] = token[2] - end - token[3] = line - if multiline_tokens[t] then - line = sync(line, token[2]) - end - end - return tokens -end - ---- get a token iterator from a source containing Lua code. --- Note that this token iterator includes spaces and comments, and does not convert --- string and number tokens - so e.g. a string token is quoted and a number token is --- an unconverted string. --- @param input the source - can be a string or a file-like object (i.e. read() returns line) --- @param name for the source -function lexer.scan_lua(input,name) - if type(input) ~= 'string' and input.read then - input = input:read('*a') - end - local tokens = lexer.scan_lua_tokenlist(input) - local i, n = 1, #tokens - return function(k) - if k ~= nil then - k = i + k - if k < 1 or k > n then return nil end - return tokens[k] - end - local tok = tokens[i] - i = i + 1 - if tok then - lexer.line = tok[3] - lexer.name = name - return tok[1],tok[2] - end - end -end - -return lexer diff --git a/tools/LuaMacro/macro/lib/class.lua b/tools/LuaMacro/macro/lib/class.lua deleted file mode 100644 index f762f36..0000000 --- a/tools/LuaMacro/macro/lib/class.lua +++ /dev/null @@ -1,35 +0,0 @@ ----- --- a basic class mechanism. --- Used for some of the demonstrations; the `class` macro in the `module` --- package uses it. It provides a single function which returns a new 'class'. --- The resulting object can be called to generate an instance of the class. --- You may provide a base class for single inheritance; in this case, the functions --- of the base class will be copied into the new class' metatable (so-called 'fat metatable') --- --- Example: --- --- local class = require 'macro.lib.class' --- A = class() --- function A._init(name) self.name = name end --- a = A("hello") --- assert(a.name == "hello") --- --- @module macro.lib.class - -return function (base) - -- OOP with single inheritance - local klass,cmt = {},{} - if base then -- 'fat metatable' inheritance - for k,v in pairs(base) do klass[k] = v end - end - klass.__index = klass - -- provide a callable constructor that invokes user-supplied ctor - function cmt:__call(...) - local obj = setmetatable({},klass) - if klass._init then klass._init(obj,...) - elseif base and base._init then base._init(base,...) end - return obj - end - setmetatable(klass,cmt) - return klass -end diff --git a/tools/LuaMacro/macro/lib/test.lua b/tools/LuaMacro/macro/lib/test.lua deleted file mode 100644 index 5fff39e..0000000 --- a/tools/LuaMacro/macro/lib/test.lua +++ /dev/null @@ -1,144 +0,0 @@ ---- `assert_` macro library support. --- This module may of course be used on its own; `assert_` merely provides --- some syntactical sugar for its functionality. It is based on Penlight's --- `pl.test` module. --- @module macro.libs.test - -local test = {} - -local _eq,_tostring - --- very much like tablex.deepcompare from Penlight -function _eq (v1,v2) - if type(v1) ~= type(v2) then return false end - -- if the value isn't a table, or it has defined the equality operator.. - local mt = getmetatable(v1) - if (mt and mt.__eq) or type(v1) ~= 'table' then - return v1 == v2 - end - -- both values are plain tables - if v1 == v2 then return true end -- they were the same table... - for k1,x1 in pairs(v1) do - local x2 = v2[k1] - if x2 == nil or not _eq(x1,x2) then return false end - end - for k2,x2 in pairs(v2) do - local x1 = v1[k2] - if x1 == nil or not _eq(x1,x2) then return false end - end - return true -end - -local function keyv (k) - if type(k) ~= 'string' then - k = '['..k..']' - end - return k -end - -function _tostring (val) - local mt = getmetatable(val) - if (mt and mt.__tostring) or type(val) ~= 'table' then - if type(val) == 'string' then - return '"'..tostring(val)..'"' - else - return tostring(val) - end - end - -- dump the table; doesn't need to be pretty! - local res = {} - local function put(s) res[#res+1] = s end - put '{' - for k,v in pairs(val) do - put(keyv(k)..'=') - put(_tostring(v)) - put ',' - end - table.remove(res) -- remove last ',' - put '}' - return table.concat(res) -end - -local function _lt (v1,v2) return v1 < v2 end -local function _gt (v1,v2) return v1 > v2 end -local function _match (v1,v2) return v1:match(v2) end - -local function _assert (v1,v2,cmp,msg) - if not cmp(v1,v2) then - print('first:',_tostring(v1)) - print(msg) - print('second:',_tostring(v2)) - error('assertion failed',3) - end -end - ---- assert if parameters are not equal. If the values are tables, --- they will be compared by value. --- @param v1 given value --- @param v2 test value -function test.assert_eq (v1,v2) - _assert(v1,v2,_eq,"is not equal to"); -end - ---- assert if first parameter is not less than second. --- @param v1 given value --- @param v2 test value -function test.assert_lt (v1,v2) - _assert(v1,v2,_lt,"is not less than") -end - ---- assert if first parameter is not greater than second. --- @param v1 given value --- @param v2 test value -function test.assert_gt (v1,v2) - _assert(v1,v2,_gt,"is not greater than") -end - ---- assert if first parameter string does not match the second. --- The condition is `v1:match(v2)`. --- @param v1 given value --- @param v2 test value -function test.assert_match (v1,v2) - _assert(v1,v2,_match,"does not match") -end - --- return the error message from a function that raises an error. --- Will raise an error if the function did not raise an error. --- @param fun the function --- @param ... any arguments to the function --- @return the error message -function test.pcall_no(fun,...) - local ok,err = pcall(fun,...) - if ok then error('expression did not throw error',3) end - return err -end - -local tuple = {} - -function tuple.__eq (a,b) - if a.n ~= b.n then return false end - for i=1, a.n do - if not _eq(a[i],b[i]) then return false end - end - return true -end - -function tuple.__tostring (self) - local ts = {} - for i = 1,self.n do - ts[i] = _tostring(self[i]) - end - return '('..table.concat(ts,',')..')' -end - ---- create a tuple capturing multiple return values. --- Equality between tuples means that all of their values are equal; --- values may be `nil` --- @param ... any values --- @return a tuple object -function test.tuple(...) - return setmetatable({n=select('#',...),...},tuple) -end - -return test - diff --git a/tools/LuaMacro/macro/module.lua b/tools/LuaMacro/macro/module.lua deleted file mode 100644 index a8e1b8d..0000000 --- a/tools/LuaMacro/macro/module.lua +++ /dev/null @@ -1,132 +0,0 @@ ---[[--- -Easy no-fuss modules. - -Any function inside the module will be exported, unless it is explicitly -local. The functions are declared up front using patching, leading to efficient -calls between module functions. - - require_ 'module' - - function one () - return two() - end - - function two () - return 42 - end - -Classes can also be declared inside modules: - - require_ 'module' - - class A - function set(self,val) @val = val end - function get(self) return @val end - end - -Within class definitions, the macro `@` expands to either `self.` or `self:` depending -on context, and provides a Ruby-like shortcut. - -If you give these modules names with `m.lua` extension like `mod.m.lua`, then you can -simply use `require()` to use them with LuaMacro. - -@module macro.module -]] -local M = require 'macro' - -local locals, locals_with_value = {}, {} -local ref - -local function module_add_new_local (name) - locals[#locals+1] = name -end - -local function module_add_new_local_with_value (name,value) - locals_with_value[name] = value -end - - -local function was_local_function (get) - local tl,keyw = get:peek(-1) - return tl=='keyword' and keyw=='local' -end - --- exclude explicitly local functions and anonymous functions. -M.keyword_handler('function',function(get) - local tn,name = get:peek(1) - local was_local = was_local_function(get) - if not was_local and tn == 'iden' then - module_add_new_local(name) - end -end) - --- when the module is closed, this will patch the locals and --- output the module table. -M.keyword_handler('END',function(get) - local concat = table.concat - local patch = '' - if next(locals_with_value) then - local lnames,lvalues = {},{} - local i = 1 - for k,v in pairs(locals_with_value) do - lnames[i] = k - lvalues[i] = v - i = i + 1 - end - patch = patch..'local '..concat(lnames,',')..'='..concat(lvalues,',')..'; ' - end - if #locals > 0 then - patch = patch .. 'local '..concat(locals,',') - end - get:patch(ref,patch) - local dcl = {} - for i,name in ipairs(locals) do - dcl[i] = name..'='..name - end - dcl = table.concat(dcl,', ') - return 'return {' .. dcl .. '}' -end) - -local no_class_require - --- the meaning of @f is either 'self.f' for fields, or 'self:f' for methods. -local function at_handler (get,put) - local tn,name,tp = get:peek2(1) - M.assert(tn == 'iden','identifier must follow @') - return put:iden ('self',true) (tp=='(' and ':' or '.') -end - -local function method_handler (get,put) - local tn,name,tp = get:peek2() - if not was_local_function(get) and tn == 'iden' and tp == '(' then - return put ' ' :iden ('_C',true) '.' - end -end - -M.define ('_C_',function() - M.define_scoped('@',at_handler) - if not no_class_require then - module_add_new_local_with_value('_class','require "macro.lib.class"') - no_class_require = true - end - M.scoped_keyword_handler('function',method_handler) -end) - -M.define('class',function(get) - local base = '' - local name = get:iden() - if get:peek(1) == ':' then - get:next() - base = get:iden() - end - module_add_new_local(name) - return ('do local _C = _class(%s); %s = _C; _C_\n'):format(base,name) -end) - --- the result of the macro is just a placeholder for the locals -return function(get,put) - ref = get:placeholder(put) - return put -end - - diff --git a/tools/LuaMacro/macro/try.lua b/tools/LuaMacro/macro/try.lua deleted file mode 100644 index 8e49eb0..0000000 --- a/tools/LuaMacro/macro/try.lua +++ /dev/null @@ -1,47 +0,0 @@ ---- A try/except block. --- This generates syntactical sugar around `pcal`l, and correctly --- distinguishes between the try block finishing naturally and --- explicitly using 'return' with no value. This is handled by --- converting any no value `return` to `return nil`. --- --- Apart from the usual creation of a closure, this uses a table --- to capture all the results. Not likely to win speed contests, --- but intended to be correct. --- @module macro.try - -local M = require 'macro' - -local function pack (...) - local args = {...} - args.n = select('#',...) - return args -end - -function pcall_(fn,...) - return pack(pcall(fn,...)) -end - -local function check_return_value(get,put) - local t,v = get:next() - put:space() - if t=='keyword' and (v=='end' or v=='else' or v=='until') then - put:keyword 'nil' - end - return put(t,v) -end - - -M.define('RR_',M.make_scoped_handler('return',check_return_value)) - - ---- A try macro, paired with except. --- --- try --- maybe_something_bad() --- except (e) --- print(e) --- end --- @macro try -M.define 'try do local r_ = pcall_(function() RR_ ' -M.define 'except(e) end); if r_[1] then if r_.n > 1 then return unpack(r_,2,r_.n) end else local e = r_[2] _END_END_ ' - diff --git a/tools/LuaMacro/macro/with.lua b/tools/LuaMacro/macro/with.lua deleted file mode 100644 index de51590..0000000 --- a/tools/LuaMacro/macro/with.lua +++ /dev/null @@ -1,31 +0,0 @@ ---[[-- -A `with` statement. This works more like the Visual Basic statement than the -Pascal one; fields have an explicit period to indicate that they are special. -This makes variable scoping explcit. - - aLongTableName = {} - with aLongTableName do - .a = 1 - .b = {{x=1},{x=2}} - .c = {f = 2} - print(.a,.c.f,.b[1].x) - end - -Fields that follow an identifier or a `}` are passed as-is. - -@module macro.with -]] -local M = require 'macro' - -M.define('with',function(get,put) - M.define_scoped('.',function() - local lt,lv = get:peek(-1,true) -- peek before the period... - if lt ~= 'iden' and lt ~= ']' then - return '_var.' - else - return nil,true -- pass through - end - end) - local expr = get:upto 'do' - return 'do local _var = '..tostring(expr)..'; ' -end) diff --git a/tools/LuaMacro/readme.md b/tools/LuaMacro/readme.md deleted file mode 100644 index e86bbfb..0000000 --- a/tools/LuaMacro/readme.md +++ /dev/null @@ -1,1010 +0,0 @@ -## LuaMacro - a macro preprocessor for Lua - -This is a library and driver script for preprocessing and evaluating Lua code. -Lexical macros can be defined, which may be simple C-preprocessor style macros or -macros that change their expansion depending on the context. - -It is a new, rewritten version of the -[Luaforge](http://luaforge.net/projects/luamacro/) project of the same name, which -required the [token filter -patch](http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#tokenf) by Luiz Henrique de -Figueiredo. This patch allowed Lua scripts to filter the raw token stream before -the compiler stage. Within the limits imposed by the lexical filter approach this -worked pretty well. However, the token filter patch is unlikely to ever become -part of mainline Lua, either in its original or -[revised](http://lua-users.org/lists/lua-l/2010-02/msg00325.html) form. So the most -portable option becomes precompilation, but Lua bytecode is not designed to be -platform-independent and in any case changes faster than the surface syntax of the -language. So using LuaMacro with LuaJIT would have required re-applying the patch, -and would remain within the ghetto of specialized, experimental use. - -This implementation uses a [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg.html) -lexical analyser originally by [Peter -Odding](http://lua-users.org/wiki/LpegRecipes) to tokenize Lua source, and builds -up a preprocessed string explicitly, which then can be loaded in the usual way. -This is not as efficient as the original, but it can be used by anyone with a Lua -interpreter, whether it is Lua 5.1, 5.2 or LuaJIT 2. An advantage of fully building -the output is that it becomes much easier to debug macros when you can actually see -the generated results. (Another example of a LPeg-based Lua macro preprocessor is -[Luma](http://luaforge.net/projects/luma/)) - -It is not possible to discuss macros in Lua without mentioning Fabien Fleutot's -[Metalua](metalua.luaforge.net/) which is an alternative Lua compiler which -supports syntactical macros that can work on the AST (Abstract Syntax Tree) itself -of Lua. This is clearly a technically superior way to extend Lua syntax, but again -has the disadvantage of being a direct-to-bytecode compiler. (Perhaps it's also a -matter of taste, since I find it easier to think about extending Lua on the lexical -level.) - -My renewed interest in Lua lexical macros came from some discussions on the Lua -mailing list about numerically optimal Lua code using LuaJIT. We have been spoiled -by modern optimizing C/C++ compilers, where hand-optimization is often discouraged, -but LuaJIT is new and requires some assistance. For instance, unrolling short loops -can make a dramatic difference, but Lua does not provide the key concept of -constant value to assist the compiler. So a very straightforward use of a macro -preprocessor is to provide named constants in the old-fashioned C way. Very -efficient code can be generated by generalizing the idea of 'varargs' into a -statically-compiled 'tuple' type. - - tuple(3) A,B - -The assigment `A = B` is expanded as: - - A_1,A_2,A_3 = B_1,B_2,B_3 - -I will show how the expansion can be made context-sensitive, so that the -loop-unrolling macro `do_` changes this behaviour: - - do_(i,1,3, - A = 0.5*B - ) - -expands to: - - A_1 = 0.5*B_1 - A_2 = 0.5*B_2 - A_3 = 0.5*B_3 - -Another use is crafting DSLs, particularly for end-user scripting. For instance, -people may be more comfortable with `forall x in t do` rather than `for _,x in -ipairs(t) do`; there is less to explain in the first form and it translates -directly to the second form. Another example comes from this common pattern: - - some_action(function() - ... - end) - -Using the following macro: - - def_ block (function() _END_CLOSE_ - -we can write: - - some_action block - ... - end - -A criticism of traditional lexical macros is that they don't respect the scoping -rules of the language itself. Bad experiences with the C preprocessor lead many to -regard them as part of the prehistory of computing. The macros described here can -be lexically scoped, and can be as 'hygenic' as necessary, since their expansion -can be finely controlled with Lua itself. - -For me, a more serious charge against 'macro magic' is that it can lead to a -private dialect of the language (the original Bourne shell was written in C -'skinned' to look like Algol 68.) This often indicates a programmer uncomfortable -with a language, who wants it to look like something more familiar. Relying on a -preprocessor may mean that programmers need to immerse themselves more in the idioms of -the new language. - -That being said, macros can extend a language so that it can be more expressive for -a particular task, particularly if the users are not professional programmers. - -### Basic Macro Substitution - -To install LuaMacro, expand the archive and make a script or batch file that points -to `luam.lua`, for instance: - - lua /home/frodo/luamacro/luam.lua $* - -(Or '%*' if on Windows.) Then put this file on your executable path. - -Any Lua code loaded with `luam` goes through four distinct steps: - - * loading and defining macros - * preprocessing - * compilation - * execution - -The last two steps happen within Lua itself, but always occur, even though the Lua -compiler is fast enough that we mostly do not bother to save the generated bytecode. - -For example, consider this `hello.lua`: - - print(HELLO) - -and `hello-def.lua`: - - local macro = require 'macro' - macro.define 'HELLO "Hello, World!"' - -To run the program: - - $> luam -lhello-def hello.lua - Hello, World! - -So the module `hello-def.lua` is first loaded (compiled and executed, but not -preprocessed) and only then `hello.lua` can be preprocessed and then loaded. - -Naturaly, there are easier ways to use LuaMacro, but I want to emphasize the -sequence of macro loading, preprocessing and script loading. `luam` has a `-d` -flag, meaning 'dump', which is very useful when debugging the output of the -preprocessing step: - - $> luam -d -lhello-def hello.lua - print("Hello, World!") - -`hello2.lua` is a more sensible first program: - - require_ 'hello-def' - print(HELLO) - -You cannot use the Lua `require` function at this point, since `require` is only -executed when the program starts executing and we want the macro definitions to be -available during the current compilation. `require_` is the macro version, which -loads the file at compile-time. - -New with 2.5 is the default @ shortcut available when using `luam`, -so `require_` can be written `@require`. -(`@` is itself a macro, so you can redefine it if needed.) - -There is also `include_/@include`, which is analogous to `#include` in `cpp`. It takes a -file path in quotes, and directly inserts the contents of the file into the current -compilation. Although tempting to use, it will not work here because again the -macro definitions will not be available at compile-time. - -`hello3.lua` fits much more into the C preprocessor paradigm, which uses the `def_` -macro: - - @def HELLO "Hello, World!" - print(HELLO) - -(Like `cpp`, such macro definitions end with the line; however, there is no -equivalent of `\` to extend the definition over multiple lines.) - -With 2.1, an alternative syntax `def_ (name body)` is also available, which can be -embedded inside a macro expression: - - def_ OF_ def_ (of elseif _value ==) - -Or even extend over several lines: - - def_ (complain(msg,n) - for i = 1,n do - print msg - end - ) - -`def_` works pretty much like `#define`, for instance, `def_ SQR(x) ((x)*(x))`. A -number of C-style favourites can be defined, like `assert_` using `_STR_`, which is -a predefined macro that 'stringifies' its argument. - - def_ assert_(condn) assert(condn,_STR_(condn)) - -`def_` macros are _lexically scoped_: - - local X = 1 - if something then - def_ X 42 - assert(X == 42) - end - assert(X == 1) - -LuaMacro keeps track of Lua block structure - in particular it knows when a -particular lexical scope has just been closed. This is how the `_END_CLOSE_` -built-in macro works - - def_ block (function() _END_CLOSE_ - - my_fun block - do_something_later() - end - -When the current scope closes with `end`, LuaMacro appends the necessary ')' to -make this syntax valid. - -A common use of macros in both C and Lua is to inline optimized code for a case. -The Lua function `assert()` always evaluates its second argument, which is not -always optimal: - - def_ ASSERT(condn,expr) if condn then else error(expr) end - - ASSERT(2 == 1,"damn! ".. 2 .." is not equal to ".. 1) - -If the message expression is expensive to execute, then this can give better -performance at the price of some extra code. `ASSERT` is now a statement, not a -function, however. - -### Conditional Compilation - -For this to work consistently, you need to use the `@` shortcut: - - @include 'test.inc' - @def A 10 - ... - -This makes macro 'preprocessor' statements stand out more. Conditional compilation -works as you would expect from C: - - -- test-cond.lua - @if A - print 'A defined' - @else - print 'A not defined' - @end - @if os.getenv 'P' - print 'Env P is defined' - @end - -Now, what is `A`? It is a Lua expression which is evaluated at _preprocessor_ -time, and if it returns any value except `nil` or `false` it is true, using -the usual Lua rule. Assuming `A` is just a global variable, how can it be set? - - $ luam test-cond.lua - A not defined - $ luam -VA test-cond.lua - A defined - $ export P=1 - $ luam test-cond.lua - A not defined - Env P is defined - -Although this looks very much like the standard C preprocessor, the implementation -is rather different - `@if` is a special macro which evaluates its argument -(everything on the rest of the line) as a _Lua expression_ -and skips upto `@end` (or `@else` or `@elseif`) if that condition is false. - - -### Using macro.define - -`macro.define` is less convenient than `def_` but much more powerful. The extended -form allows the substitution to be a _function_ which is called in-place at compile -time. These definitions must be loaded before they can be used, -either with `-l` or with `@require`. - - macro.define('DATE',function() - return '"'..os.date('%c')..'"' - end) - -Any text which is returned will be tokenized and inserted into the output stream. -The explicit quoting here is needed to ensure that `DATE` will be replaced by the -string "04/30/11 09:57:53". ('%c' gives you the current locale's version of the -date; for a proper version of this macro, best to use `os.date` [with more explicit -formats](http://www.lua.org/pil/22.1.html) .) - -This function can also return nothing, which allows you to write macro code purely -for its _side-effects_. - -Non-operator characters like `@`,`$`, etc can be used as macros. For example, say -you like shell-like notation `$HOME` for expanding environment variables in your -scripts. - - macro.define '$(x) os.getenv(_STR_(x))' - -A script can now say `$(PATH)` and get the expected expansion, Make-style. But we -can do better and support `$PATH` directly: - - macro.define('$',function(get) - local var = get:iden() - return 'os.getenv("'..var..'")' - end) - -If a macro has no parameters, then the substitution function receives a 'getter' -object. This provides methods for extracting various token types from the input -stream. Here the `$` macro must be immediately followed by an identifier. - -We can do better, and define `$` so that something like `$(pwd)` has the same -meaning as the Unix shell: - - macro.define('$',function(get) - local t,v = get() - if t == 'iden' then - return 'os.getenv("'..v..'")' - elseif t == '(' then - local rest = get:upto ')' - return 'os.execute("'..tostring(rest)..'")' - end - end) - -(The getter `get` is callable, and returns the type and value of the next token.) - -It is probably a silly example, but it illustrates how a macro can be overloaded -based on its lexical context. Much of the expressive power of LuaMacro comes from -allowing macros to fetch their own parameters in this way. It allows us to define -new syntax and go beyond 'pseudo-functions', which is more important for a -conventional-syntax language like Lua, rather than Lisp where everything looks like -a function anyway. These kinds of macros are called 'reader' macros in the Lisp world, -since they temporarily take over reading code. - -It is entirely possible for macros to create macros; that is what `def_` does. -Consider how to add the concept of `const` declarations to Lua: - - const N,M = 10,20 - -Here is one solution: - - macro.define ('const',function(get) - get() -- skip the space - local vars = get:idens '=' - local values = get:list '\n' - for i,name in ipairs(vars) do - macro.assert(values[i],'each constant must be assigned!') - macro.define_scoped(name,tostring(values[i])) - end - end) - -The key to making these constants well-behaved is `define_scoped`, which installs a -block handler which resets the macro to its original value, which is usually `nil`. -This test script shows how the scoping works: - - require_ 'const' - do - const N,M = 10,20 - do - const N = 5 - assert(N == 5) - end - assert(N == 10 and M == 20) - end - assert(N == nil and M == nil) - - -If we were designing a DSL intended for non-technical users, then we cannot just -say to them 'learn the language properly - go read PiL!'. It would be easier to -explain: - - forall x in {10,20,30} do - -than the equivalent generic `for` loop. `forall` can be implemented fairly simply -as a macro: - - macro.define('forall',function(get) - local var = get:iden() - local t,v = get:next() -- will be 'in' - local rest = tostring(get:upto 'do') - return ('for _,%s in ipairs(%s) do'):format(var,rest) - end) - -That is, first get the loop variable, skip `in`, grab everything up to `do` and -output the corresponding `for` statement. - -Useful macros can often be built using these new forms. For instance, here is a -simple list comprehension macro: - - macro.define('L(expr,select) '.. - '(function() local res = {} '.. - ' forall select do res[#res+1] = expr end '.. - 'return res end)()' - ) - -For example, `L(x^2,x in t)` will make a list of the squares of all elements in `t`. - -Why don't we use a long string here? Because we don't wish to insert any extra line -feeds in the output.`macro.forall` defines more sophisticated `forall` statements -and list comprehension expressions, but the principle is the same - see 'tests/test-forall.lua' - -There is a second argument passed to the substitution function, which is a 'putter' -object - an object for building token lists. For example, a useful shortcut for -anonymous functions: - - M.define ('\\',function(get,put) - local args = get:idens('(') - local body = get:list() - return put:keyword 'function' '(' : idens(args) ')' : - keyword 'return' : list(body) : space() : keyword 'end' - end) - -The `put` object has methods for appending particular kinds of tokens, such as -keywords and strings, and is also callable for operator tokens. These always return -the object itself, so the output can be built up with chaining. - -Consider `\x,y(x+y)`: the `idens` getter grabs a comma-separated list of identifier -names upto the given token; the `list` getter grabs a general argument list. It -returns a list of token lists and by default stops at ')'. This 'lambda' notation -was suggested by Luiz Henrique de Figueiredo as something easily parsed by any -token-filtering approach - an alternative notation `|x,y| x+y` has been -[suggested](http://lua-users.org/lists/lua-l/2009-12/msg00071.html) but is -generally impossible to implement using a lexical scanner, since it would have to -parse the function body as an expression. The `\\` macro also has the advantage -that the operator precedence is explicit: in the case of `\\(42,'answer')` it is -immediately clear that this is a function of no arguments which returns two values. - -I would not necessarily suggest that lambdas are a good thing in -production code, but they _can_ be useful in iteractive exploration and within tests. - -Macros with explicit parameters can define a substitution function, but this -function receives the values themselves, not the getter and putter objects. These -values are _token lists_ and must be converted into the expected types using the -token list methods: - - macro.define('test_(var,start,finish)',function(var,start,finish) - var,start,finish = var:get_iden(),start:get_number(),finish:get_number() - print(var,start,finish) - end) - - -Since no `put` object is received, such macros need to construct their own: - - local put = M.Putter() - ... - return put - -(They can of course still just return the substitution as text.) - -### Dynamically controlling macro expansion - -Consider this loop-unrolling macro: - - do_(i,1,3, - y = y + i - ) - -which will expand as - - y = y + 1 - y = y + 2 - y = y + 3 - -For each iteration, it needs to define a local macro `i` which expands to 1,2 and 3. - - macro.define('do_(v,s,f,stat)',function(var,start,finish,statements) - local put = macro.Putter() - var,start,finish = var:get_iden(),start:get_number(),finish:get_number() - macro.push_token_stack('do_',var) - for i = start, finish do - -- output `set_ ` - put:iden 'set_':iden(var):number(i):space() - put:tokens(statements) - end - -- output `undef_ ` - put:iden 'undef_':iden(var) - -- output `_POP_ 'do_'` - put:iden '_DROP_':string 'do_' - return put - end) - -Ignoring the macro stack manipulation for a moment, it works by inserting `set_` -macro assignments into the output. That is, the raw output looks like this: - - set_ i 1 - y = y + i - set_ i 2 - y = y + i - set_ i 2 - y = y + i - undef_ i - _DROP_ 'do_' - -It's important here to understand that LuaMacro does not do _recursive_ -substitution. Rather, the output of macros is pushed out to the stream which is -then further substituted, etc. So we do need these little helper macros to set the -loop variable at each point. - -Using the macro stack allows macros to be aware that they are expanding inside a -`do_` macro invocation. Consider `tuple`, which is another macro which creates -macros: - - tuple(3) A,B - A = B - -which would expand as - - local A_1,A_2,A_3,B_1,B_2,B_3 - A_1,A_2,A_3 = B_1,B_2,B_3 - -But we would like - - do_(i,1,3, - A = B/2 - ) - -to expand as - - A_1 = B_1/2 - A_2 = B_2/2 - A_2 = B_2/2 - -And here is the definition: - - macro.define('tuple',function(get) - get:expecting '(' - local N = get:number() - get:expecting ')' - get:expecting 'space' - local names = get:idens '\n' - for _,name in ipairs(names) do - macro.define(name,function(get,put) - local loop_var = macro.value_of_macro_stack 'do_' - if loop_var then - local loop_idx = tonumber(macro.get_macro_value(loop_var)) - return put:iden (name..'_'..loop_idx) - else - local out = {} - for i = 1,N do - out[i] = name..'_'..i - end - return put:idens(out) - end - end) - end - end) - -The first expansion case happens if we are not within a `do_` macro; a simple list -of names is outputted. Otherwise, we know what the loop variable is, and can -directly ask for its value. - -### Operator Macros - -You can of course define `@` to be a macro; a new feature allows you to add new -operator tokens: - - macro.define_tokens {'##','@-'} - -which can then be used with `macro.define`, but also now with `def_`. It's now -possible to define a list comprehension syntax that reads more naturally, e.g. -`{|x^2| i=1,10}` by making `{|` into a new token. - -Up to now, making a Lua operator token such as `.` into a macro was not so useful. -Such a macro may now return an extra value which indicates that the operator should -simply 'pass through' as is. Consider defining a `with` statement: - - with A do - .x = 1 - .y = 2 - end - -I've deliberately indicated the fields using a dot (a rare case of Visual Basic -syntax being superior to Delphi). So it is necessary to overload '.' and look at -the previous token: if it isn't a case like `name.` or `].` then we prepend the -table. Otherwise, the operator must simply _pass through_, to prevent an -uncontrolled recursion. - - M.define('with',function(get,put) - M.define_scoped('.',function() - local lt,lv = get:peek(-1,true) -- peek before the period... - if lt ~= 'iden' and lt ~= ']' then - return '_var.' - else - return nil,true -- pass through - end - end) - local expr = get:upto 'do' - return 'do local _var = '..tostring(expr)..'; ' - end) - -Again, scoping means that this behaviour is completely local to the with-block. - -A more elaborate experiment is `cskin.lua` in the tests directory. This translates -a curly-bracket form into standard Lua, and at its heart is defining '{' and '}' as -macros. You have to keep a brace stack, because these tokens still have their old -meaning and the table constructor in this example must still work, while the -trailing brace must be converted to `end`. - - if (a > b) { - t = {a,b} - } - -### Pass-Through Macros - -Normally a macro replaces the name (plus any arguments) with the substitution. It -is sometimes useful to pass the name through, but not to push the name into the -token stream - otherwise we will get an endless expansion. - - macro.define('fred',function() - print 'fred was found' - return nil, true - end) - -This has absolutely no effect on the preprocessed text ('fred' remains 'fred', but -has a side-effect. This happens if the substitution function returns a second -`true` value. You can look at the immediate lexical environment with `peek`: - - macro.define('fred',function(get) - local t,v = get:peek(1) - if t == 'string' then - local str = get:string() - return 'fred_'..str - end - return nil,true - end) - -Pass-through macros are useful when each macro corresponds to a Lua variable; they -allow such variables to have a dual role. - -An example would be Python-style lists. The [Penlight -List](http://stevedonovan.github.com/Penlight/api/modules/pl.List.html) class has -the same functionality as the built-in Python list, but does not have any -syntactical support: - - > List = require 'pl.List' - > ls = List{10,20,20} - > = ls:slice(1,2) - {10,20} - > ls:slice_assign(1,2,{10,11,20,21}) - > = ls - {10,11,20,21,30} - -It would be cool if we could add a little bit of custom syntax to make this more -natural. What we first need is a 'macro factory' which outputs the code to create -the lists, and also suitable macros with the same names. - - -- list [ = ] - M.define ('list',function(get) - get() -- skip space - -- 'list' acts as a 'type' followed by a variable list, which may be - -- followed by initial values - local values - local vars,endt = get:idens (function(t,v) - return t == '=' or (t == 'space' and v:find '\n') - end) - -- there is an initialization list - if endt[1] == '=' then - values,endt = get:list '\n' - else - values = {} - end - -- build up the initialization list - for i,name in ipairs(vars) do - M.define_scoped(name,list_check) - values[i] = 'List('..tostring(values[i] or '')..')' - end - local lcal = M._interactive and '' or 'local ' - return lcal..table.concat(vars,',')..' = '..table.concat(values,',')..tostring(endt) - end) - -Note that this is a fairly re-usable pattern; it requires the type constructor -(`List` in this case) and a type-specific macro function (`list_check`). The only -tricky bit is handling the two cases, so the `idens` method finds the end using a -function, not a simple token. `idens`, like `list`, returns the list and the token -that ended the list, so we can use `endt` to check. - - list a = {1,2,3} - list b - -becomes - - local a = List({1,2,3}) - local b = List() - -unless we are in interactive mode, where `local` is not appropriate! - -Each of these list macro/variables may be used in several ways: - - - directly `a` - no action! - - `a[i]` - plain table index - - `a[i:j]` - a list slice. Will be `a:slice(i,j)` normally, but must - be `a:slice_assign(i,j,RHS)` if on the right-hand side of an assignment. - -The substitution function checks these cases by appropriate look-ahead: - - function list_check (get,put) - local t,v = get:peek(1) - if t ~= '[' then return nil, true end -- pass-through; plain var reference - get:expecting '[' - local args = get:list(']',':') - -- it's just plain table access - if #args == 1 then return '['..tostring(args[1])..']',true end - - -- two items separated by a colon; use sensible defaults - M.assert(#args == 2, "slice has two arguments!") - local start,finish = tostring(args[1]),tostring(args[2]) - if start == '' then start = '1' end - if finish == '' then finish = '-1' end - - -- look ahead to see if we're on the left hand side of an assignment - if get:peek(1) == '=' then - get:next() -- skip '=' - local rest,eoln = get:upto '\n' - rest,eoln = tostring(rest),tostring(eoln) - return (':slice_assign(%s,%s,%s)%s'):format(start,finish,rest,eoln),true - else - return (':slice(%s,%s)'):format(start,finish),true - end - end - -This can be used interactively, like so (it requires the Penlight list library.) - - $> luam -llist -i - Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio - Lua Macro 2.3.0 Copyright (C) 2007-2011 Steve Donovan - > list a = {'one','two'} - > = a:map(\x(x:sub(1,1))) - {o,t} - > a:append 'three' - > a:append 'four' - > = a - {one,two,three,four} - > = a[2:3] - {two,three} - > = a[2:2] = {'zwei','twee'} - {one,zwei,twee,three,four} - > = a[1:2]..{'five'} - {one,zwei,five} - -### Preprocessing C - -With the 2.2 release, LuaMacro can preprocess C files, by the inclusion of a C LPeg -lexer based on work by Peter Odding. This may seem a semi-insane pursuit, given -that C already has a preprocessor, (which is widely considered a misfeature.) -However, the macros we are talking about are clever, they can maintain state, and -can be scoped lexically. - -One of the irritating things about C is the need to maintain separate include -files. It would be better if we could write a module like this: - - - // dll.c - #include "dll.h" - - export { - typedef struct { - int ival; - } MyStruct; - } - - export int one(MyStruct *ms) { - return ms->ival + 1 - } - - export int two(MyStruct *ms) { - return 2*ms->ival; - } - -and have the preprocessor generate an apppropriate header file: - - - #ifndef DLL_H - #define DLL_H - typedef struct { - int ival; - } MyStruct; - - int one(MyStruct *ms) ; - int two(MyStruct *ms) ; - #endif - -The macro `export` is straightforward: - - - M.define('export',function(get) - local t,v = get:next() - local decl,out - if v == '{' then - decl = tostring(get:upto '}') - decl = M.substitute_tostring(decl) - f:write(decl,'\n') - else - decl = v .. ' ' .. tostring(get:upto '{') - decl = M.substitute_tostring(decl) - f:write(decl,';\n') - out = decl .. '{' - end - return out - end) - -It looks ahead and if it finds a `{}` block it writes the block as text to a file -stream; otherwise writes out the function signature. `get:upto '}'` will do the -right thing here since it keeps track of brace level. To allow any other macro -expansions to take place, `substitute_tostring` is directly called. - -`tests/cexport.lua` shows how this idea can be extended, so that the generated -header is only updated when it changes. - -To preprocess C with `luam`, you need to specify the `-C` flag: - - luam -C -lcexport -o dll.c dll.lc - -Have a look at [lc](modules/macro.lc.html) which defines a simplified way to write -Lua bindings in C. Here is `tests/str.l.c`: - - // preprocess using luam -C -llc -o str.c str.l.c - #include - - module "str" { - - def at (Str s, Int i = 0) { - lua_pushlstring(L,&s[i-1],1); - return 1; - } - - def upto (Str s, Str delim = " ") { - lua_pushinteger(L, strcspn(s,delim) + 1); - return 1; - } - - } - -The result looks like this: - - // preprocess using luam -C -llc -o str.c str.l.c - #line 2 "str.lc" - #include - - #include - #include - #include - #ifdef WIN32 - #define EXPORT __declspec(dllexport) - #else - #define EXPORT - #endif - typedef const char *Str; - typedef const char *StrNil; - typedef int Int; - typedef double Number; - typedef int Boolean; - - - #line 6 "str.lc" - static int l_at(lua_State *L) { - const char *s = luaL_checklstring(L,1,NULL); - int i = luaL_optinteger(L,2,0); - - #line 7 "str.lc" - - lua_pushlstring(L,&s[i-1],1); - return 1; - } - - static int l_upto(lua_State *L) { - const char *s = luaL_checklstring(L,1,NULL); - const char *delim = luaL_optlstring(L,2," ",NULL); - - #line 12 "str.lc" - - lua_pushinteger(L, strcspn(s,delim) + 1); - return 1; - } - - static const luaL_reg str_funs[] = { - {"at",l_at}, - {"upto",l_upto}, - {NULL,NULL} - }; - - EXPORT int luaopen_str (lua_State *L) { - luaL_register (L,"str",str_funs); - - return 1; - } - -Note the line directives; this makes working with macro-ized C code much easier -when the inevitable compile and run-time errors occur. `lc` takes away some -of the more irritating bookkeeping needed in writing C extensions -(here I only have to mention function names once) - -`lc` was used for the [winapi](https://github.com/stevedonovan/winapi) project to -preprocess [this -file](https://github.com/stevedonovan/winapi/blob/master/winapi.l.c) -into [standard C](https://github.com/stevedonovan/winapi/blob/master/winapi.c). - -This used an extended version of `lc` which handled the largely superficial -differences between the Lua 5.1 and 5.2 API. - -(The curious thing is that `winapi` is my only project where I've leant on -LuaMacro, and it's all in C.) - -### A Simple Test Framework - -LuaMacro comes with yet another simple test framework - I apologize for this in -advance, because there are already quite enough. But consider it a demonstration -of how a little macro sugar can make tests more readable, even if you are -uncomfortable with them in production code (see `tests/test-test.lua`) - - require_ 'assert' - assert_ 1 == 1 - assert_ "hello" matches "^hell" - assert_ x.a throws 'attempt to index global' - -The last line is more interesting, since it's transparently wrapping -the offending expression in an anonymous function. The expanded output looks -like this: - - T_ = require 'macro.lib.test' - T_.assert_eq(1 ,1) - T_.assert_match("hello" ,"^hell") - T_.assert_match(T_.pcall_no(function() return x.a end),'attempt to index global') - -(This is a generally useful pattern - use macros to provide a thin layer of sugar -over the underlying library. The `macro.assert` module is only 75 lines long, with -comments - its job is to format code to make using the implementation easier.) - -Remember that the predefined meaning of @ is to convert `@name` into `name_`. So we -could just as easily say `@assert 1 == 1` and so forth. - -Lua functions often return multiple values or tables: - - two = \(40,2) - table2 = \({40,2}) - @assert two() == (40,2) - @assert table2() == {40,2} - -For a proper grown-up Lua testing framework -that uses LuaMacro, see [Specl](http://gvvaughan.github.io/specl). - - -### Implementation - -It is not usually necessary to understand the underlying representation of token -lists, but I present it here as a guide to understanding the code. - -#### Token Lists - -The token list representation of the expression `x+1` is: - - {{'iden','x'},{'+','+'},{'number','1'}} - -which is the form returned by the LPeg lexical analyser. Please note that there are -also 'space' and 'comment' tokens in the stream, which is a big difference from the -token-filter standard. - -The `TokenList` type defines `__tostring` and some helper methods for these lists. - -The following macro is an example of the lower-level coding needed without the -usual helpers: - - local macro = require 'macro' - macro.define('qw',function(get,put) - local append = table.insert - local t,v = get() - local res = {{'{','{'}} - t,v = get:next() - while t ~= ')' do - if t ~= ',' then - append(res,{'string','"'..v..'"'}) - append(res,{',',','}) - end - t,v = get:next() - end - append(res,{'}','}'}) - return res - end) - -We're using the getter `next` method to skip any whitespace, but building up the -substitution without a putter, just manipulating the raw token list. `qw` takes a -plain list of words, separated by spaces (and maybe commas) and makes it into a -list of strings. That is, - - qw(one two three) - -becomes - - {'one','two','three'} - -#### Program Structure - -The main loop of `macro.substitute` (towards end of `macro.lua`) summarizes the -operation of LuaMacro: - -There are two macro tables, `imacro` for classic name macros, and `smacro` for -operator style macros. They contain macro tables, which must have a `subst` field -containing the substitution and may have a `parms` field, which means that they -must be followed by their arguments in parentheses. - -A keywords table is chiefly used to track block scope, e.g. -`do`,`if`,`function`,etc means 'increase block level' and `end`,`until` means -'decrease block level'. At this point, any defined block handlers for this level -will be evaluated and removed. These may insert tokens into the stream, like -macros. This is how something like `_END_CLOSE_` is implemented: the `end` causes -the block level to decrease, which fires a block handler which passes `end` through -and inserts a closing `)`. - -Any keyword may also have an associated keyword handler, which works rather like a -macro substitution, except that the keyword itself is always passed through first. -(Allowing keywords as regular macros would generally be a bad idea because of the -recursive substitution problem.) - -The macro `subst` field may be a token list or a function. if it is a function then -that function is called, with the parameters as token lists if the macro defined -formal parameters, or with getter and setter objects if not. If the result is text -then it is parsed into a token list.