Sucha's Blog ~ Welcome

22年5月14日 周日 23:49

cmark-gfm 静态链接

默认情况下,cmark-gfm 是通过 cmake 编译为动态库的方式,但其实可以通过设置

$ cmake -DCMARK_SHARED=OFF ../
$ make

配置为静态链接,少一个依赖,如果能修改一下再链接到 musl libc,那就更进一步,不受限于系统版本了哈哈。

不过即便使用系统的 libc,也已经很不错了,毕竟也是向后兼容的,不担心未来的使用

$ otool -L src/cmark-gfm
src/cmark-gfm:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)

如果是动态链接的话,会是下面这样

$ otool -L src/cmark-gfm
src/cmark-gfm:
	@rpath/libcmark-gfm-extensions.0.29.0.gfm.3.dylib (compatibility version 0.29.0, current version 0.29.0)
	@rpath/libcmark-gfm.0.29.0.gfm.3.dylib (compatibility version 0.29.0, current version 0.29.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)

编译后当然不会放到系统目录,而是随便丢到一个 PATH 能访问到的目录下用就好了。

22年5月14日 周日 21:16

倾我所有去生活

前一段时间在多抓鱼上买了这本二手书,之所以买,是因为一篇豆瓣上的文章《没有什么必须要说》1,作者苏美。

短篇记载的是一位女儿父亲的一生,从荣耀到低沉,从生机勃勃到病患缠身黯然神伤,这篇短篇的另外一条线是女儿跟父亲的关系,她们的关系算是父亲境遇的反映。

父亲年轻时是一位帕米尔高原的边防战士,在一片白茫茫的雪海里面恪尽职守,直来直往,追雪兔、打雪狼,莽莽撞撞,不言轻伤。直来直往的性格,也体现在跟女儿的关系上面。父亲威严,不允许孩子反驳,女儿为此挨了不少耳光。

画风一转。

可这又有什么用呢,父亲退伍转业,突然患病后身体状况急转而下,为了救命,不得不躺在手术台上,身体被医生划开,插满了管子,任人摆布。

冰天雪地上顶天立地的男儿不在,只剩下无法自理,得依靠儿女度过难关的一位凄惨病人,当时父亲才 49 岁。

父亲状态转好,但在更为复杂、广大的社会丛林里里的小角色,随时间流逝,生计慢慢成了问题,不得不依靠一些之前的狐朋狗友接济。

这位狐朋狗友丢给了父亲一份差事,表面上是管个小公司,实际上是帮摆平这位前辈的一个二奶,二奶啥都不懂,公司帐很乱。

女儿看到父亲这般情景,是恨不得给父亲这位朋友一个耳光。可这位朋友也算是女儿从小就认识的长辈,只能作罢。

故事从高光开局,进入接近死亡的威胁,身体转好的递进,来到社会丛林复杂关系的碾压,然后进入了尾声。

父亲年迈,在厨房里面为儿辈准备晚餐,唠唠叨叨,完全融入了家庭生活。

[1] 被删帖了,禁止网站 JS 能看到

22年5月8日 周日 23:16

Parallels 和 VirtualBox

cincau 开发了一个小站点,而 cincau 是支持使用定制的 luastatic 打包的,目标系统是 Linux。因为容器什么的我还不熟悉,所以其实最终拿到的是 luajit 包含了所有脚本 C 数据 + 一堆 shared library,tar 到一起的结果。

像上面说的,因为没搞容器,所以还是需要通过虚拟机上的 Linux 系统生成目标二进制,之前在 Mac 上我用的是 Parallels,也挺方便的,但是 Parallels 贵啊,非正规的也不好搞,这次新机器上的 Parallels 好不容易安装好了,结果网络怎么都用不了,我是相当郁闷,花了几个小时还搞不定,但是又确实不想花钱升级了。

于是试了一下 VirtualBox,发现同样一个版本,低至 10.12 的 Mac 也能安装,新 Mac 系统上面创建的虚拟机文件,拷贝过来后,直接用,不能更开心。

感觉不需要什么特别的配置,直接用就行,早知道我早早地使用 VirtualBox 好了,毕竟不是使用 Windows,对于虚拟化性能、3D、2D 什么我是一点都不在乎的,现在身边已经没有 Windows 机器了。

22年5月4日 周三 23:28

罗浮山一日游

3 号 7 点的时候,太阳已经把深圳晒得金黄,五一开头的冷雨天气正式结束,然后气温紧接着就上来了。我是计划好了去罗浮山,早买了票。

一路还挺顺利的,老地方惠州北站转华侨中学,为了吃到肠粉早餐,走了 3 站公交,然后上 268 到了罗浮山。

人巨多,门票相比之前便宜了,只要 ¥27,不知道是不是因为放假的原因。还有缆车升级了,之前一个筐 2 个人,全开放的结构,景观不能更好,就是有点危险,要脚踩加手扶着前面弹性的顶架,要不顶架会自动上翻,这种情况不敢想。拿着手机、相机拍照也得很小心,如果掉下去,要找回来估计难,没法刻缆求剑。

记得当时第一次坐这种缆车,看着树尖在脚底下过,风在耳边呼呼地吹,心跳有点快。

现在变全封闭的车厢了,挤满可以坐 8 个人,但一般只进去 6 个。运量是之前的 3 倍,然后周转速度也快了很多,排队也不焦躁了。不过单程贵了 10 块,双程现在上到 120 了。

人好多呀,几乎是排队上山,好多狭窄的小径,都要挤着才能过去。即便到了飞云顶,人一样多。山顶的小土堆、石头堆不见了,成了一个大土堆,之前石头堆上插着的五星红旗也不见了。

我是觉得罗浮山的山体好美,远看就像是拔地而起,山脉轮廓非常清晰。如果从山顶看,几个山脊各种曲线蜿蜒向下,墨绿色铺满了整座山,爱了爱了。

爬山中间有路过瀑布,景点叫分水坳,也许是刚停雨的原因吧,水流很大,哗哗地。旁边有卖甘蔗的,也不贵,6 块钱,我后悔只搞了一根,一边嚼着,一边看瀑布,吃完还可以洗个手冰凉一下。可惜下山路过的时候,发现卖完了。

晚上仍然是 268 到博罗后,转 L1A、L1B 到惠州市区。晚饭就在街边的推车小吃上,搞了牛肉杂碎煮物,还要了个煎饼果子。不过牛肉真的一般,我觉得太硬了,味道勉勉强强。

第二早又搞了一波肠粉,反正就是觉得比深圳的好吃,然后原路高铁返深了,排队核酸搞了快 20 分钟才出站。

22年4月5日 周六 01:28

改进侧边栏搜索

UI 交互没变化,也是方便之后如果想从站点、博客搜索内容的话,会方便一些,不过自己测试了一下,很多内容搜索不到,应该是小站点,搜索引擎照顾不了。

没名气的静态网页就这样的了,😌

22年4月5日 周六 01:28

MoocHelper 更新到 0.2.12

项目地址 MoocHelper,同时更新了 .vsix 跨平台安装包。

因为 LuaHelper 前段时间更新了,我也跟着将基本的代码更新上去,这次改动主要是 anotation,语法方面我觉得没有什么变化。 anotation 我用得少,所以相关改变也说不上了解。

另外修复了 switch case 中 case 后面 exp 的一些解析问题,之前跟 Lua EBNF 表述的 prefixexp 语法 ':' 有冲突。

mooncake 里 case 紧接着的 exp 可以是右值,比如 object:func1(param1):func2(param2) 是一个合法的 Lua prefixexp, 而 case 语句依赖 ':' 作为终结符,由于 Lua 不依赖空格、分行、间距来作为语法分割,因此刚刚的 exp 对于两个不同的 ':' 可以有多种解释:

-- case 1
case object:
    func1(param):func2(param2)
-- case 2
case object:func1(param1):
    func2(param2)
-- case 3
case (object:func1(param1):func2(param2))

实际上 Lua 的 prefixexp 使用的是贪心策略,最终解析出来的是 case 3,认为 case 子句没有完结,会报语法错误。 而 mooncake 之前对这里没有定义,因此 case 1 和 case 2 都是可选的合法解释。

这次的修改加上了补充,认为 ':' 之后如果紧接着空格,比如 '\t'、'\n'、'\t'、'\v',则认为是 case 语句的终结。 因此 case 1、case 2 才是 mooncake 合法的 case 语句,并且语义有区别。而 case 3 并不是合法的语句。

大概这样吧。

22年3月29日 后日 22:54

深圳停摆一周

深圳因为疫情整个城市停摆了一周,我所在的小区因为有密接者检测出阳性,被封了两周。

密接者所在的楼栋,在被封锁的 2 周内足不出户,医护都是送吃的到户;我们这些顺带的,前面 4 天,足不出户, 但是可以点外卖,医护送外卖和快递到户,4 天之后可以自己下楼拿快递。

好不容易解封了,感觉自己胖了一圈。

这两周从足不出户、足不出楼、足不出小区走过来,真的挺痛苦的,因为小区有点小,人多,又不鼓励下楼到处转,憋得太慌了。

好在之前做了一些准备,即便不买外卖,自热饭够一周,加上囤的面,两三周都不成问题。就是吃得确实寒碜了点,肉类不够,蔬菜也不够。

我是中午点外卖,晚上换着吃自热饭、方便面、竹升面等,话说竹升面挺不错的,看起来小小一卷,煮开后足够我吃的,味道也不错, 面感挺好的,没有单独的包装这点不够好。

我是打算手头这些方便面吃完后,搞些其他的面食试一下,尝尝鲜,因为方便面多是油炸类型的,不够健康,而且味道类似,吃多了有点腻。

竹升面这类看似清淡,其实可以搭配的感觉要多很多,反正我都是会煮 2 ~ 3 分钟的,方面程度来说是一样的。

不大想煮饭,煮饭是好吃,只是时间上耗费太多,后面等条件好些,看看再煮饭吧。

22年2月27日 后日 23:24

本站支持代码高亮

用的方案是 Prism,cmark-gfm 本来就支持生成相关的 pre、code tag,并带上 "language-css" 这样的 attribute 值,那只要简单加上下面两行就好了,在项目站点上,选了几个自己觉得也许会用得比较多的语言,比如 Bash/Shell/JS/Lua/Go/Swift/Lisp,我也就大概会这么多而已吧。

<link rel="stylesheet" type="text/css" href="../styles/prism.min.css">
<script type="text/javascript" src="../js/prism.min.js"></script>

感觉效果很不错的,比如之前

选的主题是 tomorrow night,跟已有的主题搭配起来也挺不错,缺点可能是 js 代码 minify 后仍然有 60k 吧。

不管怎么说,还有 Etag 撑着呢,另外,毕竟是存放在 github 的,算是白嫖了。

22年2月27日 周日 22:35

MNet 支持 TLS 插件

先说一下背景,mnet 很早就想着加入 TLS 的支持,比如在做 cincau 或者 rpc_framework 的时候,就分别有作为独立 server 支持 https,以及作为 agent 使用内建的 TCP,去请求一个 https 认证的 API,或者拉取网页这样的要求。

但之前都是在 Lua/LuaJIT 这一层,在 C 之外做的 SSL 的状态管理,用起来不稳定,就放弃了,直接用了 curl。当时没有认真研究,自从考虑在 C 这一层,mnet 这一层加入 TLS 插件 后,之前不稳定的原因我终于找到了,这个后面再说。

先过一下 mnet 的插件系统,比如为什么要做成插件系统。

因为 mnet 本身是一个小型网络库,几乎是就是单文件支持 MacOS/Linux/FreeBSD/Windows,抽象了 epoll/kqueue/wepoll (IOCP epoll 化),将 TLS 做成插件,是希望没有编译链接 TLS、OpenSSL 时,仍能独立支持 TCP、UDP。而加入 TLS、OpenSSL 的插件代码,以及相关编译选项后,提供 TLS 的支持,使用方式跟抽象为 chann 的普通 TCP 一样。

为了达成这一个目标,mnet 将插件接口抽象为基于 chann_type 的一种配置,mnet_core 单文件本身就提供了 TCP、UDP 的内建插件,外部只需要在 open_chann 时指定使用 TCP、UDP 接口,就能使用 core 提供的 chann listen、connect、send、recv API。

TLS 插件则是抽象 TLS 为一种外部定义的 chann_type,配置了这个定义后,TLS chann 使用方式跟 TCP、UDP 没有差别,从内部往外看,TLS chann_type 也是一种普通的 chann 而已,内部 core 对于所有类型的 chann_type,都提供同样能力的配置接口,没有特别对待。

所谓插件配置,其实就是抽象了 open_chann,listen_chann/connect_chann, send_chann/recv_chann, disconnect_chann/close_chann,chann_state 的回调函数,mnet 的插件,只要能提供 fd,以及这些插件接口,就可以利用上内部的 epoll、kqueue,以及 sending cache。

这样,外部建立 TLS chann,只需要指定 TLS chann_type,open 后做 listen/connect 就好,插件需要外部提供 SSL_CTX,因此证书管理是在外部的,网络无关的 API 内部没有引入。插件提供了 filter 接口,对于 epoll/kqueue 的读写事件,会 filter 询问 chann_type 对应的插件接口,是否要传递给外层调用的 API,还是插件还需要继续处理。

插件同样封装了 chann_state,这样 TLS 的状态可以有 CLOSE/DISCONNECT/CONNECTING/CONNECTED/LISTEN 了。

接着说一下之前在 Lua/LuaJIT 层为何基于 mnet 的 TCP 层做 SSL 没成功,仔细阅读 SSL_read/SSL_write 的 man page,可以看到这个接口接收到 buffer 读写命令后,底层仍然可能有重协商的逻辑,重协商的逻辑需要双方的数据传递,因此有可能需要先读取数据(协商相关的数据)才能写 buffer,反观 SSL_read 底层的重协商有可能先要写数据,才能读。

SSL_read/SSL_write 都有可能导致底层重协商,如果 fd 是阻塞的,协商结束,数据读、写成功后,才会返回给调用方;如果是非阻塞的 fd,ret 返回 <= 0,需要调用 SSL_get_error(ret) 来获取是否是 SSL_ERROR_WANT_READ 或者 SSL_ERROR_WANT_WRITE,还是确实出错了,比如 SSL_ERROR_SSL 这种错误只有 SSL_shutdown,然后 close(fd) 了。

非阻塞 fd 导致的 WANT_READ 或 WANT_WRITE 的错误,需要调用方在合适的时机,使用同样的参数重新调用。说实话,看到这里,我总觉得这个 API 怎能这样设计,但是从 API 提供者的角度来说,非阻塞的 fd,需要打包额数据也许不足 ,也许干嘛干嘛,反正需要等待双方协商好后才能重新打包,如果传递过来的不是之前的数据,或者长度,又需要重新协商了。

为了方便调用方,SSL 提供了两个改善性的配置,一个是 SSL_MODE_ENABLE_PARTIAL_WRITE,只写一部分数据成功后,也返回了,再次发起 SSL_write 可以传递新的数据了,而不是整个 buffer 都成功才返回;另外一个是 SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER,意味着调用方可以换用 buffer 指针(内容、长度还需一致)。

之前 mnet 的 send 是有 cache 的,cache 长度是固定的,导致上面 SSL_write 这个接口不满足,长度变了,这次为了支持 TLS,一旦有 send 数据 cache,都是申请足量的内存 cache 住,后续 epoll/kqueue raise 了可以写的事件,在传递同样的内容、长度(仅 buffer 指针变化)给插件层的 send。

以上就解决了之前 Lua/LuaJIT SSL 不稳定的问题。

我自己是搞了自签名的证书、私钥测试了 reconnect 和 rwdata 这两个跟 TCP 一样的单点测试程序,算是通过了验收。因为 SSL_CTX 是外部提供的,因此后续的认证问题,应该不是问题,即便有特殊需要,基于插件的系统,应该可以可以回调解决吧,毕竟其实我也没有很重度使用这个插件的功能呢。

先这样吧。

22年2月10日 周二 00:56

LuaRocks with LuaJIT (2)

之前我还提到使用了 torch 的 LuaRock 方案,但实际上不晓得 LuaRocks 在哪个版本起(至少在 3.7),编译的时候指定 Lua 解释器,就能直接支持,我其实已经用上很长时间了。

说实话,比较期待 LuaRocks 的方面是,直接根据依赖的 rocks,能够输出 single executable,虽然按照目前 LuaRocks 的 spec 构建描述,应该是很难的。

22年1月27日 周四 14:42

MoocHelper

MoonCake 终于有了支持 LSP 的 VSCode 插件 MoocHelper,修改自 LuaHelper, 一个基于 go 的 Lua LSP 方案。

话说 12 月底到 1 月中旬大部分的时间都在补充完善这个插件,基本功能两周之内的业余时间就搞定了。不得不说 go 确实比较简单,另外 LuaHelper 的代码结构也可圈可点,很好懂。

基本功能完成后,就开始了边用边改的流程,就这样又过了两周多,感觉配置好后,在 Mac 上的基本使用没啥问题了。

对比一下原版的功能,基本的代码补全,跳转,提示都是有的,只是缺少 debug 能力,因为 moocscript 是先翻译成 Lua 再交给 Lua 解释器的。另外,配置方面,修改了 luahelper.json 的读取路径,修改到了 .vscode/ 目录下。增加了配置项 ProjectLuaPath 以及 ProjectLuaCPath,对应工程使用的 LUA_PATH 和 LUA_CPATH 携带的路径列表。

增加了这两个配置后,浏览代码期间的环境变量跟运行时候就几乎是一样的了。

说到 Lua LSP 的方案选择,之前考虑过 sumneko/lua-language-server,虽然我是 C/C++ 以及 Lua 都了解,但初步评估后,发现门槛有点高,不知道如何入手修改,以及 debug,想哭😭。

相比之下,基于 Go 的 LuaHelper,就太好 debug 了。只要按照 README 描述,在插件配置里面将选项 connect lsp server way 修改为 socket(默认是 cmd),而在另外一边的 luahelper-lsp 目录,按下 F5 就开启了调试模式,断点什么都特别方便。就算像我这样 go 基础非常浅的,也很快弄懂了如何 debug,几遍下来,程序的结构就很清楚了。

相比之下,lua-server 是 Lua + C++ 的方案,估计调试没那么容易,当然 go 的效率较高又是另外一个点了。

除此之外,我还看了一下相关的代码,知道其最终输出是 AST,后续的语法分析,提示、跳转什么都是依赖这个 AST 工作的,大概读懂这个 AST 结构后,我就比较放心了。moocscript 虽然语法不一样,但其最终会编译到 Lua,会沿用一样的 AST 结构,不会遗漏语言表达的任何细节。

一开始我想用 lua-server 的,但几遍下来,看不懂 parser 部分,没能抓住最后输出 AST 的逻辑,少了这个核心关键点,就只能放弃了。

也是基于上面输出同样 Lua AST 的原因,除了 debug 功能外,其他的功能,moocscript 和 lua 几乎是一样的,只是有一些限制。

这些限制是因为 Lua 和 MoonCake 在 token 阶段,共享了 token 这些关键字,moocscript 的关键字比 Lua 多了不少,这些关键字在 MoocHelper 里面的 Lua 文件也会被提示不能使用而报错,虽然 lua 解释器是能正常运行这些脚本的,这也算缺点之一吧。

对了,因为 AST 共通,所以 MoocHelper 也能解析 Lua 代码,工程里面不同文件,混用 moocscript 和 lua 都是没有问题的。