如何将状态添加到 Racket 中的词法分析器?
How can I add state to a lexer in Racket?
我正在尝试为 Racket 中的 sed 语言编写词法分析器(例如 "s/find/replace/")。我遇到的一个问题是处理许多令牌没有确定形式并且可以更改的事实。例如,我可以将上面的例子写成 "ssfindsreplaces",其中使用字母 's' 而不是 '/'。
我已经开始编写词法分析器,例如,
(define sed-lexer
(lexer-srcloc
["\n" (token 'NEWLINE lexeme)]
["/" (token 'DIVIDER lexeme]
[(:or "s" "y" "d" "p") (token 'CMD lexeme)]
[(:* (complement "/") (token 'LITERAL lexeme)]))
但这在多个层面上都失败了:
- 命令只能放在开头(在这个简化的例子中)。读取命令后,我想忽略命令大小写直到换行符。
- 不能始终将 DIVIDER 标记设置为斜杠。
我可以想象这个问题的解决方案是向这个词法分析器添加状态。因此,例如,词法分析器从 'start' 状态开始,它会在其中寻找命令,然后进入 'divider1' 状态,寻找分隔符。这样的功能似乎存在于这里http://pygments.org/docs/lexerdevelopment/。鉴于 Racket 生态系统中的工具,解决此问题的最佳方法是什么?
词法分析器只是一个使用输入端口和 returns 令牌的函数。如果(br-)parser-tools/lex
对你来说还不够,你可以自己写(应该不难)。
从理论上讲,有限状态机和正则表达式的表现力是一样的,所以我认为您实际上可以使用 parser-tools/lex
来完成您想要的。由于您需要在所有可能的 "divider" 上拆分大小写(因为纯正则表达式没有反向引用能力),所以它看起来真的很乏味。我想你提到的pygments也会有类似的问题。
另一种可能性是使用比正则表达式更强大的东西。由于 sed 语法非常简单,您甚至可以立即解析它而无需先进行词法分析。这是我使用 megaparsack
、一个解析器组合器库
快速编写的糟糕版本
#lang racket
(require megaparsack megaparsack/text
data/monad data/applicative)
(struct substitution (search replace flags) #:transparent)
(define substitution/p (do (char/p #\s)
[divider <- any-char/p]
[search <- (many/p (char-not/p divider))]
(char/p divider)
[replace <- (many/p (char-not/p divider))]
(char/p divider)
[flags <- (many/p (char-in/p "gIp"))]
(pure (substitution search replace flags))))
(define dummy-command/p (string/p "dummy-command"))
(define line/p (or/p substitution/p
dummy-command/p))
(define program/p (do [result <- (many/p line/p #:sep (char/p #\newline))]
eof/p
(pure result)))
(pretty-print
(parse-result!
(parse-string program/p
"s/hello/world/\ndummy-command\ns|search|replace|gp")))
#|
Result:
(list
(substitution '(#\h #\e #\l #\l #\o) '(#\w #\o #\r #\l #\d) '())
"dummy-command"
(substitution
'(#\s #\e #\a #\r #\c #\h)
'(#\r #\e #\p #\l #\a #\c #\e)
'(#\g #\p)))
|#
我正在尝试为 Racket 中的 sed 语言编写词法分析器(例如 "s/find/replace/")。我遇到的一个问题是处理许多令牌没有确定形式并且可以更改的事实。例如,我可以将上面的例子写成 "ssfindsreplaces",其中使用字母 's' 而不是 '/'。
我已经开始编写词法分析器,例如,
(define sed-lexer
(lexer-srcloc
["\n" (token 'NEWLINE lexeme)]
["/" (token 'DIVIDER lexeme]
[(:or "s" "y" "d" "p") (token 'CMD lexeme)]
[(:* (complement "/") (token 'LITERAL lexeme)]))
但这在多个层面上都失败了:
- 命令只能放在开头(在这个简化的例子中)。读取命令后,我想忽略命令大小写直到换行符。
- 不能始终将 DIVIDER 标记设置为斜杠。
我可以想象这个问题的解决方案是向这个词法分析器添加状态。因此,例如,词法分析器从 'start' 状态开始,它会在其中寻找命令,然后进入 'divider1' 状态,寻找分隔符。这样的功能似乎存在于这里http://pygments.org/docs/lexerdevelopment/。鉴于 Racket 生态系统中的工具,解决此问题的最佳方法是什么?
词法分析器只是一个使用输入端口和 returns 令牌的函数。如果(br-)parser-tools/lex
对你来说还不够,你可以自己写(应该不难)。
从理论上讲,有限状态机和正则表达式的表现力是一样的,所以我认为您实际上可以使用 parser-tools/lex
来完成您想要的。由于您需要在所有可能的 "divider" 上拆分大小写(因为纯正则表达式没有反向引用能力),所以它看起来真的很乏味。我想你提到的pygments也会有类似的问题。
另一种可能性是使用比正则表达式更强大的东西。由于 sed 语法非常简单,您甚至可以立即解析它而无需先进行词法分析。这是我使用 megaparsack
、一个解析器组合器库
#lang racket
(require megaparsack megaparsack/text
data/monad data/applicative)
(struct substitution (search replace flags) #:transparent)
(define substitution/p (do (char/p #\s)
[divider <- any-char/p]
[search <- (many/p (char-not/p divider))]
(char/p divider)
[replace <- (many/p (char-not/p divider))]
(char/p divider)
[flags <- (many/p (char-in/p "gIp"))]
(pure (substitution search replace flags))))
(define dummy-command/p (string/p "dummy-command"))
(define line/p (or/p substitution/p
dummy-command/p))
(define program/p (do [result <- (many/p line/p #:sep (char/p #\newline))]
eof/p
(pure result)))
(pretty-print
(parse-result!
(parse-string program/p
"s/hello/world/\ndummy-command\ns|search|replace|gp")))
#|
Result:
(list
(substitution '(#\h #\e #\l #\l #\o) '(#\w #\o #\r #\l #\d) '())
"dummy-command"
(substitution
'(#\s #\e #\a #\r #\c #\h)
'(#\r #\e #\p #\l #\a #\c #\e)
'(#\g #\p)))
|#