packages feed

pretty-ghci-0.1.0.0: src/Text/PrettyPrint/GHCi/Value/Parser.y

{
module Text.PrettyPrint.GHCi.Value.Parser (
  parseValue,
  Id, Op, Value(..),
) where

import Text.PrettyPrint.GHCi.Value.Lexer

import qualified Data.List.NonEmpty as N
}

%name parseTokens value
%monad { Maybe } { (>>=) } { return }
%expect 0
%tokentype { Token }
%token number     { NumberTok $$ }
       string     { StringTok $$ }
       character  { CharacterTok $$ }
       operator   { OperatorTok $$ }
       identifier { IdentifierTok $$ }
       '('        { OpenParen } 
       ')'        { CloseParen }
       '['        { OpenBracket }
       ']'        { CloseBracket }
       '{'        { OpenBrace }
       '}'        { CloseBrace }
       ','        { Comma }
       '='        { Equal }

%%

atom :: { Value }
    : number                           { Num   $1 }
    | string                           { Str   $1 }
    | character                        { Char  $1 }
    | '(' ')'                          { Tuple [] }
    | '(' value comma_values ')'       { if null $3
                                           then Paren $2
                                           else Tuple ($2 : reverse $3) }
    | '[' ']'                          { List [] }
    | '[' value comma_values ']'       { List ($2 : reverse $3) }

-- Reversed list of values, each value being preceded by a comma
comma_values :: { [Value] }
    : {- empty -}                      { []      }
    | comma_values ',' value           { $3 : $1 }

-- Prefix constructor application
prefix :: { Value }
    : identifier prefix_apps           { Prefix $1 (reverse $2) }
    | identifier '{' fields '}'        { Record $1 (N.reverse $3) }
    | atom                             { $1 }

-- Reversed arguments to a prefix constructor
prefix_apps :: { [Value] }
    : {- empty -}                      { []                }
    | prefix_apps atom                 { $2           : $1 }
    | prefix_apps identifier           { Prefix $2 [] : $1 }

-- A record field
field :: { (Id, Value) }
    : identifier       '=' value       { ($1,               $3) }
    | '(' operator ')' '=' value       { ("(" ++ $2 ++ ")", $5) }

-- Non-empty list of reversed record fields
fields :: { N.NonEmpty (Id, Value) }
    : field                            { $1 N.:| [] }
    | fields ',' field                 { N.cons $3 $1 }

-- Infix constructor application
infixes :: { Value }
    : prefix infixes_sufs              { case $2 of
                                           [] -> $1
                                           x : xs -> Infix $1 (N.reverse (x N.:| xs)) }

-- Reversed list of operator suffixes
infixes_sufs :: { [(Op, Value)] }
    : {- empty -}                      { []            }
    | infixes_sufs operator prefix     { ($2, $3) : $1 }

-- Entry point
value :: { Value }
    : infixes                          { $1 }

{
-- | Throws an exception, not particularly helpful
happyError :: [Token] -> Maybe a
happyError _ = Nothing

-- | A @conid@ or @varid@ (possibly ending in hashes, to account for @MagicHash@)
type Id = String

-- | A @conop@ or @varop@
type Op = String

-- | A very simple representation of the output of 'Show'
data Value
  = Prefix Id [Value]
  | Infix Value (N.NonEmpty (Op, Value))
  | Record Id (N.NonEmpty (Id, Value))
  | Tuple [Value]
  | List [Value]
  | Num String  -- ^ integer or floating point
  | Char String -- ^ character
  | Str String  -- ^ string
  | Paren Value
  deriving Show

-- | Parse a value from a 'String'. Returns 'Nothing' for inputs that
-- could not be parsed.
parseValue :: String -> Maybe Value
parseValue = parseTokens . filter notWhite . lexTokens
  where
    notWhite (WhiteTok _) = False
    notWhite _ = True
}