看了以后我算是了解了下 Java 的流,和 Kotlin 的 Iterable/Collection operators ext funs 完全不是一个概念,这个流是 queue,async 的
但是上面实现的人明显不重视简洁性(甚至在
我感觉不是很优雅。(M<V>=Map<Object,V>) 理论上 forks: M<Function<Stream<T>, ?>>不能说难看,应该说是错的,完全滥用了流窗口大小 (不对,本身没有窗口,应该说传参方式太奇怪了
forks: M<String, Consumer<T>> 才算正常,或者说这根本就应该实现为组合些许 Consumer 的 Consumer 才对
在 build() 里它把函数们默认参
get(i)=actions[i].await() ?
实际上就是
🌚?? 这么麻烦
但是上面实现的人明显不重视简洁性(甚至在
map.assignEach {v->op(a,v)} 时有拽技巧的嫌疑)我感觉不是很优雅。(M<V>=Map<Object,V>) 理论上 forks: M<Function<Stream<T>, ?>>
forks: M<String, Consumer<T>> 才算正常,或者说这根本就应该实现为组合些许 Consumer 的 Consumer 才对
在 build() 里它把函数们默认参
()->op.apply(queueStream) 再套了一层 future (supplyAsync) ,然后再在 consumer 里 apply(x) = queues.forEach { it.add(x) } ?get(i)=actions[i].await() ?
实际上就是
workers: M<Pair<Queue<T>, /*supplyAsync(op.bind(queueStream))*/Future<R>>> 🌚?? 这么麻烦
Forwarded from Epics of Anti-Censorship
Telegraph
也论腾讯拿你的历史记录做了点啥
拿到历史记录之后干了什么?应该是读取了淘宝、天猫和京东的搜索记录,并且通过统一接口上传了特定的搜索关键词吧。 根据有关灵媒的说法,内部算法具体 URL 检查步骤如下: 确保 URL 是 UTF-16LE 编码的宽字符串 把整条 URL 转换成大写 寻找长度为 size 的,MD5(4个整数)哈希为 A B C D的子字符串。 MD5 是一种将任意长度内容转换为四个整数的单向函数。由于它找的是一小段字符串的 MD5,所以我们没法恢复出它想要找的真正原文是什么。 总共有四对这样的 (size, A, B, C…
Forwarded from dnaugsuz
https://t.me/dsuse/15727
总结了下,最后得出三行:
queues 也可以不放在
原版的其它函数:
Forker 类的 fork() 和 build() 感觉不至于用设计模式、命名失败,不作评。
Forker 可以用于归纳流至结果(如 1,2,3 => 6),但是默认带 async ,而且你的
看了我真的觉得一些 JavaEE 者必须学习一个,净搞些花里胡哨的,功能都没做好。
总结了下,最后得出三行:
workers: Map<Object, Pair<Queue<T>, Future<R>>> = forks.entries.associate { (k,op) -> val q=BlockingQueue(); k to (q to Complet_Future.supplyAsync(op.bind(SSupport.stream(BQSpilterator(q)) ))) }
apply(x) = workers.forEach { it.first.add(x) }
get(k) = workers[k].second.get()/*await*/ queues 也可以不放在
Pair.first ,而独立成 List 或 Set 。原版的其它函数:
finish() = apply((T)(Object)END_OF_STREAM) //unchecked cast
getResults() = sequential().forEach { accept(it) }.let { finish(); this }Forker 类的 fork() 和 build() 感觉不至于用设计模式、命名失败,不作评。
Forker 可以用于归纳流至结果(如 1,2,3 => 6),但是默认带 async ,而且你的
op: Function<Stream<T>,?> 必须处理一个 (Object)END_OF_STREAM ,返回类型无泛型参数,需强转看了我真的觉得一些 JavaEE 者必须学习一个,净搞些花里胡哨的,功能都没做好。
Telegram
duangsuse::Echo
https://segmentfault.com/a/1190000012641345 #Java #DontKnow #Stream
其实 ParserKt 里出现过的 Reducer 在 Java stream 里叫 Consumer, 动词都一样
这是个 Java8 StreamForker 实现,可以流一遍出多个结果,实质上和我的版本功能一致
简单说下, builder() 出一个 Consumer 然后会被拿去 sequential().forEach()
其中 m1.entries.reduce(new…
其实 ParserKt 里出现过的 Reducer 在 Java stream 里叫 Consumer, 动词都一样
这是个 Java8 StreamForker 实现,可以流一遍出多个结果,实质上和我的版本功能一致
简单说下, builder() 出一个 Consumer 然后会被拿去 sequential().forEach()
其中 m1.entries.reduce(new…
Forwarded from dnaugsuz
如果看的懂的话,可能我就没时间写它了🌝
想要提高技术水平,就必须压缩不重要的细节带来的时间开销,否则很难有精力走下去而不被表象蒙住眼睛。
其实原版也半通不通吧…… 看看以后能不能有改善
想要提高技术水平,就必须压缩不重要的细节带来的时间开销,否则很难有精力走下去而不被表象蒙住眼睛。
其实原版也半通不通吧…… 看看以后能不能有改善
Forwarded from duangsues.is_a? SaltedFish
duangsuse::Echo
执拗的我最后TM 还是写了,但是发现性能并没有比使用 Console.flush 之后有任何改善,看来不是渲染速度问题,是 IO 问题, Jawa 的 IO 速率他妈是废的 🌝 为了寻找减少抹布效应的方法我把原版 StringBuilder 都试过了,结果只是更慢 还是算了吧,不过 chunk buffering 写都写了,还是留着吧(本来直接 while ((frame = vid.getNativeFrame()) != null) 渲染也没问题的)
RingBuffer 支持的,奇妙的是我还用了 Android 上应该没的 AtomicInteger …… 其实 Queue 的实现才好吧 🌚
#Java #serialize #parsing, [18.01.21 10:11]
chen leiothrix: json反序列化的时候,有key对应两种不同结构有什么办法能针对性读取吗
捏造的信仰: [In reply to chen leiothrix]
这种情况的原因是类型信息缺失。如果你用 Jackson 可以看这篇 https://segmentfault.com/a/1190000023218408
ヾ(^▽^*)))Sam: 这种你的分别处理
要根据value值去if else
没啥好办法
I lluZ: 是的,直接当 map 处理,没法反序列化成一个 List<POJO>
ヾ(^▽^*)))Sam: list不行,因为你的数组不属于同一个类型无法搞
chen leiothrix: json反序列化的时候,有key对应两种不同结构有什么办法能针对性读取吗
捏造的信仰: [In reply to chen leiothrix]
这种情况的原因是类型信息缺失。如果你用 Jackson 可以看这篇 https://segmentfault.com/a/1190000023218408
ヾ(^▽^*)))Sam: 这种你的分别处理
要根据value值去if else
没啥好办法
I lluZ: 是的,直接当 map 处理,没法反序列化成一个 List<POJO>
ヾ(^▽^*)))Sam: list不行,因为你的数组不属于同一个类型无法搞
Telegram
ヾ(^▽^*)))Sam in Java 编程语言
要根据value值去if else
duangsuse::Echo
https://gist.github.com/duangsuse/64c9ac7a278da48f4b3de3dafd70e9df 🥳遇到困难,就老熬夜;后来我就提了一个,年轻人早点睡,做到12点就不做了睡大觉,长大了有福报。
#Kotlin #parser #ce https://t.me/dsuses/4519 🤔看来想做 REPL ,对字符终端模型没有坚实的理解是不可以的
写完贪吃蛇和烂苹果,就有经验了,不要老侥幸,下次就不会出了事,倒大霉。
写完贪吃蛇和烂苹果,就有经验了,不要老侥幸,下次就不会出了事,倒大霉。
Telegram
duangsues.is_a? SaltedFish
看了一下我觉得 Java 上的 REPL 最好有个 LineEditReader 这种,需要的 native API 仅 termios tcaget/set attr; struct termios 一个 (Windows 上再实现即可)
像这样:
class X extends Reader {
static native void cbreak(boolean on);
static void hmove(int offset); // 水平移动
static void vmove(int…
像这样:
class X extends Reader {
static native void cbreak(boolean on);
static void hmove(int offset); // 水平移动
static void vmove(int…
https://github.com/ice1000/arend-language-server/blob/88dd2e94ea7ae564a743d349b93487dd5aa4b5f8/src/main/kotlin/server.kt 草,原来冰封那么厉害也没有写自己的 Argument Parser... 我还以为函数式爱好者都痛恨 (
看来事情还是要一分为二的去辩证地看待 🤔
现在想来我几乎从不用第三方的库,凡事只要行数限制不大,能自己动手,决不允许任何可能的辣鸡代码掺进项目里,哪怕有也要自己先封一层。
可能这就是编程习惯的问题吧
org.apache.commons.cli) 需要 (opt as Options).addOption(Option.builder("i").build()) 的代码呢看来事情还是要一分为二的去辩证地看待 🤔
现在想来我几乎从不用第三方的库,凡事只要行数限制不大,能自己动手,决不允许任何可能的辣鸡代码掺进项目里,哪怕有也要自己先封一层。
可能这就是编程习惯的问题吧
GitHub
ice1000/arend-language-server
A language server protocol implementation for Arend - ice1000/arend-language-server
第一次知道原来大佬也有不重视代码质量的时候(话说我是有点强迫症吧……)
这 20 行要我就不会用
我对代码的追求,慢慢也从仅排版整齐上,变成了空格排版项目范围内语义一致、重视代码简洁性和可配置性;想想以前的我看见现在的代码,估计会觉得很难看吧 🤔
运行时开销又高了😂 不过总体看处理了 3种 case (normal+1param+optional, 交织的不算),还是挺清晰的
这 20 行要我就不会用
o.addXXX(cfg.build() ) opt.initArgs("""
c client-port language-
s server-port language-
a client-host ?language client hostname, default localhost
i interactive ?start the Arend REPL
""".trimPadding()) 我对代码的追求,慢慢也从仅排版整齐上,变成了空格排版项目范围内语义一致、重视代码简洁性和可配置性;想想以前的我看见现在的代码,估计会觉得很难看吧 🤔
fun Options.initArgs(code: String, sep: String = "-") {
addOption("h", "help", false, "print this message")
code.split("\n").forEach { line ->
val (s, sLong, desc) = line.split(' ', limit = 3)
val opt = Option.builder(s).longOpt(sLong)
if (sep in sLong) opt.hasArg().argName(sLong.substringAfter(sep))
opt.desc(if (desc[0] == '?') { opt.optionalArg(true); desc.drop(1) } else { desc }.replace('-', sLong.replace(sep, ' ')) )
addOption(opt.build())
}
} 运行时开销又高了😂 不过总体看处理了 3种 case (normal+1param+optional, 交织的不算),还是挺清晰的
GitHub
ice1000/arend-language-server
A language server protocol implementation for Arend - ice1000/arend-language-server
duangsuse::Echo
https://github.com/ice1000/arend-language-server/blob/88dd2e94ea7ae564a743d349b93487dd5aa4b5f8/src/main/kotlin/server.kt 草,原来冰封那么厉害也没有写自己的 Argument Parser... 我还以为函数式爱好者都痛恨 (org.apache.commons.cli) 需要 (opt as Options).addOption(Option.builder("i").build())…
#Java #Kotlin #GitHub #project #suggest 评论一下我之前开发的 ArgParser 😂
是个好东西,对命令行参数的建模也很好,就是,应为 addHelpVerCommand 不对,这个是显示子命令树结构的... 那个 -h -v 的也应该有,但可以另加
还有,生成命令行补全的代码也该加
存在的一些问题,包括强类型约束过于严苛(为此还有 ArgParser4~1 的泛型), 以及 subcommand&formatter 参数实现未测试的瑕疵("All tests passed" 但它太复杂了 我不敢保证) 😂
(Apache: 你敢和我比?🌚 有它强大吗? 还有
谈谈
我们的解析器是流式的(因为框架很重视不同甜度需求的复用性,它是一层层建起来的,其根基
所以实践上
一个即得的技巧是,记住上一次读的 list ,然后读 dst 时从 vararg items 里“偷” 一个项目出来,末尾有 N 项就“偷” N 次。
另一个技巧是寻找 vararg items 的 position ,解析时直接就算 srcs 有多少项,取了跳过 (有点类似 Lua 虚拟机对不定长参函数的编码方法,但这种方法可以做成支持多个 vararg 的不知道有啥样用)
ArgParser 的解决方法比较骚,因为它支持 unparse (backRun) ,换句话说它可以先用无问题的 [dst] [src...] 解析(通过动态构造解析器),再把整个 items 拼了转过来
没有那么高级,其实只需要把参数输入和解析结果,共反转两次就够了 🌝(不过依然要用到 backRun 😂
这是为什么呢? 因为我们必须先检查参数是否合法,并且取得其中 item arg 部分…… 但如果你的代码没有用到 reverse, 相应
是个好东西,对命令行参数的建模也很好,就是
addHelpSubCommand() 这样的帮助函数,没有考虑到应用的需要还有,生成命令行补全的代码也该加
存在的一些问题,包括强类型约束过于严苛(为此还有 ArgParser4~1 的泛型), 以及 subcommand&formatter 参数实现未测试的瑕疵("All tests passed" 但它太复杂了 我不敢保证) 😂
(Apache: 你敢和我比?🌚 有它强大吗? 还有
"<!s #d" 的 fmtstr...)谈谈
"both [src...] [dst] and [dst] [src...] are supported" 是怎么实现的吧我们的解析器是流式的(因为框架很重视不同甜度需求的复用性,它是一层层建起来的,其根基
SwitchParser 就是基于状态机构建输出数据结构的)所以实践上
[src...] dst 里你怎么知道 srcs 不能把最后一个 dst 也解析了(看起来 dst 也可以理解为 src 的一项啊) 🤔? Py 的 argparse 也支持这样(但我不清楚它的架构细节),总之没有一些较 dirty 的 hack 是比较不容易的。一个即得的技巧是,记住上一次读的 list ,然后读 dst 时从 vararg items 里“偷” 一个项目出来,末尾有 N 项就“偷” N 次。
另一个技巧是寻找 vararg items 的 position ,解析时直接就算 srcs 有多少项,取了跳过 (有点类似 Lua 虚拟机对不定长参函数的编码方法,但这种方法可以做成支持多个 vararg 的
ArgParser 的解决方法比较骚,
没有那么高级,其实只需要把参数输入和解析结果,共反转两次就够了 🌝(不过依然要用到 backRun 😂
这是为什么呢? 因为我们必须先检查参数是否合法,并且取得其中 item arg 部分…… 但如果你的代码没有用到 reverse, 相应
backRun 逻辑是不需要的(只是即便这样 progurad 等工具也不知道它可以 delete 掉 (xGitHub
ParserKt/ArgParser
Simplified argument parser / multiplatform CLI for Kotlin ~500 LoC - ParserKt/ArgParser
Forwarded from &'a ::rynco::UntitledChannel (Rynco Maekawa)
Twitter
谦谦
震撼我妈
&'a ::rynco::UntitledChannel
https://twitter.com/magicxqq/status/1351032008533831684?s=19 微信毒瘤特点++
#cplusplus #linux #apple #China #tencent #wechat
相应肇事代码: [lib] [app] (by
奇怪的是,在这些代码
已经有人开了修复 PR (@NeverBehave 和 @metowolf 对此表示 😂)
我感到非常奇怪, WeChat 凭什么去影响上游,让别人帮忙擦屁股? 😡
影响力就是这么大!(指文化输出)
相应肇事代码: [lib] [app] (by
dlsym(RTLD_DEFAULT,"X") )奇怪的是,在这些代码
is_debug = (sym!=NULL&&*sym!=0); 里我没有注意到不兼容无此符号的问题,估计是 dlsym 设置了 errno 导致运行时错误。已经有人开了修复 PR (@NeverBehave 和 @metowolf 对此表示 😂)
我感到非常奇怪, WeChat 凭什么去影响上游,让别人帮忙擦屁股? 😡
影响力就是这么大!
GitHub
duduWang20/libmalloc
https://opensource.apple.com/tarballs/libmalloc/. Contribute to duduWang20/libmalloc development by creating an account on GitHub.
duangsuse::Echo
#cplusplus #linux #apple #China #tencent #wechat 相应肇事代码: [lib] [app] (by dlsym(RTLD_DEFAULT,"X") ) 奇怪的是,在这些代码 is_debug = (sym!=NULL&&*sym!=0); 里我没有注意到不兼容无此符号的问题,估计是 dlsym 设置了 errno 导致运行时错误。 已经有人开了修复 PR (@NeverBehave 和 @metowolf 对此表示 😂) 我感到非常奇怪, WeChat 凭什…
为什么人家要修:估计是果粉报给 Apple 了,人家一生气就改了 libmalloc ,反正多个符号也没问题 🌚
为什么人家骂 weird :
为什么人家骂 weird :
stack_logging_enable_logging 实际就是“你怎么证明你活着”,本来被使用了就是 enabled ,何必搞什么 _enable_logging 呢?Forwarded from RWTG9Y4's STDOUT
好消息 好消息
现在你的 Chrome 可以直接造成系统 BSOD 啦
立刻在地址栏输入 \\.\globalroot\device\condrv\kernelconnect 领取蓝屏大礼包一份
* 就算是放在 <a href/> 里的链接也可以造成蓝屏
现在你的 Chrome 可以直接造成系统 BSOD 啦
立刻在地址栏输入 \\.\globalroot\device\condrv\kernelconnect 领取蓝屏大礼包一份
* 就算是放在 <a href/> 里的链接也可以造成蓝屏
duangsuse::Echo
https://github.com/ice1000/arend-language-server/blob/88dd2e94ea7ae564a743d349b93487dd5aa4b5f8/src/main/kotlin/server.kt 草,原来冰封那么厉害也没有写自己的 Argument Parser... 我还以为函数式爱好者都痛恨 (org.apache.commons.cli) 需要 (opt as Options).addOption(Option.builder("i").build())…
https://github.com/ice1000/jimgui/blob/master/core/test/org/ice1000/jimgui/tests/Demo.java ... 看了以后我对冰封哥的审美有点失望
虽然这只是一个直接的重写,我看出 jimgui 没有比 ImGui 本身更高的封装,它仅以 add container 的方式暴露了 tree ,这不符合之前写 TkGUI 时我的期望。
这里也有一个 initNewFrame + listen keyEvent & StringBuilder & draw text 的示例
总的来说是很重视性能的一个 JNI 封装, 利用了Critical Native ,从不在 Java/C++ 之间传输 primitive (array) 外的数据(这是十分推荐的);也会处理 String/std::string 缓存的问题(感觉管得有点宽,应该解耦了) 甚至可以自定义封送 转换函数
用了 intFlag
看了一会想点 star ,突然发现我 GitHub 好像被 @ice1000 ban 了,现在还没有解除一样
可能是以前冒犯过他吧,觉得应该解除了啊,唉🌚
找了半天没找到他之前发的 JLine ,找到这个 ,代码质量……
虽然这只是一个直接的重写,我看出 jimgui 没有比 ImGui 本身更高的封装,它仅以 add container 的方式暴露了 tree ,这不符合之前写 TkGUI 时我的期望。
这里也有一个 initNewFrame + listen keyEvent & StringBuilder & draw text 的示例
总的来说是很重视性能的一个 JNI 封装, 利用了Critical Native ,从不在 Java/C++ 之间传输 primitive (array) 外的数据(这是十分推荐的);也会处理 String/std::string 缓存的问题(感觉管得有点宽,应该解耦了) 甚至可以自定义
用了 intFlag
@MagicConstant 和 @Nullable 有好的 IDE 支持看了一会想点 star ,突然发现我 GitHub 好像被 @ice1000 ban 了,现在还没有解除一样
可能是以前冒犯过他吧,觉得应该解除了啊,唉🌚
找了半天没找到他之前发的 JLine ,找到这个 ,代码质量……
GitHub
ice1000/jimgui
:sparkling_heart: Pure Java binding for dear-imgui - ice1000/jimgui
duangsuse::Echo
https://github.com/ice1000/jimgui/blob/master/core/test/org/ice1000/jimgui/tests/Demo.java ... 看了以后我对冰封哥的审美有点失望 虽然这只是一个直接的重写,我看出 jimgui 没有比 ImGui 本身更高的封装,它仅以 add container 的方式暴露了 tree ,这不符合之前写 TkGUI 时我的期望。 这里也有一个 initNewFrame + listen keyEvent & StringBuilder…
吃饭的路上(最近有点劳累过度了,思量着 ANSI BadApple 赶紧结束休息几天吧),谈到所谓“优雅性”,想了一下 ParserKt 新 LexerFeed 的问题,感觉很 complicated ,流对象的各种属性真的不好办
首先是说这个给冰封提到底有没有意义的问题(毕竟 PM 冰封是一个比较要心理准备的事情,心理难度比与 Python 红姐、九月姐 谈笑风生不低多少),最后结论是有。
虽然 jimgui 的本意未必是做“定义式”的 GUI 框架,更像是学习 JNI 设计,而且 Kotlin wrapper 很可能会有好的接口,技术交流也是不应有太多压力的。
TkGUI 的代码生成方法利用了 Python 无编译期/运行期,虚拟机相关组件基本可用的动态性,以及动态类型;很难(或者说意义不大)移植到 Java ,但我的本意是 GUI 可以这么写, ImGui 的做法可以说是业界惯例(就我的观察, GTK, Wx 的大部分封装不需要为子控件选择 parent, 但没有一个支持树形代码定义一个视图,即便其语言有足够表现力),Java 的 Swing Frame,Panel 和 Fx Stage,Scene,Group 都必须用 mutate 对象的方法「创建」模板化的视图树(当然这是过程式的自然映照,无可厚非), Qt 和 C#, VB 有 uic 这样的 code generator ;而 TkGUI 的 way 更像是 parser combinator 那样,尽可能少用外部工具,直接在语言里组合。
这个 way 就是一句话,“优美的代码能直观地反映它所处理数据的结构”,程序结构和数据结构相互照应、谐调统一,虽然会有额外开销,但一件事情只有你重视了才能找到各方面的最优解决办法,否则就永远只是传说。
再谈 ParserKt 的问题,其实最初版本相较于一些同类已经可以算是优雅了(当然离我想的还更远)
几个月前的重计划里包含了“削除 Parser 里 Lexer 相关代码”的改动,可以说是解决了我心头一厌(很多 PEG 生成器都逃不开跳空格注释的问题,要么然写文法里,要么然走 lexer/parser 的老路,要么然可配置性不够好,这是比较草的,因为我觉得在 a b c “按顺序”模式里默认插跳空格的逻辑是接近正解的)
具体实现还算好, Lexer/Parser 的区分、 Token 而非 Char 流的存在,核心原因是空格和注释对语法结构是无效的——最好能无视,免得解析器混杂
scannerless parser 很好,但跳空格其实有更容易的解决方法——为 Input stream 添加 filter ,到底还是 java 那一套自由组合的 stream 最好,连 skipped whitespaces 以及 AST element spans 它都可以往
核心思想容易,实现上也有些问题—— Lexer 和 parser 在最近的语言里越来越模糊了,你可以看到 KotlinLexer 里会处理一些嵌套问题(就需要 push/pop state number 了),而且
这么做势必造成计算力的浪费(分词器和解析器对同一份数据做了类似的动作——检查它的嵌套结构),以及编程的冗余、重复代码,是应该努力避免的。
解析器与“分词器”之间的交流,显然是 parser combinator 的优势——它们的结构对程序员是完全透明的,可以自由定义、随意组合,让 Parser 去驱动其输入流上的 Lexer ,告诉分词器现在是什么状态,需不需要跳空格(例如 "" 里就不能跳);分词器是一个针对
有的观众就会问了:这么好的方法,比你高到不知哪里去的聪明人可多了,怎么就你想出来?
首先,不能说是没人想,要看编程实践怎么用、怎么组合这些技巧,不是说你去做了,效果就真的能像想象的那样好
其次,如果你没把代码重写 9遍,也容易被 Lexer/parser 和 scannerless 的那群既有实践误导,以为必须有 tokenizer ,或者流只能是一层,不能有“滤过”操作的
如果被动性[2](非阻塞,要不然无法共享 string 等词法的定义)以及 Parser 对其的主动性(传递在解析词条类型号)不能被保证, 许多人对输入字符序列的抽象不够灵活(万恶之源),使得他们不能够发现这一点。 (所以你在编程的时候,记得重复的少写一点、稀奇的功能特性多写一点,说不定还能帮助你对程序模型整体的理解)
(这种设计也很好的发扬了 ParserKt 的 one-pass 设计,而 C 系语言 // /* 注释与除号的区分早有给
但这个封装有很大问题—— ParserKt 最初只有
尽管这是根基(SliceFeed, Iterator/ReaderFeed 子类),它也是不切实际的,所以很快有了
而那些接收 Input 的 Input ,就只能用一个代理(delegate)类
如果说你组织流嵌套的方法是手工的,应该不需要滥用多态去做“动态类型”,又或者是自动的——真的到那种“可组合”的地步吗?
最后我觉得,还是做则不能打破 Lexer 需求 Input (SourceLocated) 的类型, 还是取消这样的限制吧…… 真不知该怎样解决这问题 #parser #parsing #learn #Kotlin #project #suggest
[^1] 现在我更倾向
isCompleteRead 是重新建模的(结果存逆波兰栈的)算符链解析器需要的,在非 complete read 时,可以像 Lua 一样直接进行简单的常量折叠,否则不仅不能折叠,还要存语法元素行号、前部空格等(重现原文所需的)信息
[^2] 其实我理解错了,这也是因为 LexerFeed 最开始是能自动识别底层输入的状态机,上级请求字符时肯定是要 blocking consume 直到非空格字符的,所谓非阻塞是因为对非空格单个字符它照样要处理状态转移;现在我倾向把它做成“能暂时屏蔽的自动 ws skip”一些,因为这才能真正统一复用文法/词法规则 ,虽然那样就没有花里胡哨的
首先是说这个给冰封提到底有没有意义的问题(毕竟 PM 冰封是一个比较要心理准备的事情,心理难度比与 Python 红姐、九月姐 谈笑风生不低多少),最后结论是有。
虽然 jimgui 的本意未必是做“定义式”的 GUI 框架,更像是学习 JNI 设计,而且 Kotlin wrapper 很可能会有好的接口,技术交流也是不应有太多压力的。
TkGUI 的代码生成方法利用了 Python 无编译期/运行期,虚拟机相关组件基本可用的动态性,以及动态类型;很难(或者说意义不大)移植到 Java ,但我的本意是 GUI 可以这么写, ImGui 的做法可以说是业界惯例(就我的观察, GTK, Wx 的大部分封装不需要为子控件选择 parent, 但没有一个支持树形代码定义一个视图,即便其语言有足够表现力),Java 的 Swing Frame,Panel 和 Fx Stage,Scene,Group 都必须用 mutate 对象的方法「创建」模板化的视图树(当然这是过程式的自然映照,无可厚非), Qt 和 C#, VB 有 uic 这样的 code generator ;而 TkGUI 的 way 更像是 parser combinator 那样,尽可能少用外部工具,直接在语言里组合。
这个 way 就是一句话,“优美的代码能直观地反映它所处理数据的结构”,程序结构和数据结构相互照应、谐调统一,虽然会有额外开销,但一件事情只有你重视了才能找到各方面的最优解决办法,否则就永远只是传说。
再谈 ParserKt 的问题,其实最初版本相较于一些同类已经可以算是优雅了(当然离我想的还更远)
几个月前的重计划里包含了“削除 Parser 里 Lexer 相关代码”的改动,可以说是解决了我心头一厌(很多 PEG 生成器都逃不开跳空格注释的问题,要么然写文法里,要么然走 lexer/parser 的老路,要么然可配置性不够好,这是比较草的,因为我觉得在 a b c “按顺序”模式里默认插跳空格的逻辑是接近正解的)
具体实现还算好, Lexer/Parser 的区分、 Token 而非 Char 流的存在,核心原因是空格和注释对语法结构是无效的——最好能无视,免得解析器混杂
scannerless parser 很好,但跳空格其实有更容易的解决方法——为 Input stream 添加 filter ,到底还是 java 那一套自由组合的 stream 最好,连 skipped whitespaces 以及 AST element spans 它都可以往
Map<K,V> 存储好了,这就同时解决了 AST data class 不好写的问题,在不必使用这些信息时,也提升了性能,就从根源上解决了许多联带(代码复用、类型冗余和构造器隐式参数、分词解析器如何相互协调的)问题。核心思想容易,实现上也有些问题—— Lexer 和 parser 在最近的语言里越来越模糊了,你可以看到 KotlinLexer 里会处理一些嵌套问题(就需要 push/pop state number 了),而且
>= 与 fun():P<T>= 的区分也使得它必须识别一些本该由文法处理的模式——在过去这是不可想象的,C 的词法规则相当简单这么做势必造成计算力的浪费(分词器和解析器对同一份数据做了类似的动作——检查它的嵌套结构),以及编程的冗余、重复代码,是应该努力避免的。
解析器与“分词器”之间的交流,显然是 parser combinator 的优势——它们的结构对程序员是完全透明的,可以自由定义、随意组合,让 Parser 去驱动其输入流上的 Lexer ,告诉分词器现在是什么状态,需不需要跳空格(例如 "" 里就不能跳);分词器是一个针对
Feed 流的状态机,本身也是一个 Feed ,而 onChar 的时候被动进行状态转移,就可以 filter 掉那些解析器不想看的字符,同时也能选择性地保留(如语法高亮) 的数据,一举多得。有的观众就会问了:这么好的方法,比你高到不知哪里去的聪明人可多了,怎么就你想出来?
首先,不能说是没人想,要看编程实践怎么用、怎么组合这些技巧,不是说你去做了,效果就真的能像想象的那样好
其次,如果你没把代码重写 9遍,也容易被 Lexer/parser 和 scannerless 的那群既有实践误导,以为必须有 tokenizer ,或者流只能是一层,不能有“滤过”操作的
如果
LexerFeed 的(这种设计也很好的发扬了 ParserKt 的 one-pass 设计,而 C 系语言 // /* 注释与除号的区分早有给
InfixPattern 扫描操作符的 TriePattern 专定子类可轻易完成,PKT 的组合性不加盖的)但这个封装有很大问题—— ParserKt 最初只有
Feed { val peek; fun consume() } ,不像一些 nextChar() 或 curChar() 数据视口不一致、命名迷惑的框架,它的流模型只允许程序员着眼一项(最本质的问题),结束时抛 Feed.End 异常尽管这是根基(SliceFeed, Iterator/ReaderFeed 子类),它也是不切实际的,所以很快有了
Input(s: Feed): Feed, SourceLocated, ErrorHandler [1],以及一大堆 Feed 上试着 (this as Input) 的扩展函数,允许解析器带行号,尽量减小开销(统计行号信息是要在 Char 输入上,而一些输入根本无需 Input 的一些成员)而那些接收 Input 的 Input ,就只能用一个代理(delegate)类
Input.By 去 proxy 这 underlying stream 实现的一些特性,这种问题严重后有点像“责任链”的字面含义——不断尝试 unwrap 一个 (可能是Input的)Feed ,寻找某个 trait 的实现者。如果说你组织流嵌套的方法是手工的,应该不需要滥用多态去做“动态类型”,又或者是自动的——真的到那种“可组合”的地步吗?
最后我觉得,还是做
LexerInput(s:Input): Input.By 比较好,这样 LexerInput(Input(s=SliceFeed(Slice("wtf") ))) 这样的二层就会成为必要的组合法,如果需要其他层,[^1] 现在我更倾向
Input: Feed, FeedControl, SourceLocated { val states:Map<String,Any>; val onError; val isCompleteRead:Boolean } isCompleteRead 是重新建模的(结果存逆波兰栈的)算符链解析器需要的,在非 complete read 时,可以像 Lua 一样直接进行简单的常量折叠,否则不仅不能折叠,还要存语法元素行号、前部空格等(重现原文所需的)信息
[^2] 其实我理解错了,这也是因为 LexerFeed 最开始是能自动识别底层输入的状态机,上级请求字符时肯定是要 blocking consume 直到非空格字符的,所谓非阻塞是因为对非空格单个字符它照样要处理状态转移;现在我倾向把它做成“能暂时屏蔽的自动 ws skip”一些,因为这才能真正统一复用文法/词法规则 ,虽然那样就没有花里胡哨的
List<Triple<Char,Int,Int>> 了(毕竟有效性在那)GitHub
ParserKt/ParserKt
Naive one-pass recursive descent, scannerless parser framework for Kotlin - ParserKt/ParserKt