OCaml 保护值后的语法

OCaml guards syntax after a value

我不太明白这里使用的语法:

let rec lex = parser
  (* Skip any whitespace. *)
  | [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream

首先,我不明白在parser后面使用守卫(竖线)是什么意思。 其次,我似乎找不到 [<>]

包围的条件的相关语法

here 获得代码。提前致谢!

正如@glennsl 所说,此页面使用 campl4 预处理器,OCaml 社区中的许多人认为它已过时。

这是 2019 年 8 月的一条论坛消息,描述了如何从 camlp4 转移到更新的 ppx:

The end of campl4

不幸的是,这并不能真正帮助您了解 LLVM 页面试图教给您的内容,这似乎与 OCaml 没什么关系。

这是我发现使用语法扩展存在问题的原因之一。他们没有基础语言的持久力。

(另一方面,OCaml 确实是编写编译器和其他语言工具的绝佳语言。)

| 

表示:"or"(流是否匹配此字符或此字符或...?)

| [< ' (' ' | '\n' | '\r' | '\t'); stream >] -> lex stream

表示:

  • IF 流(一个字符,在本子句中,但它可以是一系列 几个字符)匹配 "space" 或 "new line" 或 "carriage return" 或 "tabulation".
  • THEN 使用 ("white") 匹配字符并调用 lex 流的其余部分。
  • ELSE 使用下一个子句(在您的示例中:“过滤 A 到 Z 和 a 到 z 字符”作为标识符)。因为匹配的字符已被消耗 根据这个条款,

(顺便说一句,添加“\n\r”,即 "newline + carriage return" 会更好地解决这个历史案例;您可以将其作为练习)。

为了能够使用此语法解析 OCaml 中的流,您需要来自 OCaml stdlib 的模块(至少是 Stream 和 Buffer),并且需要知道关键字含义的 camlp4 或 camlp5 语法扩展系统 parser[<' 等。 在您的顶层,您可以执行以下操作:

#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) 流解析器。 您提到的 exammple 效果很好。如果您在 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",它实际上是 "genuine 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 的 a/the 历史语法扩展系统。有人决定在 2006 年左右 improve/retrofit camlp4,但似乎某些设计决定将其变成了某些人以某种方式将其视为 "beast" 的东西(通常,少即是多)。因此,它可以工作,但是 "there is a lot of stuff under the hood"(它的签名变得非常大)。 他的历史作者 Daniel de Rauglaudre 决定继续以他的方式开发 camlp4 并将其重命名为 "campl5" 以区别于 "new camlp4"(名为 camlp4)。即使 camlp5 没有被大量使用,它仍然被维护、操作和使用,例如,coq 最近集成了 campl5 的一部分,而不是依赖于整个 camlp5 库(这并不意味着 "coq doesn't use camlp5 anymore" ,如您所见)。 ppx已经成为OCaml界的主流语法扩展技术(好像是专门做"limited and reliable" OCaml语法扩展的,主要是小而非常有用的代码生成(helpers函数等);算是旁白) .这并不意味着 camlp5 是 "deprecated"。 camlp5 肯定被误解了。一开始我很难过,主要是因为它的文档。真希望那个时候能读到这篇post!无论如何,在使用 OCaml 编程时,我相信探索各种技术是一件好事。由你来发表意见。

所以,今天所谓的"camlp4"实际上是"old campl4"(或者"new camlp4 of the past";我知道,这很复杂)。 LALR(1) 解析器,例如 ocamlyacc 或 menhir 已经成为或已经成为主流。他们采用自下而上的方法(定义 .mll 和 .mly,然后编译为 OCaml 代码)。 LL(1) 解析器,例如 camlp4/camlp5,采用自上而下的方法,非常接近函数式风格。 最好的事情是你自己比较。实现你的语言的 lexer/parser 是完美的:使用 ocamllex/menhir 和 ocamllex/camlp5,甚至只使用 camlp5 因为它也是一个词法分析器(使用 pros/cons)。

希望您会喜欢 LLVM 教程。

非常欢迎所有技术和历史补充评论。