mirror of
https://github.com/CatalaLang/catala.git
synced 2024-11-08 07:51:43 +03:00
Better indications for the user :
- We're able to say from the parser what the user could have written. It may not be complete due to the acceptable function of Menhir : it is only an indication given to the user (and not intended to be an adaptive documentation) - .mli file added : module interface for suggestions - Add a test that provides a typographical or a logical error. - Documentation and type / name changes for suggestions
This commit is contained in:
parent
c96c6e187f
commit
94f8eac858
@ -165,7 +165,7 @@ module Content = struct
|
|||||||
| MainMessage msg -> msg ppf
|
| MainMessage msg -> msg ppf
|
||||||
| Result msg -> msg ppf
|
| Result msg -> msg ppf
|
||||||
| Suggestion suggestions_list ->
|
| Suggestion suggestions_list ->
|
||||||
Suggestions.display ppf suggestions_list)
|
Suggestions.format ppf suggestions_list)
|
||||||
ppf message_elements)
|
ppf message_elements)
|
||||||
content
|
content
|
||||||
| Cli.GNU ->
|
| Cli.GNU ->
|
||||||
@ -177,35 +177,40 @@ module Content = struct
|
|||||||
let ppf = get_ppf target in
|
let ppf = get_ppf target in
|
||||||
Format.pp_print_list ~pp_sep:Format.pp_print_newline
|
Format.pp_print_list ~pp_sep:Format.pp_print_newline
|
||||||
(fun ppf elt ->
|
(fun ppf elt ->
|
||||||
let pos, message = match elt with
|
let pos, message =
|
||||||
| MainMessage m ->
|
match elt with
|
||||||
let pos =
|
| MainMessage m ->
|
||||||
List.find_map (function
|
let pos =
|
||||||
| Position {pos_message = None; pos} -> Some pos
|
List.find_map
|
||||||
| _ -> None) content
|
(function
|
||||||
|> function
|
| Position { pos_message = None; pos } -> Some pos
|
||||||
| None ->
|
| _ -> None)
|
||||||
List.find_map (function
|
content
|
||||||
| Position {pos_message = _; pos} -> Some pos
|
|> function
|
||||||
| _ -> None) content
|
| None ->
|
||||||
| some -> some
|
List.find_map
|
||||||
in
|
(function
|
||||||
pos, m
|
| Position { pos_message = _; pos } -> Some pos
|
||||||
| Position { pos_message; pos } ->
|
| _ -> None)
|
||||||
let message = match pos_message with
|
content
|
||||||
| Some m -> m
|
| some -> some
|
||||||
| None -> fun _ -> ()
|
in
|
||||||
in
|
pos, m
|
||||||
(Some pos), message
|
| Position { pos_message; pos } ->
|
||||||
| Result m -> None, m
|
let message =
|
||||||
| Suggestion sl -> None, fun ppf -> Suggestions.display ppf sl
|
match pos_message with Some m -> m | None -> fun _ -> ()
|
||||||
in
|
in
|
||||||
Option.iter (fun pos -> Format.fprintf ppf "@{<blue>%s@}: "
|
Some pos, message
|
||||||
(Pos.to_string_short pos))
|
| Result m -> None, m
|
||||||
pos;
|
| Suggestion sl -> None, fun ppf -> Suggestions.format ppf sl
|
||||||
pp_marker target ppf;
|
in
|
||||||
Format.pp_print_char ppf ' ';
|
Option.iter
|
||||||
Format.pp_print_string ppf (unformat message))
|
(fun pos ->
|
||||||
|
Format.fprintf ppf "@{<blue>%s@}: " (Pos.to_string_short pos))
|
||||||
|
pos;
|
||||||
|
pp_marker target ppf;
|
||||||
|
Format.pp_print_char ppf ' ';
|
||||||
|
Format.pp_print_string ppf (unformat message))
|
||||||
ppf content;
|
ppf content;
|
||||||
Format.pp_print_newline ppf ()
|
Format.pp_print_newline ppf ()
|
||||||
end
|
end
|
||||||
@ -220,19 +225,19 @@ exception CompilerError of Content.t
|
|||||||
|
|
||||||
let raise_spanned_error
|
let raise_spanned_error
|
||||||
?(span_msg : Content.message option)
|
?(span_msg : Content.message option)
|
||||||
?(suggestion : string list option)
|
?(suggestion = ([] : string list))
|
||||||
(span : Pos.t)
|
(span : Pos.t)
|
||||||
format =
|
format =
|
||||||
let continuation (message : Format.formatter -> unit) =
|
let continuation (message : Format.formatter -> unit) =
|
||||||
raise
|
raise
|
||||||
(CompilerError
|
(CompilerError
|
||||||
([MainMessage message; Position { pos_message = span_msg; pos = span }]
|
([MainMessage message; Position { pos_message = span_msg; pos = span }]
|
||||||
@ match suggestion with None -> [] | Some sugg -> [Suggestion sugg]))
|
@ match suggestion with [] -> [] | sugg -> [Suggestion sugg]))
|
||||||
in
|
in
|
||||||
Format.kdprintf continuation format
|
Format.kdprintf continuation format
|
||||||
|
|
||||||
let raise_multispanned_error_full
|
let raise_multispanned_error_full
|
||||||
?(suggestion : string list option)
|
?(suggestion = ([] : string list))
|
||||||
(spans : (Content.message option * Pos.t) list)
|
(spans : (Content.message option * Pos.t) list)
|
||||||
format =
|
format =
|
||||||
Format.kdprintf
|
Format.kdprintf
|
||||||
@ -243,14 +248,14 @@ let raise_multispanned_error_full
|
|||||||
:: List.map
|
:: List.map
|
||||||
(fun (pos_message, pos) -> Position { pos_message; pos })
|
(fun (pos_message, pos) -> Position { pos_message; pos })
|
||||||
spans
|
spans
|
||||||
@ match suggestion with None -> [] | Some sugg -> [Suggestion sugg])))
|
@ match suggestion with [] -> [] | sugg -> [Suggestion sugg])))
|
||||||
format
|
format
|
||||||
|
|
||||||
let raise_multispanned_error
|
let raise_multispanned_error
|
||||||
?(suggestion : string list option)
|
?(suggestion = ([] : string list))
|
||||||
(spans : (string option * Pos.t) list)
|
(spans : (string option * Pos.t) list)
|
||||||
format =
|
format =
|
||||||
raise_multispanned_error_full ?suggestion
|
raise_multispanned_error_full ~suggestion
|
||||||
(List.map
|
(List.map
|
||||||
(fun (msg, pos) ->
|
(fun (msg, pos) ->
|
||||||
Option.map (fun s ppf -> Format.pp_print_string ppf s) msg, pos)
|
Option.map (fun s ppf -> Format.pp_print_string ppf s) msg, pos)
|
||||||
|
@ -55,7 +55,7 @@ let levenshtein_distance (s : string) (t : string) : int =
|
|||||||
of keyword + 1, in order to get suggestions close to "keyword")*)
|
of keyword + 1, in order to get suggestions close to "keyword")*)
|
||||||
let suggestion_minimum_levenshtein_distance_association
|
let suggestion_minimum_levenshtein_distance_association
|
||||||
(candidates : string list)
|
(candidates : string list)
|
||||||
(keyword : string) : string list option =
|
(keyword : string) : string list =
|
||||||
let rec strings_minimum_levenshtein_distance
|
let rec strings_minimum_levenshtein_distance
|
||||||
(minimum : int)
|
(minimum : int)
|
||||||
(result : string list)
|
(result : string list)
|
||||||
@ -88,16 +88,13 @@ let suggestion_minimum_levenshtein_distance_association
|
|||||||
(*The "result" list is returned at the end of the "candidates'" list.*)
|
(*The "result" list is returned at the end of the "candidates'" list.*)
|
||||||
| [] -> result
|
| [] -> result
|
||||||
in
|
in
|
||||||
let suggestions =
|
strings_minimum_levenshtein_distance
|
||||||
strings_minimum_levenshtein_distance
|
(1 + (String.length keyword / 3))
|
||||||
(1 + (String.length keyword / 3))
|
(*In order to select suggestions that are not too far away from the
|
||||||
(*In order to select suggestions that are not too far away from the
|
keyword*)
|
||||||
keyword*)
|
[] candidates
|
||||||
[] candidates
|
|
||||||
in
|
|
||||||
match suggestions with [] -> None | _ :: _ -> Some suggestions
|
|
||||||
|
|
||||||
let display (ppf : Format.formatter) (suggestions_list : string list) =
|
let format (ppf : Format.formatter) (suggestions_list : string list) =
|
||||||
match suggestions_list with
|
match suggestions_list with
|
||||||
| [] -> ()
|
| [] -> ()
|
||||||
| _ :: _ ->
|
| _ :: _ ->
|
||||||
|
23
compiler/catala_utils/suggestions.mli
Normal file
23
compiler/catala_utils/suggestions.mli
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
(* This file is part of the Catala compiler, a specification language for tax
|
||||||
|
and social benefits computation rules. Copyright (C) 2023 Inria, contributor:
|
||||||
|
Aminata Boiguillé <aminata.boiguille@etu.sorbonne-universite.fr>, Emile
|
||||||
|
Rolley <emile.rolley@tuta.io>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations under
|
||||||
|
the License. *)
|
||||||
|
|
||||||
|
val suggestion_minimum_levenshtein_distance_association :
|
||||||
|
string list -> string -> string list
|
||||||
|
(**Returns a list of the closest words into {!name:candidates} to the keyword
|
||||||
|
{!name:keyword}*)
|
||||||
|
|
||||||
|
val format : Format.formatter -> string list -> unit
|
@ -130,7 +130,7 @@ let raise_error_cons_not_found
|
|||||||
in
|
in
|
||||||
Message.raise_spanned_error
|
Message.raise_spanned_error
|
||||||
~span_msg:(fun ppf -> Format.fprintf ppf "Here is your code :")
|
~span_msg:(fun ppf -> Format.fprintf ppf "Here is your code :")
|
||||||
?suggestion:closest_constructors (Mark.get constructor)
|
~suggestion:closest_constructors (Mark.get constructor)
|
||||||
"The name of this constructor has not been defined before@ (it's probably \
|
"The name of this constructor has not been defined before@ (it's probably \
|
||||||
a typographical error)."
|
a typographical error)."
|
||||||
|
|
||||||
|
@ -138,14 +138,14 @@ module ParserAux (LocalisedLexer : Lexer_common.LocalisedLexer) = struct
|
|||||||
Format.fprintf ppf "Message: @{<yellow>%s@}@,%t"
|
Format.fprintf ppf "Message: @{<yellow>%s@}@,%t"
|
||||||
(String.trim (String.uncapitalize_ascii msg)))
|
(String.trim (String.uncapitalize_ascii msg)))
|
||||||
(fun (ppf : Format.formatter) ->
|
(fun (ppf : Format.formatter) ->
|
||||||
Format.fprintf ppf "You can only write : ";
|
Format.fprintf ppf "You could have written : ";
|
||||||
Format.pp_print_list
|
Format.pp_print_list
|
||||||
~pp_sep:(fun ppf () -> Format.fprintf ppf ",@ or ")
|
~pp_sep:(fun ppf () -> Format.fprintf ppf ",@ or ")
|
||||||
(fun ppf string -> Format.fprintf ppf "@{<yellow>\"%s\"@}" string)
|
(fun ppf string -> Format.fprintf ppf "@{<yellow>\"%s\"@}" string)
|
||||||
ppf
|
ppf
|
||||||
(List.map (fun (s, _) -> s) acceptable_tokens))
|
(List.map (fun (s, _) -> s) acceptable_tokens))
|
||||||
in
|
in
|
||||||
raise_parser_error ?suggestion:similar_acceptable_tokens
|
raise_parser_error ~suggestion:similar_acceptable_tokens
|
||||||
(Pos.from_lpos (lexing_positions lexbuf))
|
(Pos.from_lpos (lexing_positions lexbuf))
|
||||||
(Option.map Pos.from_lpos last_positions)
|
(Option.map Pos.from_lpos last_positions)
|
||||||
(Utf8.lexeme lexbuf) custom_menhir_message
|
(Utf8.lexeme lexbuf) custom_menhir_message
|
||||||
|
@ -31,7 +31,7 @@ $ catala Interpret -s Test1
|
|||||||
[ERROR]
|
[ERROR]
|
||||||
Syntax error at token "scope"
|
Syntax error at token "scope"
|
||||||
Message: expected either 'condition', or 'content' followed by the expected variable type
|
Message: expected either 'condition', or 'content' followed by the expected variable type
|
||||||
You can only write : "condition",
|
You could have written : "condition",
|
||||||
or "content"
|
or "content"
|
||||||
|
|
||||||
Error token:
|
Error token:
|
||||||
@ -75,7 +75,7 @@ $ catala Interpret -s Test2
|
|||||||
[ERROR]
|
[ERROR]
|
||||||
Syntax error at token "scope"
|
Syntax error at token "scope"
|
||||||
Message: expected either 'condition', or 'content' followed by the expected variable type
|
Message: expected either 'condition', or 'content' followed by the expected variable type
|
||||||
You can only write : "condition",
|
You could have written : "condition",
|
||||||
or "content"
|
or "content"
|
||||||
|
|
||||||
Error token:
|
Error token:
|
||||||
@ -119,7 +119,7 @@ $ catala Interpret -s Test3
|
|||||||
[ERROR]
|
[ERROR]
|
||||||
Syntax error at token "scope"
|
Syntax error at token "scope"
|
||||||
Message: expected either 'condition', or 'content' followed by the expected variable type
|
Message: expected either 'condition', or 'content' followed by the expected variable type
|
||||||
You can only write : "condition",
|
You could have written : "condition",
|
||||||
or "content"
|
or "content"
|
||||||
|
|
||||||
Error token:
|
Error token:
|
||||||
@ -165,7 +165,7 @@ $ catala Interpret -s Test4
|
|||||||
[ERROR]
|
[ERROR]
|
||||||
Syntax error at token "scope"
|
Syntax error at token "scope"
|
||||||
Message: expected either 'condition', or 'content' followed by the expected variable type
|
Message: expected either 'condition', or 'content' followed by the expected variable type
|
||||||
You can only write : "condition",
|
You could have written : "condition",
|
||||||
or "content"
|
or "content"
|
||||||
|
|
||||||
Error token:
|
Error token:
|
||||||
|
37
tests/test_default/bad/typing_or_logical_error.catala_en
Normal file
37
tests/test_default/bad/typing_or_logical_error.catala_en
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
###Article
|
||||||
|
|
||||||
|
```catala
|
||||||
|
declaration scope A:
|
||||||
|
output wrong_definition content integer
|
||||||
|
|
||||||
|
scope A:
|
||||||
|
definition wrong_definition = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```catala-test-inline
|
||||||
|
$ catala Interpret -s A
|
||||||
|
[ERROR]
|
||||||
|
Syntax error at token "="
|
||||||
|
Message: expected 'under condition' followed by a condition, 'equals' followed by the definition body, or the rest of the variable qualified name
|
||||||
|
You could have written : "of",
|
||||||
|
or "state",
|
||||||
|
or "equals",
|
||||||
|
or "under condition",
|
||||||
|
or "."
|
||||||
|
|
||||||
|
Error token:
|
||||||
|
┌─⯈ tests/test_default/bad/typing_or_logical_error.catala_en:8.30-8.31:
|
||||||
|
└─┐
|
||||||
|
8 │ definition wrong_definition = 1
|
||||||
|
│ ‾
|
||||||
|
|
||||||
|
Last good token:
|
||||||
|
┌─⯈ tests/test_default/bad/typing_or_logical_error.catala_en:8.13-8.29:
|
||||||
|
└─┐
|
||||||
|
8 │ definition wrong_definition = 1
|
||||||
|
│ ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
|
|
||||||
|
Maybe you wanted to write : "."
|
||||||
|
#return code 123#
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user