/tmp/duangsuse.sock
23 subscribers
303 photos
3 videos
92 files
337 links
从 duangsuse::Echo (@dsuse) 跟进出来的分支,将在作者恢复原帐号访问的时候合并删除。
Download Telegram
#NSFW 好像是 porn 群呢 @newsob
好了,Parser.kt 基本出山 🤔
Forwarded from Deleted Account
val dict = TriePattern<Char, String>().apply {
mergeStrings("hello" to "你好")
mergeStrings("world" to "世界")
}

val noun = Repeat(asList(), dict)
val pharse = JoinBy(Decide(elementIn('0'..'9'), elementIn(' ', '\t', '\n', '\r')), dict)

>>> pharse.rebuild("hello world")
res21: kotlin.String? = hello world
>>> pharse.rebuild("hello world a")
res22: kotlin.String? = null
>>> pharse.rebuild("helloworld")
res23: kotlin.String? = hello
>>> noun.rebuild("helloworld")
res24: kotlin.String? = helloworld
>>> noun.rebuild("hello world")
res25: kotlin.String? = hello

>>> pharse
res19: JoinBy<kotlin.Char, kotlin.Char, kotlin.String> = {Path{h=Path{e=Path{l=Path{l=Path{o=Bin[你好]{}}}}}, w=Path{o=Path{r=Path{l=Path{d=Bin[世界]{}}}}}}}([0-9]|(' '|'\t'|'\n'|'\r'))

>>> pharse.read("hello world")
res20: DoubleList<kotlin.String, kotlin.Char>? = Tuple2(first=[你好, 世界], second=[ ])
Forwarded from Deleted Account
这次的解析器框架是纯 one-pass 的,靠 Contextual, Peek, Pipe 的组合完成附加的处理过程,不存在 mark/reset 和 k>1 的情况,但完全可以兼容那种好像需要 lookahead 的情况,所用的数据结构更贴近编程语言而不是一些栈和自动机。

框架的 Pattern<IN, T> 是全泛型、面向一切序列(Iterator、InputStream、Slice也即List、Array、CharSequence)的,这意味着你甚至可以用它抽提反射元数据或者 Array<out String> 里的信息

为了方便测试以及一些其他的考虑,解析器框架不止负责数据的提取,还负责管理提出的数据,并且能够(允许在修改变动后)重新把它们架构回输入数据(rebuild),这可能是比较独特的一点——其他方法都仅仅只有提取基本值的解析器,不包含对提出数据的建模,但 ParserKt 利用 Tuple 和 Fold 两个扩展完全包办了整个生命周期。

就错误处理方面,ParserKt 的 Pattern<IN, T> 返回 null 代表解析失败,目前支持的错误策略是 ErrorListener { onError: ActionOn<Feed<*>, IN> } 和 clamWhile(pat, defaultValue, message),对输入的扩展方法,大家都知道的,随便里面分配个 MutableList 引用加给 onError 闭包。

行号当然必须支持啊,就是 Input<T> 的扩展 CharInput 有。CRLF 也是可以计入的,也就是说 Kotlin 实现语法需要的特殊处理,也没问题喽。

而且提供了一个简单、带注释的中缀解析器实现,以及字典树(方便解析关键字什么的)

代码里有用到 Kotlin 的 intersection upper bound (在 extension fun 的 receiver 里)
@UnsafeVariance (Seq 和 Decide 的兼容)

感兴趣的大佬可以看下
同样的东西,这是我写的第十遍。
Deleted Account
val dict = TriePattern<Char, String>().apply { mergeStrings("hello" to "你好") mergeStrings("world" to "世界") } val noun = Repeat(asList(), dict) val pharse = JoinBy(Decide(elementIn('0'..'9'), elementIn(' ', '\t', '\n', '\r')), dict) >>> pharse.rebuild("hello…
val ints = Seq(::IntTuple, item(1), *Contextual(item<Int>()) { i -> satisfy<Int> {it>i} }.flatten().items() )

>>> i.rebuild(1,2,3)
res1: kotlin.collections.List<kotlin.Int>? = [1, 2, 3]
>>> i.rebuild(1,2,1)
res2: kotlin.collections.List<kotlin.Int>? = null
>>> i.rebuild(1,0,1)
res3: kotlin.collections.List<kotlin.Int>? = [1, 0, 1]

val xsv = JoinBy(elementIn(',',':'), Repeat(asString(), !elementIn(',',':')))

>>> xsv
res1: JoinBy<kotlin.Char, kotlin.Char, kotlin.String> = {{!(','|':')}...(','|':')}
>>> xsv.rebuild("adadc,dasda,de")
res2: kotlin.String? = adadc,dasda,de
>>> xsv.read("adadc,dasda,de")
res3: DoubleList<kotlin.String, kotlin.Char>? = Tuple2(first=[adadc, dasda, de], second=[,, ,])
>>> xsv.rebuild("adadc,dasda,de:dasd,fsd:f") { second = second.mapTo(mutableListOf()) { if (it == ':') ',' else it } }
res4: kotlin.String? = adadc,dasda,de,dasd,fsd,f
又新加了个 elementIn('A'..'Z', 'a'..'z', '0'..'9') or elementIn('_') “逻辑连接符” 支持
看起来 ParserKt 的确是能够用来辅助设计语法,但要想优雅地用于实际目的,很难……
这时候 one-pass 和 rebuild 反而成为了两个傲娇的特性,就比较难受……
Seq 不能用,得用 SurroundBy
Seq(::AnyTuple, *(item<Char>()-'*')) 还有这种用法表达一个 Until pattern……
也是无奈啊
如果允许添加扩展方法,这其实不是多困难的,可在没 IDE 的情况下我不方便做这些非核心的工作……
/tmp/duangsuse.sock
看起来 ParserKt 的确是能够用来辅助设计语法,但要想优雅地用于实际目的,很难……
不是很难……我相信,只是还有点距离…… 初心和理论是好的,就是实现上后来有点小偏差……

现在的 ParserKt 虽然对辅助语法设计有很好的作用,也没见很严重的 bug,但写出来的代码依然不是很易读……
有点头疼,undecide 和 unfold 到底是如何如何…… 那么多 extension 怎么整理……
还是先来自 high 一下吧,本苏写的这个 ParserKt,虽然只有 700 行,绝对可以说是发挥了 Kotlin 的极限……之前的一点,大部分都是不太常见的编程方法,以及 Kotlin 1.3.6 的新特性

反正平常只写那么一两个应用的开发者,肯定是写不出来的

用这个框架写的计算器,只要 30 行就能同时支持优先级、动态中缀定义、REPL 什么的,而且还包含字典树等高级操作
可以做到四两拨千斤的效果,无论是对带行号的代码文本,还是普通的序列、无论是 Char 还是 String 甚至 Int, List,ParserKt 都能完美兼容,编写有健壮性、可进行上下文相关解析的解析器,同时可以做到逆向解析、重构解析结果
用 ParserKt 的复用定义写一行等于手写递归下降解析器的五行、十行,甚至更多
🤔 看起来稍微好了点,可惜还是感觉有点复杂
不过这可是 ParserKt 啊!这么一点代码也支持了 rebuild 和镇静解析策略,提供了 error message,我也满足了。
比靠 parser compiler,生成一堆不知道是怎么工作的代码要强。Haskell GHC 的解析器都不支持镇静策略呢,输入稍微有点错误就全局爆炸。
This media is not supported in your browser
VIEW IN TELEGRAM
duangsuse 你真机智,那么我们来休息一下算了,别写代码了……
我们最后玩一个梗吧,
李明开始喜欢上了坐在教室一隅的小静
现在咱有字典树了,看看怎么提取这个句子结构
李明(r) 开始(pre) 喜欢(v) 上(b) 了(b) 坐(v) 在(pre) 教室(n)一隅(n) 的 小静(r)
r=代词、引用
n=名词; v=动词
pre=前导
b=补语(迫真)

李明(r) 开始(pre) 喜欢(v) 这是一部分
(r) (v)
(v) 前面可以有 (pre)
(v) 后面可以有 (b), (pre)

(v) (pre)

坐(v) 在(pre) 教室(n)一隅(n) 的(c) 小静(r)
(n) (n) 是名词连接

艹写不下去了

enum class WordKind {
Ref, Verb, Noun, Pre, Mid, Unknown
}

typealias Word = Pair<WordKind, String>
val dict = object: TrieReplace<Word>() {
override fun from(path: String) = WordKind.Unknown to path
}.apply { }

算了不写了,狗命要紧