/tmp/duangsuse.sock
23 subscribers
303 photos
3 videos
92 files
337 links
从 duangsuse::Echo (@dsuse) 跟进出来的分支,将在作者恢复原帐号访问的时候合并删除。
Download Telegram
#acg https://ihentai.moe/ 虽然我之前没有去过 ehentai,但是我见过某 ehentai 的工具
ehentai 总体结构比较简单,就是一个以 gid 为 key 本子元数据为结果的 dict
这里是一位大佬根据令一位大佬 8k 本本子数据 创建的站
数据在 GitHub 上可以访问,不过需要 Git LFS(large filestore) (本子缩略图什么的>2G)

也是看到开源哥的点赞才知道有这玩意...
fp.js
11.7 KB
#FP #JavaScript 现在已经打包成了 CommonJS require 库的形式... 随用随复制
未来还会加入解析组合子选项(可以做一些数据量小的 EDSL 了
/tmp/duangsuse.sock
未来还会加入解析组合子选项(可以做一些数据量小的 EDSL 了
得等我先完成那几个插件,然后修整一点时间...
刚才去了解了个博客,就有 GraphQL, Express, Koa, React, Vue 什么的流行技术入眼
/tmp/duangsuse.sock
未来还会加入解析组合子选项(可以做一些数据量小的 EDSL 了
解析组合子是什么?
下面会简单科普一下 LL(1)、LR(1)、LALR 和一般手写的递归下降(recursive descent)解析器的 一些 知识(

每次看到很多各种各样的线性 数据结构,比如说,字符串
不管是采用怎样的字符集、怎样的编码,字符串都是一堆可以判断相等性的『字符』组成的『序列』
那么,如果为了方便,想从这个序列里提取出一些结构(比如从 "$a . $b" 里提取 [dollar a, '.', dollar b]
就需要一种依据序列 进行语法树推导的算法
一般被称作解析算法

LL(1) 的第一个 L 是 "Left to right",指这是个解析器 Left-To-Right 从左到右扫描输入序列
LL(1) 的第二个 L 是 "Leftmost derivation",LL(1) 是自顶向下构造提出数据(推导)的
LL(1) 的这个 (1) 代表任何时候,LL(1) 只需向前阅读一个项目,即可决定解析流程走向了(终结符 terminal 推导预判)
LL 解析器一般用的是显式的栈(stack) 而非递归下降法的机器栈(语言提供的调用栈)

LR(1) 的第二条则正好相反,它不是自顶向下收束,而是自底向上规约(大概吧... Parser 和 grammar 的关系还是蛮大的,LR 的 back-substraction 是什么鬼,我怎么感觉这些自动机有点重造编程语言执行者的嫌疑...

LALR 我听过《Ruby 原理剖析》说是“Look-Ahead Left Rightmost derivation”

以上都可能很不准确,中国国内能找到很少的资料(恐怕大部分都还是转发...)而且我懒得看资料(因为我又不是专门学面向解析器的编译原理的.... 或者 NLP 什么的)

总之我不是特别喜欢这类自动机... 看起来虽然优化明显一些,但是它们也是只能一个字符一个字符的推导... SIMD 扫描字符串可能都没有。而且很多 parser compiler 生成出的程序还贼大... 对手动优化也不友好的


不过这里要讲的是一般的手写递归下降法解析器
即使很多语言官方都是在用 Lex/Yacc style 的 parser combinator (比如 PHP 就是 re2c、GCC C 前端之前也应该用过自动生成的解析器)

这个递归下降呢... 思路很简单,就是利用所用编程语言自有的结构,比如判断、循环、递归,来扫描目标输入
自动机的状态,是由这门编程语言的控制流转换图构成的

比如扫描一个简单的数字(类似 130, 229 这种 /(\d+)/):

#include <stdio.h>
#include <ctype.h>

#define PARSER int ch;
#define GETC (ch = getchar())
unsigned int readUnsigned() {
PARSER
unsigned readn = 0;
while (isdigit(GETC)) { readn*=10; readn+=(ch -'0'); }
}

就是用这个 while,那这个实际上有一点问题,就是不能保证 (实际上是 /d*/),其实 int ch; 可以提升到全局作用域,这样在函数入口处就可以检查下(是不是 isdigit,会不会至少循环一次)语法正确与否了

跑题了... 不过的确是有『工业级』语言 使用手写的递归下降解析器的,比如 Lua

这个 fp.js 的解析器呢... 一般会把输入看成是流,最好还能 mark/reset,这样就能轻而易举地实现 (多) branch

不过目前看来,虽然 (intoIter 从 array-like 转化的) iterator 是可以随便 tell/seek 的,但是没有 API 来 reset。
不过 LookAhead-1 还是可以实现的。方法就是预取 — 利用 lookAhead1 函数得到的流,有一个 next 属性可以窥视到下一个项目,这样可以实现需要预判的语法

Python 的缩进语义需不需要预判呢?其实缩进语义本身是有点 Non-Context free 的,毕竟你没法写出一种刻板的描述,
一个 ':' 会开启新 layout(语法布局,就是缩进层次),可是 CFG 可以有 sequence 和 branch,但它没法有 context — 算不出来下一层布局是多少个空格开头什么的,也就是 Non-CFG 的了(当然,这种情况可以先扫描一遍转化 layout 语法为 CFG 语法(添加 <begin> 和 <end> 之类的隐藏关键字什么的),这样就依然可以扫描

Python 的列表 List comprehension LL(1) 能不能实现呢?

先来看看例子:里面 '' 包裹的是文本,[] 包裹的是可选内容、<> 包裹的也是文本、{} 包裹是重复 0-n、{{}} 是 1-n
大写的是非终结符(可以被替换为终结符序列),小写的是终结符。

List = '[' Items ']'
Items = Expr {',' Expr}
[1,2,3,4] #valid
[0] #valid

ListComp = Expr <for> ident <in> Expr [ <if> Expr ]
Items |= ListComp

[x for x in xs] #valid
[sin(x) for x in xs if x < 10] #valid

那这是不是 LL(1) 可以推导的呢?Left, Leftmost derivation, LookAhead(1)
首先重写一下式子

List = '[' Expr {',' Expr}
| Expr <for> ident <in> Expr
| Expr <for> ident <in> Expr <if> Expr
']'

我们看看,是不是靠着 LookAhead1 就可以判断解决所有的 | 分支呢?这关系到解析器状态的转换 🤔

首先咱收束个 Expr,然后怎么办呢?从字符组织,不得不看看 字符的情况了
当然这里是不可能 的,不过还是可以做到。下一项的可能性:
1.
{',' Expr}
',' 即可
2,3. 都是 <for> 这就混了(

  | Expr <for> ident <in> Expr
| Expr <for> ident <in> Expr * <if> Expr
']'
不过两边 左 的文法都是一致的,所以找一下最右边的情况 (*)
这个时候也是可以通过预取 1 推导的:为什么?判断是 <if> | ']' 即可

所以 Python 的这个语法也是 LL(1) 兼容的
目前在想... 手工检查一下(癖好)加上 FP 特性即可
现在已经完成并且测试了绝大部分, fp.js, fp_test.js
加起来有 1k 行,还差一个解析组合子没有完成 #FP #JavaScript 🤔 emmm.
我为了写一个应用,就花了这么长时间叉出去写这种东西...
#FP #JavaScript 代码的截图们(