
Pratt 解析中的数组索引处理
来源标题:Pratt parsing is a black box
文章展示了 Pratt 解析器里数组索引的 led 实现,与函数调用的 led 结构相似。代码解析方括号内的逗号分隔表达式列表,构建 ArrayIndexing 节点,并在遇到非逗号非右括号时抛出 unexpected_token 异常。
以下正文由 AI 翻译自 Hacker News(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
数组索引的 led 与函数调用的 led 非常相似。下面是我在自己解析器里使用的变体:
std::shared_ptrast::Expr parse_array_indexing( ParsingContext& context, reader tok, reader& r, std::shared_ptrast::Expr left) std::vector<std::shared_ptrast::Expr> indexes; if (r.code() != rbrack) { indexes.emplace_back(parse_expr(context, r, parse_until_end)); while (r.code() != rbrack) { if (!r.eatcode::comma()) { throw unexpected_token{r, /expected=/"]"}; indexes.emplace_back(parse_expr(context, r, parse_until_end)); assert(r.code() == rbrack); r.next_on_expr_scope(); return ast::make_exprast::ArrayIndexing( tok.line(), tok.column(), left, std::move(indexes)); std::shared_ptrast::Expr parse_array_indexing( ParsingContext& context, reader tok, reader& r, std::shared_ptrast::Expr left) std::vector<std::shared_ptrast::Expr> indexes; if (r.code() != rbrack) { indexes.emplace_back(parse_expr(context, r, parse_until_end)); while (r.code() != rbrack) { if (!r.eatcode::comma()) { throw unexpected_token{r, /expected=/"]"}; indexes.emplace_back(parse_expr(context, r, parse_until_end)); assert(r.code() == rbrack); r.next_on_expr_scope(); return ast::make_exprast::ArrayIndexing( tok.line(), tok.column(), left, std::move(indexes)); std::shared_ptrast::Expr parse_array_indexing( ParsingContext& context, reader tok, reader& r, std::shared_ptrast::Expr left) std::vector<std::shared_ptrast::Expr> indexes; if (r.code() != rbrack) { indexes.emplace_back(parse_expr(context, r, parse_until_end)); while (r.code() != rbrack) { if (!r.eatcode::comma()) { throw unexpected_token{r, /expected=/"]"}; indexes.emplace_back(parse_expr(context, r, parse_until_end)); assert(r.code() == rbrack); r.next_on_expr_scope(); return ast::make_exprast::ArrayIndexing( tok.line(), tok.column(), left, std::move(indexes)); std::shared_ptrast::Expr parse_array_indexing( ParsingContext& context, reader tok, reader& r, std::shared_ptrast::Expr left) std::vector<std::shared_ptrast::Expr> indexes; if (r.code() != rbrack) { indexes.emplace_back(parse_expr(context, r, parse_until_end)); while (r.code() != rbrack) { if (!r.eatcode::comma()) { throw unexpected_token{r, /expected=/"]"}; indexes.emplace_back(parse_expr(context, r, parse_until_end)); assert(r.code() == rbrack); r.next_on_expr_scope(); return ast::make_exprast::ArrayIndexing( tok.line(), tok.column(), left, std::move(indexes));
Pratt 并不只是优先级爬升(precedence climbing)。Pratt 是一种组织代码的方式。先把 Pratt 当作黑箱来学习,实际上有助于理解这一区别。如果你无法接触黑箱内部,就不会忍不住把 Pratt 简化为优先级爬升。希望等到你想揭开抽象、看看里面究竟是什么的时候,你不会犯下把 Pratt 仅仅当作优先级爬升变体的错误,因为到那时你已经见识过 Pratt 解析所释放的全部威力了。Andy Chu 那篇关于不使用原型继承的 Pratt 解析的博客文章值得一读,因为它对比了编写 Pratt 解析器的两种对立风格²。下面是一个用 Rhombus 编写的极简计算器,其风格更接近使用原型继承的版本,而不是 Andy Chu 展示的那种风格:
#lang rhombus import: parser/lex open class Token(~nud: nud_impl = #false, ~led: led_impl = #false, ~lbp = 0): nonfinal method nud(): nud_impl(this) method led(lhs): led_impl(this, lhs) method prefix(): expression(30) method rhs(): expression(this.lbp) class RParen(): extends Token def lex: lexer | "+": Token( ~nud: (.prefix()), ~led: fun(tok, lhs): lhs + tok.rhs(), ~lbp: 10) | "-": Token( ~nud: (-.prefix()), ~led: fun(tok, lhs): lhs - tok.rhs(), ~lbp: 10) | "": Token( ~led: fun(tok, lhs): lhs * tok.rhs(), ~lbp: 20) | "/": Token( ~led: fun(tok, lhs): lhs / tok.rhs(), ~lbp: 20) | digit+: Token(~nud: fun(): String.to_int(lexeme)) | " "+: lex(input_port) | "(": Token( ~nud: fun(): let ret = expression() guard token is_a RParen | error("expected closing parens") advance() ret) | ")": RParen() | ~eof: Token() def input = Port.Input.open_string("4 * (3 - 3) / 2 + -10") def mutable token = lex(input) fun advance(): token := lex(input) fun expression(rbp = 0): let mutable t = token advance() let mutable left = t.nud() while rbp < token.lbp: t := token advance() left := t.led(left) left println(expression()) #lang rhombus import: parser/lex open class Token(~nud: nud_impl = #false, ~led: led_impl = #false, ~lbp = 0): nonfinal method nud(): nud_impl(this) method led(lhs): led_impl(this, lhs) method prefix(): expression(30) method rhs(): expression(this.lbp) class RParen(): extends Token def lex: lexer | "+": Token( ~nud: (.prefix()), ~led: fun(tok, lhs): lhs + tok.rhs(), ~lbp: 10) | "-": Token( ~nud: (-.prefix()), ~led: fun(tok, lhs): lhs - tok.rhs(), ~lbp: 10) | "": Token( ~led: fun(tok, lhs): lhs * tok.rhs(), ~lbp: 20) | "/": Token( ~led: fun(tok, lhs): lhs / tok.rhs(), ~lbp: 20) | digit+: Token(~nud: fun(): String.to_int(lexeme)) | " "+: lex(input_port) | "(": Token( ~nud: fun(): let ret = expression() guard token is_a RParen | error("expected closing parens") advance() ret) | ")": RParen() | ~eof: Token() def input = Port.Input.open_string("4 * (3 - 3) / 2 + -10") def mutable token = lex(input) fun advance(): token := lex(input) fun expression(rbp = 0): let mutable t = token advance() let mutable left = t.nud() while rbp < token.lbp: t := token advance() left := t.led(left) left println(expression()) #lang rhombus import: parser/lex open class Token(~nud: nud_impl = #false, ~led: led_impl = #false, ~lbp = 0): nonfinal method nud(): nud_impl(this) method led(lhs): led_impl(this, lhs) method prefix(): expression(30) method rhs(): expression(this.lbp) class RParen(): extends Token
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。