elm-community / parser-combinators

A parser combinator library for Elm.
http://package.elm-lang.org/packages/elm-community/parser-combinators/latest
BSD 3-Clause "New" or "Revised" License
104 stars 13 forks source link

Runtime exception in recursive parsers (elm compiler bug?) #23

Closed turboMaCk closed 7 years ago

turboMaCk commented 7 years ago

Hi folks :wave:!

I think I've found bug but also believe this is cased by elm compiler rather that Combine itself. Anyway I think it's still good to have a record of this.

This is so far the most minimal example I've managed to find:

module Parser exposing (..)

import Combine exposing (..)
import Html exposing (Html)

type Expression
    = Node (List Expression)

node : Parser s Expression
node =
    Node
        <$> brackets (many expression)

expression : Parser s Expression
expression =
    Combine.lazy <| \() -> node

parseExpression : String -> Result String Expression
parseExpression s =
    case Combine.parse expression s of
        Ok ( _, _, expr ) ->
            Ok expr

        Err ( _, _, errs ) ->
            Err <| String.join " or " errs

json : String
json =
    """[]"""

main : Html msg
main =
    Html.text <| toString <| parseExpression json

I've tried to debug deeply but undefined value has been past many times so I didn't manage to identify where it actually happens (yet).

Hope it all make sense.

update: simpler example

turboMaCk commented 7 years ago

update: I've managed to make example much simpler.

turboMaCk commented 7 years ago

This is same issue reproduced by reducing official Scheme parser example:

module Scheme exposing (..)

import Combine exposing (..)

type E
    = EList (List E)

list : Parser s E
list =
    EList
        <$> parens (many expr)
        <?> "list"

expr : Parser s E
expr =
    lazy <|
        \() ->
            whitespace *> list <* whitespace

program : Parser s (List E)
program =
    manyTill expr end

parse : String -> Result String (List E)
parse s =
    case Combine.parse program s of
        Ok ( _, _, e ) ->
            Ok e

        Err ( _, _, errs ) ->
            Err <| String.join " or " errs

code =
    """()"""

res =
    parse code
eeue56 commented 7 years ago

@turboMaCk it would help a lot if you could also describe the error itself, paste it here.

turboMaCk commented 7 years ago

@eeue56 I don't get why it disappeared from original post. I've tried to update but it's keep disappearing on save:(

exception is:

Uncaught TypeError: Cannot read property 'ctor' of undefined
    at _elm_community$parser_combinators$Combine$app

compiled code:

var _elm_community$parser_combinators$Combine$app = function (p) {
    var _p0 = p;
    if (_p0.ctor === 'Parser') {
        return _p0._0;
    } else {
        return _elm_lang$lazy$Lazy$force(_p0._0);
    }
};

More on investigation

I was playing with this a bit more and I have failing & fix example.

let me start with broken code (again using simplified Scheme parser)

module Scheme exposing (..)

import Combine exposing (..)

type E
    = EList (List E)
    | EComment String

comment : Parser s E
comment =
    EComment
        <$> regex ";[^\n]+"
        <?> "comment"

list : Parser s E
list =
    EList
        <$> parens (many expr)
        <?> "list"

expr : Parser s E
expr =
    lazy <|
        \() ->
            let
                parsers =
                    [ list
                    , comment
                    ]
            in
                whitespace *> choice parsers <* whitespace

program : Parser s (List E)
program =
    manyTill expr end

parse : String -> Result String (List E)
parse s =
    case Combine.parse program s of
        Ok ( _, _, e ) ->
            Ok e

        Err ( _, _, errs ) ->
            Err <| String.join " or " errs

code =
    """()"""

res =
    parse code

this fails in runtime with same exception.

Now you can fix code just by simply changing order of E constructors:

type E
    = EComment String
    | EList (List E)

works in repl as expected.

> res
Ok ([EList []]) : Result.Result String (List Scheme.E)
stil4m commented 7 years ago

@turboMaCk I've fixed your issue in an Ellie (https://ellie-app.com/x4K8Ly3g9Na1/2)

You added the lazy to the expr, but it was needed on the list. If you have a cyclic reference of combinators. For example: list -> expr -> list, it is kind of a best practice to define lazy on each of them.

I believe the issue that causes this problem is Elm Compiler #1527.