我不太明白这里使用的语法:
let rec lex = parser
(* Skip any whitespace. *)
| [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream
首先,我不明白使用警卫(垂直线)后跟parser
. 其次,我似乎找不到[<
和包围的条件的相关语法>]
从这里得到代码。提前致谢!
|
表示:“或”(流是否匹配此字符或此字符或...?)
| [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream
方法:
(顺便说一句,添加 '\n\r',即“换行符 + 回车”会更好地解决这个历史案例;您可以将其作为练习)。
为了能够使用这种语法解析 OCaml 中的流,您需要来自 OCaml stdlib 的模块(至少是 Stream 和 Buffer),并且您需要知道关键字parser
、 [<'
等含义的camlp4或camlp5语法扩展系统。在您的顶层,您可以执行以下操作:
#use "topfind";; (* useless if already in your ~/.ocamlinit file *)
#camlp4o;; (* Topfind directive to load camlp4o in the Toplevel *)
# let st = Stream.of_string "OCaml"
val st : char Stream.t = <abstr>
# Stream.next st
- : char = 'O'
# Stream.next flux_car
- : char = 'C'
(* btw, Exception: Stdlib.Stream.Failure must be handled(empty stream) *)
# let rec lex = parser
| [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream
| [< >] -> [< >]
(* just the beginning of the parser definition *)
# val lex : char Stream.t -> 'a = <fun>
现在您可以开始处理流和 LL(1) 流解析器了。您提到的示例效果很好。如果您在 Toplevel 中玩游戏,您可以使用 #use 指令评估 token.ml 和 lexer.ml 文件以尊重模块名称(#use "token.ml")。或者,如果将类型标记嵌套在模块标记中,则可以直接评估 lexer.ml 的表达式。
# let rec lex = parser (* complete definition *)
val lex : char Stream.t -> Token.token Stream.t = <fun>
val lex_number : Buffer.t -> char Stream.t -> Token.token Stream.t = <fun>
val lex_ident : Buffer.t -> char Stream.t -> Token.token Stream.t = <fun>
val lex_comment : char Stream.t -> Token.token Stream.t = <fun>
# let pgm =
"def fib(x) \
if x < 3 then \
1 \
else \
fib(x-1)+fib(x-2)";;
val pgm : string = "def fib(x) if x < 3 then 1 else fib(x-1)+fib(x-2)"
# let cs' = lex (Stream.of_string pgm);;
val cs' : Token.token Stream.t = <abstr>
# Stream.next cs';;
- : Token.token = Token.Def
# Stream.next cs';;
- : Token.token = Token.Ident "fib"
# Stream.next cs';;
- : Token.token = Token.Kwd '('
# Stream.next cs';;
- : Token.token = Token.Ident "x"
# Stream.next cs';;
- : Token.token = Token.Kwd ')'
您将获得预期的令牌类型流。
现在介绍一些关于camlp4 和camlp5 的技术词汇。
确实建议不要使用被弃用的所谓“camlp4”,而是使用“camlp5”,它实际上是“正版camlp4”(见下文)。假设您想使用 LL(1) 解析器。为此,您可以使用以下 camlp5 Toplevel 指令而不是 camlp4 指令:
#require "camlp5";; (* add the path + loads the module (topfind directive) *)
#load "camlp5o.cma";;(* patch: manually loads camlp50 module,
because #require forgets to do it (why?)
"o" in "camlp5o" stands for "original syntax" *)
let rec lex = parser
| [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream
| [< >] -> [< >]
# val lex : char Stream.t -> 'a = <fun>
更多关于camlp4和camlp5的历史。
免责声明:虽然我尽量保持中立和真实,但这个太短的解释可能也反映了我的个人观点。当然,欢迎讨论。作为一名 Ocaml 初学者,我发现camlp4 非常有吸引力且功能强大,但很难区分到底是什么camlp4 并找到它最新的文档。简而言之:这是一个古老而混乱的故事,主要是因为“camlp4”的命名。campl4 是 OCaml 的历史语法扩展系统。有人决定在 2006 年左右改进/改造camlp4,但似乎一些设计决定将它变成了某种被某些人视为“野兽”的东西(通常,少即是多)。所以,它有效,但是“引擎盖下有很多东西”(它的签名变得非常大)。他的历史作家,Daniel de Rauglaudre 决定继续按照自己的方式开发camlp4,并将其重命名为“campl5”,以区别于“新的camlp4”(命名为camlp4)。即使camlp5没有被大量使用,它仍然被维护、运行和使用,例如,最近集成了campl5的一部分而不是依赖于整个camlp5库的coq(这并不意味着“coq不不再使用camlp5”,如您所读)。ppx 已经成为 OCaml 界的主流语法扩展技术(看来是专门做“有限且可靠”的 OCaml 语法扩展,主要用于小而有用的代码生成(helpers 函数等);这是一个边讨论) . 这并不意味着 camlp5 已“弃用”。camlp5 肯定被误解了。一开始我很艰难,主要是因为它的文档。我希望我能在那个时候看到这篇文章!不管怎样,在OCaml编程的时候,我相信探索各种技术是件好事。由你来发表你的意见。
所以,今天所谓的“camlp4”其实就是“老campl4”(或者说“过去的新camlp4”,我知道,很复杂)。诸如 ocamlyacc 或 menhir 之类的 LALR(1) 解析器已经或已经成为主流。他们有一个自底向上的方法(定义 .mll 和 .mly,然后编译成 OCaml 代码)。LL(1) 解析器,例如camlp4/camlp5,采用自顶向下的方法,非常接近函数式风格。最好的事情是你自己比较。实现您的语言的词法分析器/解析器非常适合:使用 ocamllex/menhir 和 ocamllex/camlp5,甚至仅使用 camlp5,因为它也是一个词法分析器(具有优点/缺点)。
我希望你会喜欢你的 LLVM 教程。
非常欢迎所有技术和历史补充评论。
正如@glennsl 所说,此页面使用campl4 预处理器,OCaml 社区中的许多人认为该预处理器已过时。
这是 2019 年 8 月的论坛消息,描述了如何从 camlp4 迁移到更新的 ppx:
不幸的是,这并不能真正帮助您了解 LLVM 页面试图教给您的内容,这似乎与 OCaml 没什么关系。
这是我发现使用语法扩展存在问题的原因之一。他们没有基础语言的持久力。
(另一方面,OCaml 确实是编写编译器和其他语言工具的绝佳语言。)