parsica-php / parsica

Parsica - PHP Parser Combinators - The easiest way to build robust parsers.
https://parsica-php.github.io/
MIT License
405 stars 18 forks source link

AST validation [Question] #53

Closed zim32 closed 3 years ago

zim32 commented 3 years ago

Hi there. Thanks for great project!

I have a quesion. I need to validate AST which I build inside map(...) functions. But I don't know how to keep track of current position whie generating AST nodes to print meaningful messages. Is it possible to pass current position to map() function so it can be saved into AST node?

turanct commented 3 years ago

Currently not really.

At the point that you want to map, you have a ParseResult. There you have the parsed data (the AST in your case) and the "remainder" of the input that you're parsing. Based on that remainder you can do some kind of positioning, but only if you compare it to the original input. You could make some kind of error like MySQL does:

SELECT * FROM `users` HERE `age` > 21;

would result in an error somewhat like

MySQL parse error at "HERE `age` > 21;"

which is basically the remainder.

However. Parsica knows how to make better error messages, so if you make a Parser that validates the Parsed map, you can make it fail like any other parser, and the error message will be something like

            <input>:5:10
              |
            5 | ...bcd
              |    ^— column 10
            Unexpected 'b'
            Expecting 'a'

where the "unexpected" and "expected" input indications are based on the parser's label

zim32 commented 3 years ago

Thanks for reply

zim32 commented 3 years ago

Unfortunately if I try to print remainder I got error: Can't read the remainder of a failed ParseResult.

turanct commented 3 years ago

Correct, I forgot about that.

The return value of a parser is a ParseResult, which is either a Success or a Failure. In case of a Success we have a value and a remainder, in case of a Failure we have an error message.

something like this

ParseResult a = Succeed<a, Stream> | Fail<string>

Isn't the error message what you need?

zim32 commented 3 years ago

F.e. I have AST node which handles function call. f.e. sum(1,2). It is represented by FunctionCallAstNode class. This class has validate() method where it checks whether function name is valid. And I need to display some error message that this function is invalid and point to position

turanct commented 3 years ago

so you're doing something like this?

$functionParser = ...;

map(
    $functionParser,
    function(array $parsedValues) {
        $name = $parsedValues[0];
        $parameters = $parsedValues[1];

        return new FunctionCallAstNode($name, $parameters);
    }
);

but you want to be able to let the parser fail if the FunctionCallAstNode throws or if you call a validation method on it and it fails?

Let's first look at the types:

map :: Parser<T1> -> (T1 -> T2) -> Parser<T2>

This means that it takes a Parser that will try to parse something of type T1 and a function from T1 to T2, and it will return a Parser of type T2.

However if i'm understanding you correctly, what you're trying to accomplish is that within the mapping function, you can still decide that you're not succeeding the parse, which is not possible with map, according to the type signature.

The function that allows us to do something similar is the bind function. Let's take a look at the type signature:

bind :: Parser<T1> -> (T1 -> Parser<T2>) -> Parser<T2>

This means that bind takes a Parser that will try to parse something of type T1 and a function from T1 to a Parser (!) that will try to parse a T2, and it will return a Parser of type T2.

Which as a result means that you can "replace" the outcome of your parser based on the function you passed.

Something like this:

$functionParser = ...;

bind(
    $functionParser,
    function(array $parsedValues) {
        $name = $parsedValues[0];
        $parameters = $parsedValues[1];

        try {
            $astNode = new FunctionCallAstNode($name, $parameters);
            $astNode->validate(); // <-- this validate call should probably happen in the constructor of `FunctionCallAstNode`
        } catch (Exception $e) {
            return fail(); // <-- this will return a parser that will always fail
        }

        return pure($astNode)->label('Function Call AST Node'); // <-- this will return a parser that returns the AST Node
    }
);

does that help you?

zim32 commented 3 years ago

Yeas, I think this is what I need. Thanks. Yeas.my goal and question was how to validate something already parsed and fail it