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

Add support for deriving ToExpr from Show #94

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion src/Data/TreeDiff/Parser.hs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Utilities to parse 'Expr'.
--
-- /Note:/ we don't parse diffs.
module Data.TreeDiff.Parser (
exprParser
exprParser,
showToExpr,
ShowParseFailed,
unsafeShowToExpr,
) where

import Control.Applicative (many, optional, (<|>))
import Control.Exception (Exception(..))
import Data.Char (chr, isAlphaNum, isPunctuation, isSymbol)

import GHC.Stack (HasCallStack)
import Text.Parser.Char (CharParsing (anyChar, char, satisfy))
import Text.Parser.Combinators (between, (<?>))
import Text.Parser.Token
Expand All @@ -18,8 +24,11 @@ import Text.Parser.Token.Highlight
(Highlight (Identifier, StringLiteral, Symbol))

import Data.TreeDiff.Expr
import Text.Parsec (ParseError)

import qualified Control.Exception as Exception
import qualified Data.TreeDiff.OMap as OMap
import qualified Text.Parsec as Parsec

-- | Parsers for 'Expr' using @parsers@ type-classes.
--
Expand Down Expand Up @@ -111,3 +120,44 @@ valid c = isAlphaNum c || isSymbol c || isPunctuation c

valid' :: Char -> Bool
valid' c = valid c && c `notElem` "[](){}`\","

{-| Parse an `Expr` from a type's `Show` instance. This can come in handy if a
type already has a `Show` instance and you don't want to have to derive
`ToExpr` for that type and all of its dependencies.
-}
showToExpr :: Show a => a -> Either ShowParseFailed Expr
showToExpr a =
case Parsec.parse exprParser "" (show a) of
Copy link
Collaborator

@phadej phadej Aug 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work. exprParser is not parser for Haskell expressions. Close syntax, but not the same.

EDIT: In particular infix operators are something which is not accepted by exprParser.

Left exception -> Left ShowParseFailed{ exception }
Right expr -> Right expr

instance Exception ShowParseFailed where
displayException ShowParseFailed{ exception } =
"Failed to parse an Expr from the output of show\n\
\\n\
\This might be due to the Show instance (or one of the Show instances it depends\n\
\on) not being derived.\n\
\\n\
\Parsing error:\n\
\\n\
\" <> show exception

-- | `unsafeShowToExpr` failed to parse the output from `show` into an `Expr`
--
-- This usually means that the type (or one of its dependencies) has a `Show`
-- instance that was not derived.
newtype ShowParseFailed = ShowParseFailed{ exception :: ParseError }
deriving (Show)

{-| You can use this to implement the `toExpr` method of the `ToExpr` class.
However, this is a partial function that is only safe to use for derived
`Show` instances and might fail for other types of instances.

If this function fails it will `Exception.throw` a `ShowParseFailed`
exception.
-}
unsafeShowToExpr :: HasCallStack => Show a => a -> Expr
unsafeShowToExpr a =
case showToExpr a of
Left exception -> Exception.throw exception
Right expr -> expr
Loading