2012-03-27 70 views
11

对于OCaml,我是一个完整的新手。我最近才开始使用该语言(大约2周前),但不幸的是,我的任务是编写语法分析器(解析器+词法分析器,其功能是接受或不是句子)使用Menhir。现在,我在网上找到了关于OCaml和Menhir的一些资料:OCaml + Menhir编译/写入

The Menhir手册。

This webpage for some French University course.

上折腾的主页在SourceForge短巨石教程。

由derdon在github上的Menhir示例。

A book on OCaml (with a few things about ocamllex+ocamlyacc

随机ocamllex教程由SooHyoung哦。

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

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

所以,你可以看到,我我一直在拼命寻找越来越多的材料来帮助我制作这个节目。不幸的是,我仍然无法理解许多概念,因此我遇到了许多困难。

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

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

我的程序分为四个不同的文件:main.ml; lexer.mll; parser.mly; tokens.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?

回答

8

更简单的方法是去除Parser/Tokens间隔。正如托马斯指出的,没有必要声明type token = ...,因为它是由%token指令的menhir自动生成的。

所以,你可以定义为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 

编译,这一切运作良好。

+0

事实上,只有一个'mly'就简单多了。我没有在我的答案中提出这个解决方案,因为我认为@Lopson想要使用menhir的“单独编译解析单元”功能。 – Thomas 2012-03-28 08:00:12

+0

感谢所有的帮助,各位,你不知道你的帖子对我来说有多宝贵!最后,事情开始变得有意义。 – 2012-03-28 11:19:19

7

因此,首先,你不需要repet的令牌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 

然后,如果你在lexer.mll取代Token任何发生BYT Parserocamlbuild -no-hygiene main.byte应该可以工作。但请注意,可能有一个聪明的方法来做到这一点。

1

我遇到了同样的问题,除了解析器需要当前直接之外的模块。我无法弄清楚如何调用ocamlbuild指定解析器{毫升,美林}必须从3个MLY文件生成,所以我只是做了一个生成文件:

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

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

编辑: 实际上,没有必要复制和删除编译模块,只需将选项传递给menhir第二步: menhir --ocamlc“ocamlc -I \”../_ build/modules/\“”--infer --base parser

不幸的是,这仍然意味着解析器的生成将与以前的编译模块,因此预期不必要的(并且失败的)第一编译。