As shown in the previous chapter there is support in the combinators to collect expressive information concering errors during parsing. The parsers in this library support the collection of position information as well (only the generic parser has no support for position information).
At the end of the parsing, the parser has either succeeded or failed because of a syntax error or a semantic error. There are functions like failed_expectations
and failed_semantic
which return the corresponding error information.
It is not too difficult to use the error information and print some expressive error messages. However it is tedious to do this by hand. The parsing library has some support to print the error messages nicely.
Let's look at the calculator example with the following input:
1 + 6^2 * 200 - (100 - 50^3) +, 300 - 2
Parsing this input will result in a syntax error at the unexpected comma.
The functions in this library help us to generate the following error message:
1 | 1 2 | + 6^2 * 200 3 | - (100 - 50^3) 4 | +, 300 ^ At the marker I was expecting one of the following: - unary operator - number - opening parenthesis '(' or '['
If we feed the calculator with the input
1 + + 6^2 * 200 - (100 - 50^3) + 300 / (100 - 50 - 20 - 30) - 2
it is possible to generate the error message
1 | 1 2 | + 6^2 * 200 3 | - (100 - 50^3) 4 | + 300 / (100 - 50 - 20 - 10) ^^^^^^^^^^^^^^^^^^ Zero divisor
The module Error_reporter
makes the generation of error messages easy.
Let's assume that we have a failed parser p:Parser.t
which has failed because of a syntax error. An error reporter can be generated from p
by
module Reporter = Error_reporter.Make (Parser)
let r = Reporter.make_syntax p
Now we have an error reporter r
which can report the error of the parser p
. Obviously this is allowed only if the parser p
is in an error state not (needs_more p) && not (has_succeeded p)
.
The error reporter needs some form of the input stream to extract the relevant source snippet. If the parser has read the input from an input channel ic
then we can generate the error message by
seek_in ic 0; (* Reposition the input channel to the start. *)
let doc =
Reporter.run_on_channel ic r
The generated doc
has type Fmlib_pretty.Print.doc
. doc
is a representation of the error message. The module Fmlib_pretty.Print
has functions to layout the message with a certain text width and write the document to a channel (i.e. file), to convert it to a string etc.
If we want to write the error message to stderr
we just issue the call
Fmlib_pretty.Print.(
layout 80 doc (* Layout with text width 80 *)
|>
write_to_channel stderr
)
and we are ready.
If the fail
combinator has been used in the parser p
, then the parser can fail either with a syntax error or a semantic error. Since semantic errors are transparent to the parser (they are encapsuled in the functor argument Semantic
) some support is needed from the user to generate the error message.
The user has to provide two functions to describe the semantic error:
semantic_range: Semantic.t -> Position.range
: Compute the start and end position of the semantic error.semantic_message: Semantic.t -> Fmlib_pretty.Print.doc
: Compute the specific error message.In the calculator example we have used Position.range * string
as the type of semantic errors. The two needed functions are in that case fairly trivial.
let semantic_range error =
fst error
let semantic_message error =
snd error
|>
Fmlib_pretty.Print.text
Having a parser p
which failed either because of a syntax or semantic error we can generate an error message by
seek_in 0 ic;
Error_reporter.(
make
semantic_range
semantic_message
p
|>
run_on_channel ic
|>
Fmlib_pretty.Print.layout 80
|>
Fmlib_pretty.write_to_channel stderr
)
Because it is very frequent to read from a file represented by an input channel and to write the error message to stderr
the above sequence can be shortened to
seek_in 0 ic;
Error_reporter.(
make
semantic_range
semantic_message
p
|>
run_on_channels ic 80 stderr
)