2012-03-27 22 views
11

Sono un novizio completo quando si tratta di OCaml. Recentemente ho iniziato a usare il linguaggio (circa 2 settimane fa), ma sfortunatamente, sono stato incaricato di creare un analizzatore di sintassi (parser + lexer, la cui funzione è accettare o meno una frase) per un linguaggio inventato usando Menhir. Ora, ho trovato alcuni materiali su Internet relativi a OCaml e Menhir:OCaml + Menhir Compiling/Writing

Il manuale di Menhir.

This webpage for some French University course.

Un breve tutorial Menhir sulla homepage di Toss a Sourceforge.

Un esempio di menhir su github by derdon.

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

Un tutorial ocamllex casuale da SooHyoung Oh.

E gli esempi forniti con il codice sorgente di Menhir.

(non riesco a mettere più di due collegamenti ipertestuali, quindi non è possibile collegare direttamente ad alcuni dei siti web che sto citano qui. Spiacente!)

Quindi, come potete vedere, I' Ho cercato disperatamente sempre più materiale per aiutarmi nella realizzazione di questo programma. Sfortunatamente, non riesco ancora a cogliere molti concetti e, come tale, sto avendo molte, molte difficoltà.

Per i principianti, non ho idea di come compilare correttamente il mio programma. Sto usando il seguente comando:

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

Il mio programma è diviso in quattro file diversi: main.ml; lexer.mll; parser.mly; tokens.mly. main.ml è la parte che riceve un input da un file nel file system dato come argomento.

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) 

Il secondo file è 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))) } 

Il terzo file è parser.mly.

%start <bool> main 
%% 

main: 
| WRITEINT INT { true } 

Il quarto è 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 
%} 

%% 

Ora, so che c'è un sacco di simboli inutilizzati qui, ma ho intenzione di usarli nel mio parser. Indipendentemente dal numero di modifiche apportate ai file, il compilatore continua a saltare in faccia. Ho provato tutto quello che riesco a pensare e niente sembra funzionare. Che cosa sta facendo esplodere ocamlbuild in una pletora di errori di costruttori non associati e simboli di inizio non definiti? Quale comando dovrei usare per compilare correttamente il programma? Dove posso trovare materiali significativi per conoscere Menhir?

risposta

8

Un modo più semplice per eseguire questa operazione è rimuovere la separazione Parser/Tokens. Come ha notato Thomas, non c'è bisogno di una dichiarazione type token = ..., perché viene prodotta automaticamente da menhir dalle direttive %token.

Così si può definire come 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 } 

e lexer.mll come:

{ 
    open Parser 
    exception Error of string 
} 

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

quindi rimuovere tokens.mly, e compilare con

ocamlbuild -use-menhir main.native 

e tutto funziona bene.

+0

Infatti, avere solo un 'mly' è molto più semplice. Non ho proposto questa soluzione nella mia risposta perché stavo partendo dal presupposto che @Lopson volesse utilizzare la funzione "compilazione separata delle unità di analisi" di menhir. – Thomas

+0

Grazie per tutto l'aiuto, ragazzi, non avete idea di quanto siano preziosi i vostri post per me! Finalmente le cose cominciano a dare un senso. –

7

Quindi, prima, non c'è bisogno di repet i gettoni in 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 

%% 

Poi, non so l'opzione magica per passare a ocamlbuild e non so menhir molto bene, ma, nella mia comprensione è necessario "pacchetto" tutto il .mly in una sola unità parser:

menhir tokens.mly parser.mly -base parser 

Quindi, se si sostituisce ogni occorrenza di Token BYT Parser in lexer.mll, ocamlbuild -no-hygiene main.byte dovrebbe funzionare. Si noti tuttavia che esiste forse un modo intelligente per farlo.

1

Mi sono imbattuto nello stesso problema, tranne che in aggiunta il parser aveva bisogno di moduli al di fuori della diretta corrente. . Non riuscivo a capire come richiamare ocamlbuild per specificare che parser {ml, MLI} doveva essere costruito da 3 file MLY, così ho semplicemente fatto un makefile che:

  • copie dei moduli .cmi da _build nella directory corrente (per soddisfare menhir --infer)
  • invocano menhir
  • rimuovere i moduli copiati per soddisfare ocamlbuild
  • quindi richiamare ocamlbuild

non sono soddisfatto con esso, così mi sono interessato in ogni migliore alt ernativo, ma se davvero devi finire il tuo progetto con il minimo sforzo, suppongo che sia la strada da percorrere

edit: In realtà, non è necessario copiare e rimuovere i moduli compilati, basta passare l'opzione a menhir a il secondo passo: menhir --ocamlc "ocamlc -I \" ../_ build/modules/\ "" --infer --base parser

Purtroppo, questo stills significa che la generazione del parser si occuperà del precedente compilazione dei moduli, quindi è da aspettarsi una prima compilazione non necessaria (e fallita).