14

当谈到 OCaml 时,我是一个完整的新手。我最近才开始使用该语言(大约 2 周前),但不幸的是,我的任务是为一种组合语言制作一个语法分析器(解析器 + 词法分析器,其功能是接受或不接受句子)使用门希尔。现在,我在网上找到了一些关于 OCaml 和 Menhir 的资料:

Menhir手册。

这个网页是一些法国大学的课程。

在 Sourceforge 的 Toss 主页上的 Menhir 简短教程。

derdon 在 github 上的 Menhir 示例。

一本关于 OCaml 的书(关于 ocamllex+ocamlyacc 的一些内容

SooHyoung Oh 的随机 ocamllex 教程。

以及 Menhir 源代码附带的示例。

(我不能放两个以上的超链接,所以我不能把你直接链接到我在这里提到的一些网站。对不起!)

所以,正如你所看到的,我一直在拼命地寻找越来越多的材料来帮助我制作这个程序。不幸的是,我仍然无法掌握许多概念,因此我遇到了很多很多困难。

对于初学者,我不知道如何正确编译我的程序。我一直在使用以下命令:

ocamlbuild -use-menhir -menhir "menhir --external-tokens Tokens" main.native

我的程序分为四个不同的文件:main.ml;词法分析器.mll; 解析器.mly; 令牌.mly。main.ml 是从作为参数给出的文件系统中的文件获取输入的部分。

let filename = Sys.argv.(1)

let () =
    let inBuffer = open_in filename in
    let lineBuffer = Lexing.from_channel inBuffer in
    try
        let acceptance = Parser.main Lexer.main lineBuffer in
        match acceptance with
            | true -> print_string "Accepted!\n"
            | false -> print_string "Not accepted!\n"
    with
        | Lexer.Error msg -> Printf.fprintf stderr "%s%!\n" msg
        | Parser.Error -> Printf.fprintf stderr "At offset %d: syntax error.\n%!" (Lexing.lexeme_start lineBuffer)

第二个文件是 lexer.mll。

{
  open Tokens
  exception Error of string
}

rule main = parse
  | [' ' '\t']+
      { main lexbuf }
  | ['0'-'9']+ as integer
      { INT (int_of_string integer) }
  | "True"
      { BOOL true }
  | "False"
      { BOOL false }
  | '+'
      { PLUS }
  | '-'
      { MINUS }
  | '*'
      { TIMES }
  | '/'
      { DIVIDE }
  | "def"
      { DEF }
  | "int"
      { INTTYPE }
  | ['A'-'Z' 'a'-'z' '_']['0'-'9' 'A'-'Z' 'a'-'z' '_']* as s
      { ID (s) }
  | '('
      { LPAREN }
  | ')'
      { RPAREN }
  | '>'
      { LARGER }
  | '<'
      { SMALLER }
  | ">="
      { EQLARGER }
  | "<="
      { EQSMALLER }
  | "="
      { EQUAL }
  | "!="
      { NOTEQUAL }
  | '~'
      { NOT }
  | "&&"
      { AND }
  | "||"
      { OR }
  | '('
      { LPAREN }
  | ')'
      { RPAREN }
  | "writeint"
      { WRITEINT }
  | '\n'
      { EOL }
  | eof
      { EOF }
  | _
      { raise (Error (Printf.sprintf "At offset %d: unexpected character.\n" (Lexing.lexeme_start lexbuf))) }

第三个文件是 parser.mly。

%start <bool> main
%%

main:
| WRITEINT INT { true }

第四个是tokens.mly

%token <string> ID
%token <int> INT
%token <bool> BOOL
%token EOF EOL DEF INTTYPE LPAREN RPAREN WRITEINT
%token PLUS MINUS TIMES DIVIDE
%token LARGER SMALLER EQLARGER EQSMALLER EQUAL NOTEQUAL
%token NOT AND OR

%left OR
%left AND
%nonassoc NOT
%nonassoc LARGER SMALLER EQLARGER EQSMALLER EQUAL NOTEQUAL
%left PLUS MINUS
%left TIMES DIVIDE
%nonassoc LPAREN
%nonassoc ATTRIB

%{
type token =
  | ID of (string)
  | INT
  | BOOL
  | DEF
  | INTTYPE
  | LPAREN
  | RPAREN
  | WRITEINT
  | PLUS
  | MINUS
  | TIMES
  | DIVIDE
  | LARGER
  | SMALLER
  | EQLARGER
  | EQSMALLER
  | EQUAL
  | NOTEQUAL
  | NOT
  | AND
  | OR
  | EOF
  | EOL
%}

%%

现在,我知道这里有很多未使用的符号,但我打算在我的解析器中使用它们。不管我对文件做了多少修改,编译器总是在我脸上炸开。我已经尝试了我能想到的一切,但似乎没有任何效果。是什么让 ocamlbuild 在大量未绑定的构造函数和未定义的开始符号的错误中爆炸?我应该使用什么命令来正确编译程序?我在哪里可以找到有意义的材料来了解 Menhir?

4

3 回答 3

10

一个更简单的方法是删除Parser/Tokens分隔。正如 Thomas 所指出的,不需要声明type token = ...,因为它是由 menhir 根据%token指令自动生成的。

所以你可以定义parser.mly为:

%start <bool> main

%token <string> ID
%token <int> INT
%token <bool> BOOL
%token EOF EOL DEF INTTYPE LPAREN RPAREN WRITEINT
%token PLUS MINUS TIMES DIVIDE
%token LARGER SMALLER EQLARGER EQSMALLER EQUAL NOTEQUAL
%token NOT AND OR

%left OR
%left AND
%nonassoc NOT
%nonassoc LARGER SMALLER EQLARGER EQSMALLER EQUAL NOTEQUAL
%left PLUS MINUS
%left TIMES DIVIDE
%nonassoc LPAREN
%nonassoc ATTRIB
%%

main:
| WRITEINT INT { true }

并且lexer.mll作为:

{
  open Parser
  exception Error of string
}

[...] (* rest of the code not shown here *)

然后删除tokens.mly,并编译

ocamlbuild -use-menhir main.native

一切都很好。

于 2012-03-28T04:34:52.393 回答
7

所以首先,你不需要重复标记tokens.mly

%token <string> ID
%token <int> INT
%token <bool> BOOL
%token EOF EOL DEF INTTYPE LPAREN RPAREN WRITEINT
%token PLUS MINUS TIMES DIVIDE
%token LARGER SMALLER EQLARGER EQSMALLER EQUAL NOTEQUAL
%token NOT AND OR

%left OR
%left AND
%nonassoc NOT
%nonassoc LARGER SMALLER EQLARGER EQSMALLER EQUAL NOTEQUAL
%left PLUS MINUS
%left TIMES DIVIDE
%nonassoc LPAREN
%nonassoc ATTRIB

%%

然后,我不知道传递给的神奇选项,ocamlbuild我也不太清楚menhir,但是,据我了解,您需要将所有内容“打包”.mly到一个解析器单元中:

menhir tokens.mly parser.mly -base parser

然后,如果您替换任何出现的Tokenbyte Parserin lexer.mllocamlbuild -no-hygiene main.byte应该可以工作。但是请注意,可能有一种聪明的方法可以做到这一点。

于 2012-03-27T21:26:12.927 回答
1

我遇到了同样的问题,除了解析器需要当前直接之外的模块。我不知道如何调用 ocamlbuild 来指定解析器。{ml,mli} 必须从 3 个 mly 文件构建,所以我只是制作了一个 makefile:

  • 将模块 .cmi 从 _build 复制到当前目录(以满足 menhir --infer)
  • 调用竖井
  • 删除复制的模块以满足 ocamlbuild
  • 然后调用 ocamlbuild

我对此不满意,所以我对任何更好的选择感兴趣,但如果你真的必须以最小的努力完成你的项目,我想这就是要走的路

编辑:实际上,不需要复制和删除编译的模块,只需在第二步将选项传递给menhir:menhir --ocamlc "ocamlc -I \"../_build/modules/\"" --infer --基础解析器

可悲的是,这仍然意味着解析器的生成将是模块的先前编译,因此预计会发生不必要的(并且失败的)第一次编译。

于 2013-03-01T09:42:28.290 回答