/tmp/duangsuse.sock
其实如果说 ParserKt 设计绝句解析器的可能性,已经完成了。 汉字数值读写不存在问题,绝句里实际上不存在需要 lookahead 才能够判断的注释,都是单字符的 🙃 ParserKt 实际上已经能用了,SURDIES, FoldPattern, SingleFeed, CCDP(Convert, Contextual, Deferred, Piped), SJIT(SurroundBy, JoinBy, InfixPattern, TriePattern), NumUnitPattern, Lay…
你们觉得好笑吧,ParserKt 的中文计算器写了 62 行还带 REPL,star 最多的 TypeScript 原生解析器框架(非 parser gen)就多支持了个函数调用,总共写了 300 多行
再加几个 example 上来就抵过 ParserKt 整个项目的代码量了,现在谁是 KISS,谁是『令人不明觉厉、显得很大佬』?
再加几个 example 上来就抵过 ParserKt 整个项目的代码量了,现在谁是 KISS,谁是『令人不明觉厉、显得很大佬』?
GitHub
duangsuse-valid-projects/Share
🐕 duangsuse's shared files(e.g. productive software projects, documents) - duangsuse-valid-projects/Share
那是不比较代码本身的,然后我们再对比一下两者的定义:
== 加减乘除运算符 ==
ParserKt 费了 7 行定义完了所有出现的运算符,并且包含它们的优先级信息。
SAP/chevrotain 单单定义加减乘除长啥样,就用掉 24 行代码
并且,它还把加减法都归类为 AdditionalOperator…… 显然对数学运算符,有没有对称性会比这种优先级上的工作有意义。
再说两个细节,数字读取和括号表达式。
star 很多的框架写了 5 行。
ParserKt 只需写一行就完了,加上错误处理也只用一行。
如果说完全等价的,
== 加减乘除运算符 ==
val ops = KeywordPattern<InfixOp<Number>>().apply {
listOf("*", "乘").forEach { register(it infixl 0 join fn(Int::times)) }
listOf("/", "除以").forEach { register(it infixl 0 join fn(Int::div)) }
register("除" infixl 0 join flip(fn(Int::div)))
register("分" infixl 0 join { a: Number, b: Number-> (a.toDouble() / b.toDouble()) as Number })
listOf("+", "加") .forEach { register(it infixl 1 join fn(Int::plus)) }
listOf("-", "减") .forEach { register(it infixl 1 join fn(Int::minus)) }
} ParserKt 费了 7 行定义完了所有出现的运算符,并且包含它们的优先级信息。
const Plus = createToken({
name: "Plus",
pattern: /\+/,
categories: AdditionOperator
}) //... SAP/chevrotain 单单定义加减乘除长啥样,就用掉 24 行代码
并且,它还把加减法都归类为 AdditionalOperator…… 显然对数学运算符,有没有对称性会比这种优先级上的工作有意义。
const AdditionOperator = createToken({
name: "AdditionOperator",
pattern: Lexer.NA
})
至于左递归 additionExpression, multiplicationExpression 就没啥可说了,毕竟属于解析器框架提供接口上的区别,这些定义用掉 18 行。再说两个细节,数字读取和括号表达式。
$.RULE("parenthesisExpression", () => {
$.CONSUME(LParen)
$.SUBRULE($.expression)
$.CONSUME(RParen)
}) star 很多的框架写了 5 行。
val atomParen = SurroundBy(parens.toCharPat(), Deferred{expr}) ParserKt 只需写一行就完了,加上错误处理也只用一行。
const NumberLiteral = createToken({
name: "NumberLiteral",
pattern: /[1-9]\d*/
})
$.RULE("atomicExpression", () => {
$.OR([/**/{ ALT: () => $.CONSUME(NumberLiteral) }, ...
单单定义 NumberLiteral 叫什么名字、长什么样子,就写了 4 行val digit = digitFor('0'..'9')
val zeroNotation = Decide(
StickyEnd(item('0') or EOF, 0) { if (octal.test(peek)) error("no octal notations"); -1 }
)
val numPart = Contextual<Char, Int, Int>(digit) {
if (it == 0) zeroNotation
else Repeat(asInt(10, it), digit).Many()
}.discardFirst()
带上可以继续扩展/抽提的结构和直接数值读取,8 行。如果说完全等价的,
Contextual(digitFor('1'..'9')) { Repeat(asInt(10, it), digitFor('0'..'9')) }.ignoreFirst(),1 行。GitHub
SAP/chevrotain
Parser Building Toolkit for JavaScript. Contribute to SAP/chevrotain development by creating an account on GitHub.
Forwarded from Deleted Account
虽然我一直有在优化项目的接口易用性,但感觉还是有些“小乌云”……
顺序、直到、重复、选择 (SURD) 这些大结构没有变
单项、含于、符合 (IES) 的最后一个字母我给它加上了 StickyEnd,也算是解决了 Feed 模型的一个不被重视的问题
模式、正模式、可选模式、修饰模式 (PPOP) 我最后削掉了那个 PositivePattern
现在又变成了
顺序、直到、重复、选择 (SURD) 这些大结构没有变
单项、含于、符合 (IES) 的最后一个字母我给它加上了 StickyEnd,也算是解决了 Feed 模型的一个不被重视的问题
模式、正模式、可选模式、修饰模式 (PPOP) 我最后削掉了那个 PositivePattern
现在又变成了
POPCorn (都是助记的) 算是把 ConstantPattern 给合并到了基本模型里,方便 skip whitespaces 的 Pattern 的 rebuild。https://sap.github.io/chevrotain/docs/tutorial/step1_lexing.html#introduction
草写不出来了
SELECT column1 FROM table2
SELECT name, age FROM persons WHERE age > 100
val letter = elementIn('a'..'z', 'A'..'Z') or item('_')
// [a-zA-Z]\w*
val name = Seq(::StringTuple, letter.toStringPat(), stringFor(letter or digit))
// 0|[1-9]\d*
val int = Contextual
sealed class SQL {
// SELECT {Name} FROM Name (WHERE Expr)?
class Select(val columns: List<String>, val table: String, val filter: Where?): SQL()
class Where(val expr: Expr)
}
val keyword = KeywordPattern<Pattern<Char, > 草写不出来了
sap.github.io
Tutorial - Lexer | Chevrotain
Parser Building Toolkit for JavaScript
/tmp/duangsuse.sock
https://sap.github.io/chevrotain/docs/tutorial/step1_lexing.html#introduction SELECT column1 FROM table2 SELECT name, age FROM persons WHERE age > 100 val letter = elementIn('a'..'z', 'A'..'Z') or item('_') // [a-zA-Z]\w* val name = Seq(::StringTuple, le…
其实我们 ParserKt 是不兴包含 lexer 的架构的
ParserKt 实在是太 general-purpose 了,以至于都不好专门为 [a-z]\w 那种情况添加函数
ParserKt 实在是太 general-purpose 了,以至于都不好专门为 [a-z]\w 那种情况添加函数
https://tomassetti.me/parsing-in-javascript/#chevrotain
sealed class Token {
data class NamedConst(val name: String): Token()
}
fun KeywordPattern<Token>.mergeConsts(vararg names: String) = names.forEach { this[it] = Token.NamedConst(it) }
val namedConsts = KeywordPattern<Token>().apply {
mergeConsts("true", "false", "null")
}
val namedEscape = mapOf('b' to '\b', 't' to '\t', 'n' to '\n', 'r' to '\r', 'f' to '\u000C', 'v' to '\u000B')
val stringChar = Decide(!elementIn('"', '\n', '\\'), MapPattern(namedEscape) prefix item('\\')).mergeFirst {
if (it in namedEscape.values) 1 else 0 }
val string = SurroundBy(item('"') to item('"').clam {"unterminated string"}, stringFor(stringChar))Strumenta
Parsing in JavaScript: all the tools and libraries you can use
We present and compare all possible alternatives you can use to parse languages in JavaScript. From libraries to parser generators, we present all options
/tmp/duangsuse.sock
https://tomassetti.me/parsing-in-javascript/#chevrotain sealed class Token { data class NamedConst(val name: String): Token() } fun KeywordPattern<Token>.mergeConsts(vararg names: String) = names.forEach { this[it] = Token.NamedConst(it) } val namedConsts…
ParserKt 是 scannerless parsing 框架,目前对这样传统的 lexer-parser 结构 支持还不完善……
其实也可以用 iterator input,但着实是没好的 Pattern 去支持即时更新的 iterator
其实也可以用 iterator input,但着实是没好的 Pattern 去支持即时更新的 iterator
class BoolScanner(feed: Feed<Char>): LexerFeed<Int>(feed) {
override fun tokenizer() = KeywordPattern<Int>().apply { mergeStrings("true" to 1, "false" to 0) }
override val eof = -1
}