Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paren parser problems #982

Open
Fuuzetsu opened this issue Apr 15, 2017 · 15 comments
Open

Paren parser problems #982

Fuuzetsu opened this issue Apr 15, 2017 · 15 comments
Labels

Comments

@Fuuzetsu
Copy link
Member

I was investigating why clever Haskell mode works as bad as it does (again). I have several findings

  1. Paren parser (Yi.Syntax.Paren.parse) forces whole result even when it is going to be thrown away.

That is, open a file, only screenfull gets loaded. Edit any part, the (lazy) parser for the rest of the file runs in full then we run another parse for the screenfull.

Basically it seems it's written poorly and needs to be greedy

  1. Parse (Parser.Incremental.Parse) violates basic laws. ap /= <*>. Fun exercise is replacing pure <$> pTree with replicateM 1 pTree and watching it crash. Indeed these two things aren't equivalent:
--      pExpr = (:) <$> pTree <*> (pure [])
      pExpr = (\x -> [x]) <$> pTree
  1. Indent parser seems ok, just paren parser seems completely fucked. Efforts trying to find the fault in lexer/scanner/etc runners might be better concentrated in verifying that first. I checked other modes using identical runners and they didn't exhibit that behaviour.
@noughtmare
Copy link
Member

@Fuuzetsu

You say the paren parser is greedy, but does that also mean that all Parser.Incremental parsers are greedy when they should be lazy, or is only the paren parser affected? With "indent parser" do you mean the indent parsing in the layoutHandler function in Yi.Syntax.Layout? I couldn't find another module that dealt with indent parsing.

@Fuuzetsu
Copy link
Member Author

I think problem lies in paren parser only, or rather not in underlying library.

I forgot exactly what I meant by indent parser but I believe it is thr name for one of the other haskell mode parsers (which do not exhibit the bug).

You can see the bug by adding tracing into the underlying Alex code and see the position and tokens it consumes. It then becomes easy to try other modes and see uf the problem persists.

@noughtmare
Copy link
Member

I found the problem: the adjustBlock function.

Changing

  & modeAdjustBlockA .~ adjustBlock

to

  & modeAdjustBlockA .~ (\_ _ -> return ())

fixes the greedy behaviour.

@Fuuzetsu
Copy link
Member Author

Aha, I'm going to blame adjustBlock -> getSubtreeSpan -> getLastElement; we should definitely fix the monad/applicative too though… Good find.

@noughtmare
Copy link
Member

noughtmare commented Feb 28, 2018

Just for the record: the adjustBlock situation is "solved" by 3f6df01

@noughtmare
Copy link
Member

I think ap == <*> is not actually a law, the Monad class only has left identity right identity and associativity as laws. I think in our case ap is not <*> because ap cannot do certain optimizations that are possible with the applicative interface.

@Fuuzetsu
Copy link
Member Author

Fuuzetsu commented Mar 2, 2018

(<*>) = ap should always hold. You don't even have to understand why, you can just look at Monad typeclass which explicitly tells you this. https://hackage.haskell.org/package/base-4.10.1.0/docs/Control-Monad.html#t:Monad

@noughtmare
Copy link
Member

Ah, I didn't see that part. But still, what if a type can do optimizations if it is applicative, but can't do those if it is monadic? Should it just not be an instance of Monad, or should it have some kind of newtype wrapper?

@Fuuzetsu
Copy link
Member Author

Fuuzetsu commented Mar 2, 2018

You're free to implement (<*>) in more efficient way than ap but the result has to be the same. See some comments on https://stackoverflow.com/questions/46913472/how-exactly-does-the-ap-applicative-monad-law-relate-the-two-classes .

A famous example is Haxl where >>= (and therefore ap) results in sequential evaluation but (<*>) is parallel. See https://hackage.haskell.org/package/haxl-0.5.1.0/docs/src/Haxl.Core.Monad.html#GenHaxl . In the end result is the same so the laws hold.

@noughtmare
Copy link
Member

I think something similar is happening in Parser.Incremental, it has intermediate states that are not equal, but running both parsers on the same text should yield the same result (or maybe bottom). There might be a mistake in the implementation that causes it to misbehave. I will do some testing on this.

You say:

replacing pure <$> pTree with replicateM 1 pTree and watching it crash

Do you mean it goes into an infinite loop? You could view an infinite loop as just a very inefficient implementation.

But overall I agree that we should do some more research on this.

@Fuuzetsu
Copy link
Member Author

Fuuzetsu commented Mar 2, 2018

IIRC it outright crashed. Anyway those parsers are based on a paper, might be worth reading that first. Maybe someone "optimised" original code and broke it.

@noughtmare
Copy link
Member

@Fuuzetsu can you link to the code where pTree is defined?

@Fuuzetsu
Copy link
Member Author

Fuuzetsu commented Mar 2, 2018

pTree :: P TT (Tree TT)
?

@noughtmare
Copy link
Member

noughtmare commented Mar 3, 2018

I am able to reproduce it! (turns out (<*) /= flip (>>)...)

@noughtmare
Copy link
Member

noughtmare commented Mar 3, 2018

I have replaced parse and parse' with:

parse :: P TT (Tree TT)
parse = liftM Expr (parse' tokT tokFromT)

parse' :: (TT -> Token) -> (Token -> TT) -> P TT [Tree TT]
parse' toTok _ = liftM2 const pExpr eof
    where
      -- parse a special symbol
      sym c = symbol (isSpecial [c] . toTok)

      pleaseSym c = recoverWith errTok <|> sym c

      pExpr :: P TT (Expr TT)
      pExpr = liftM2 (:) pTree pExpr <|> pure []

      pBlocks = (liftM Expr pExpr) `sepBy1` sym '.' -- the '.' is generated by the layout, see HACK above
      -- note that we can have empty statements, hence we use sepBy1.

      pTree :: P TT (Tree TT)
      pTree = (liftM3 Paren (sym '(') pExpr (pleaseSym ')'))
          <|> (liftM3 Paren (sym '[') pExpr (pleaseSym ']'))
          <|> (liftM3 Paren (sym '{') pExpr (pleaseSym '}'))

          <|> (liftM Block (liftM3 (\_ a _ -> a) (sym '<') pBlocks (sym '>'))) -- see HACK above

          <|> (liftM Atom (symbol (isNoise . toTok)))
          <|> (liftM Error (recoverWith (symbol (isSpecial "})]" . toTok))))

And that seems to still work (i.e. I opened some haskell files with cleverMode after recompiling yi and nothing crashed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants