2016-07-21 7 views
0

J'ai un frontend écrit en menhir qui essaie d'analyser une expression: d'une chaîne à une expression AST. Le point d'entrée du frontal Parser_e.main est appelé à plusieurs endroits différents dans mon code OCaml. Donc, je voudrais être en mesure d'attraper les erreurs possibles à l'intérieur de l'interface plutôt qu'à l'extérieur. Lorsque j'attrape une erreur, une information importante particulière que je veux montrer est la chaîne entière entrée que le frontend ne peut pas analyser. (Les erreurs de la lexer sont très rares, car le frontal peut presque tout lire).Récupère la chaîne d'entrée qui soulève l'erreur d'analyse à l'intérieur de l'analyseur

J'ai donc essayé de suivre this thread et d'imprimer plus d'informations en cas d'erreur. En parser_e.mly, j'ai ajouté

exception LexErr of string 
exception ParseErr of string 

let error msg start finish = 
    Printf.sprintf "(line %d: char %d..%d): %s" start.pos_lnum 
     (start.pos_cnum - start.pos_bol) (finish.pos_cnum - finish.pos_bol) msg 

let parse_error msg nterm = 
    raise (ParseErr (error msg (rhs_start_pos nterm) (rhs_end_pos nterm))) 

e_expression: 
/* empty */ { EE_empty } 
| INTEGER { EE_integer $1 } 
| DOUBLE { EE_double $1 } 
... 
| error { parse_error "e_expression" 1; ERR "" } 

Mais il n'a toujours pas la chaîne d'entrée comme information. Est-ce que quelqu'un s'il y a une fonction qui me manque pour l'obtenir?

Répondre

1

Dans le contexte d'une erreur, vous pouvez extraire un emplacement de lexème défaillant dans un format à deux positions, en utilisant les fonctions Parsing.symbol_start_pos et Parsing.symbol_end_pos. Malheureusement, le module Parsing ne fournit pas vraiment un accès au lexème sous forme de chaîne, mais si l'entrée a été stockée dans un fichier, il est possible de l'extraire manuellement ou d'imprimer une erreur dans un compilateur. manuellement. Un module Parser_error est ci-dessous. Il définit la fonction Parser_error.throw qui déclenchera une exception Parser_error.T. L'exception porte sur un message de diagnostic et une position d'un lexème défectueux. Plusieurs fonctions pratiques sont fournies pour extraire ce lexème d'un fichier ou pour générer un message de fichier. Si votre entrée n'est pas stockée dans un fichier, vous pouvez utiliser la fonction string_of_exn qui accepte l'entrée en tant que chaîne et l'exception Parser_error.T, et en extrait la sous-chaîne incriminée. Ceci est un example d'un analyseur qui utilise cette exception pour les rapports d'erreurs.

open Lexing 

(** T(message,start,finish) parser failed with a [message] on an 
    input specified by [start] and [finish] position.*) 
exception T of (string * position * position) 

(** [throw msg] raise a [Parser_error.T] exception with corresponding 
    message. Must be called in a semantic action of a production rule *) 
let throw my_unique_msg = 
    let check_pos f = try f() with _ -> dummy_pos in 
    Printexc.(print_raw_backtrace stderr (get_raw_backtrace())); 
    let sp = check_pos Parsing.symbol_start_pos in 
    let ep = check_pos Parsing.symbol_end_pos in 
    raise (T (my_unique_msg,sp,ep)) 

(** [fileposition start finish] creates a string describing a position 
    of an lexeme specified by [start] and [finish] file positions. The 
    message has the same format as OCaml and GNU compilers, so it is 
    recognized by most IDE, e.g., Emacs. *) 
let fileposition err_s err_e = 
    Printf.sprintf 
    "\nFile \"%s\", line %d, at character %d-%d\n" 
    err_s.pos_fname err_s.pos_lnum err_s.pos_cnum err_e.pos_cnum 

(** [string_of_exn line exn] given a [line] in a file, extract a failed 
    lexeme form the exception [exn] and create a string denoting the 
    parsing error in a format similar to the format used by OCaml 
    compiler, i.e., with fancy underlying. *) 
let string_of_exn line (msg,err_s,err_e) = 
    let b = Buffer.create 42 in 
    if err_s.pos_fname <> "" then 
    Buffer.add_string b (fileposition err_s err_e); 
    Buffer.add_string b 
    (Printf.sprintf "Parse error: %s\n%s\n" msg line); 
    let start = max 0 (err_s.pos_cnum - err_s.pos_bol) in 
    for i=1 to start do 
    Buffer.add_char b ' ' 
    done; 
    let diff = max 1 (err_e.pos_cnum - err_s.pos_cnum) in 
    for i=1 to diff do 
    Buffer.add_char b '^' 
    done; 
    Buffer.contents b 

(** [extract_line err] a helper function that will extract a line from 
    a file designated by the parsing error exception *) 
let extract_line err = 
    let line = ref "" in 
    try 
    let ic = open_in err.pos_fname in 
    for i=0 to max 0 (err.pos_lnum - 1) do 
     line := input_line ic 
    done; 
    close_in ic; 
    !line 
    with exn -> !line 

(** [to_string exn] converts an exception to a string *) 
let to_string ((msg,err,_) as exn) = 
    let line = extract_line err in 
    string_of_exn line exn 

Voici un exemple qui montre comment utiliser au cas où il n'y a pas de fichier, et l'entrée est à partir d'un flux ou interactif (coquille) source:

let parse_command line = 
    try 
    let lbuf = Lexing.from_string line in 
    `Ok Parser.statement Lexer.tokens lbuf 
    with 
    | Parsing.Parse_error -> `Fail "Parse error" 
    | Parser_error.T exn -> `Fail (Parser_error.string_of_exn line exn) 
+0

Compte tenu d'une ligne de chaîne en entrée, vos fonctions renvoient la ** sous-chaîne ** précise qui soulève des erreurs, alors que je demandais comment montrer toute la chaîne d'entrée. Mais je pense que ma question initiale est assez simple: on pourrait juste enrouler une erreur de manipulation autour de 'Parser_e.main' ou' Parse.statement' et toujours appeler le wrapper ... Bon à savoir votre exemple et module qui est plus précis .. – SoftTimur

+0

Cela ne serait pas possible, car l'analyseur ne le sait pas tout seul. Au moment de l'échec, il se trouve dans un état où il n'y a plus de transitions. L'historique de la façon dont il a fini dans cet état n'est pas stocké. Vous pouvez activer le mode de débogage et imprimer cet historique, mais cela est différent de la création d'une erreur d'analyse pour un utilisateur final. – ivg