packages feed

Earley (empty) → 0.6.0

raw patch · 7 files changed

+473/−0 lines, 7 filesdep +ListLikedep +basedep +containerssetup-changed

Dependencies added: ListLike, base, containers, kan-extensions

Files

+ Earley.cabal view
@@ -0,0 +1,28 @@+name:                Earley+version:             0.6.0+synopsis:            Parsing all context-free grammars using Earley's algorithm.+description:         See <https://www.github.com/ollef/Earley> for more+                     information and+                     <https://github.com/ollef/Earley/tree/master/examples> for+                     examples.+license:             BSD3+license-file:        LICENSE+author:              Olle Fredriksson+maintainer:          fredriksson.olle@gmail.com+copyright:           (c) 2014-2015 Olle Fredriksson+category:            Parsing+build-type:          Simple+cabal-version:       >=1.10++source-repository    head+  type:     git+  location: https://github.com/ollef/Earley.git++library+  exposed-modules:     Text.Earley.Derived, Text.Earley.Grammar, Text.Earley.Parser Text.Earley+  -- other-modules:+  build-depends:       base ==4.8.*, containers >=0.5, kan-extensions >=4.2, ListLike >=4.1+  -- hs-source-dirs:+  default-language:    Haskell2010+  ghc-options:         -Wall -funbox-strict-fields+
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014-2015, Olle Fredriksson++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Olle Fredriksson nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/Earley.hs view
@@ -0,0 +1,13 @@+-- | Parsing all context-free grammars using Earley's algorithm.+module Text.Earley+  ( -- * Context-free grammars+    Prod, satisfy, (<?>), Grammar, rule+  , -- * Derived operators+    symbol, namedSymbol, word+  , -- * Parsing+    Report(..), Result(..), parser, allParses, fullParses+  )+  where+import Text.Earley.Grammar+import Text.Earley.Derived+import Text.Earley.Parser
+ Text/Earley/Derived.hs view
@@ -0,0 +1,18 @@+-- | Derived operators.+module Text.Earley.Derived where+import Control.Applicative hiding (many)++import Text.Earley.Grammar++-- | Match a single token.+symbol :: Eq t => t -> Prod r e t t+symbol x = satisfy (== x)++-- | Match a single token and give it the name of the token.+namedSymbol :: Eq t => t -> Prod r t t t+namedSymbol x = symbol x <?> x++-- | Match a list of tokens in sequence.+{-# INLINE word #-}+word :: Eq t => [t] -> Prod r e t [t]+word = foldr (liftA2 (:) . satisfy . (==)) (pure [])
+ Text/Earley/Grammar.hs view
@@ -0,0 +1,136 @@+-- | Context-free grammars.+{-# LANGUAGE GADTs, RankNTypes #-}+module Text.Earley.Grammar+  ( Prod(..)+  , satisfy+  , (<?>)+  , Grammar(..)+  , rule+  ) where+import Control.Applicative+import Control.Monad+import Control.Monad.Fix++infixr 0 <?>++-- | A production.+--+-- The type parameters are:+--+-- @a@: The return type of the production.+--+-- @t@: The type of the terminals that the production operates on.+--+-- @e@: The type of names, used for example to report expected tokens.+--+-- @r@: The type of a non-terminal. This plays a role similar to the @s@ in the+--      type @ST s a@.  Since the 'parser' function expects the @r@ to be+--      universally quantified, there is not much to do with this parameter+--      other than leaving it universally quantified.+--+-- As an example, @'Prod' r 'String' 'Char' 'Int'@ is the type of a production that+-- returns an 'Int', operates on (lists of) characters and reports 'String'+-- names.+--+-- Most of the functionality of 'Prod's is obtained through its instances, e.g.+-- 'Functor', 'Applicative', and 'Alternative'.+data Prod r e t a where+  -- Applicative.+  Terminal    :: !(t -> Bool) -> !(Prod r e t (t -> b)) -> Prod r e t b+  NonTerminal :: !(r e t a) -> !(Prod r e t (a -> b)) -> Prod r e t b+  Pure        :: a -> Prod r e t a+  -- Monoid/Alternative. We have to special-case 'many' (though it can be done+  -- with rules) to be able to satisfy the Alternative interface.+  Plus        :: !(Prod r e t a) -> !(Prod r e t a) -> Prod r e t a+  Many        :: !(Prod r e t a) -> !(Prod r e t ([a] -> b)) -> Prod r e t b+  Empty       :: Prod r e t a+  -- Error reporting.+  Named       :: !(Prod r e t a) -> e -> Prod r e t a++-- | Match a token that satisfies the given predicate. Returns the matched token.+{-# INLINE satisfy #-}+satisfy :: (t -> Bool) -> Prod r e t t+satisfy p = Terminal p $ Pure id++-- | A named production (used for reporting expected things).+(<?>) :: Prod r e t a -> e -> Prod r e t a+(<?>) = Named++instance Monoid (Prod r e t a) where+  mempty  = empty+  mappend = (<|>)++instance Functor (Prod r e t) where+  {-# INLINE fmap #-}+  fmap f (Terminal b p)    = Terminal b $ fmap (f .) p+  fmap f (NonTerminal r p) = NonTerminal r $ fmap (f .) p+  fmap f (Pure x)          = Pure $ f x+  fmap f (Plus p q)        = Plus (fmap f p) (fmap f q)+  fmap f (Many p q)        = Many p $ fmap (f .) q+  fmap _ Empty             = Empty+  fmap f (Named p n)       = Named (fmap f p) n++instance Applicative (Prod r e t) where+  pure = Pure+  {-# INLINE (<*>) #-}+  Terminal b p    <*> q = Terminal b $ flip <$> p <*> q+  NonTerminal r p <*> q = NonTerminal r $ flip <$> p <*> q+  Pure f          <*> q = fmap f q+  Plus a b        <*> q = a <*> q <|> b <*> q+  Many a p        <*> q = Many a $ flip <$> p <*> q+  Empty           <*> _ = Empty+  Named p n       <*> q = Named (p <*> q) n++instance Alternative (Prod r e t) where+  empty = Empty+  Empty     <|> q         = q+  p         <|> Empty     = p+  Named p m <|> q         = Named (p <|> q) m+  p         <|> Named q n = Named (p <|> q) n+  p         <|> q         = Plus p q+  many p       = Many p $ Pure id+  some p       = (:) <$> p <*> many p++-- | A context-free grammar.+--+-- The type parameters are:+--+-- @a@: The return type of the grammar (often a 'Prod').+--+-- @e@: The type of names, used for example to report expected tokens.+--+-- @r@: The type of a non-terminal. This plays a role similar to the @s@ in the+--      type @ST s a@.  Since the 'parser' function expects the @r@ to be+--      universally quantified, there is not much to do with this parameter+--      other than leaving it universally quantified.+--+-- Most of the functionality of 'Grammar's is obtained through its instances,+-- e.g.  'Monad' and 'MonadFix'. Note that GHC has syntactic sugar for+-- 'MonadFix': use @{-\# LANGUAGE RecursiveDo \#-}@ and @mdo@ instead of+-- @do@.+data Grammar r e a where+  RuleBind :: Prod r e t a -> (Prod r e t a -> Grammar r e b) -> Grammar r e b+  FixBind  :: (a -> Grammar r e a) -> (a -> Grammar r e b) -> Grammar r e b+  Return   :: a -> Grammar r e a++instance Functor (Grammar r e) where+  fmap f (RuleBind ps h) = RuleBind ps (fmap f . h)+  fmap f (FixBind g h)   = FixBind g (fmap f . h)+  fmap f (Return x)      = Return $ f x++instance Applicative (Grammar r e) where+  pure  = return+  (<*>) = ap++instance Monad (Grammar r e) where+  return = Return+  RuleBind ps f >>= k = RuleBind ps (f >=> k)+  FixBind f g   >>= k = FixBind f (g >=> k)+  Return x      >>= k = k x++instance MonadFix (Grammar r e) where+  mfix f = FixBind f return++-- | Create a new non-terminal by listing its production rule.+rule :: Prod r e t a -> Grammar r e (Prod r e t a)+rule p = RuleBind p return
+ Text/Earley/Parser.hs view
@@ -0,0 +1,246 @@+-- | Parsing.+{-# LANGUAGE BangPatterns, DeriveFunctor, GADTs, Rank2Types #-}+module Text.Earley.Parser+  ( Report(..)+  , Result(..)+  , parser+  , allParses+  , fullParses+  ) where+import Control.Applicative+import Control.Arrow+import Control.Monad.Fix+import Control.Monad.ST.Lazy+import Data.Functor.Yoneda+import Data.ListLike(ListLike)+import qualified Data.ListLike as ListLike+import Data.STRef.Lazy+import Text.Earley.Grammar++-------------------------------------------------------------------------------+-- * Concrete rules and productions+-------------------------------------------------------------------------------+-- | The concrete rule type that the parser uses+data Rule s r e t a = Rule+  { ruleProd     :: ProdR s r e t a+  , ruleNullable :: {-# UNPACK #-} !(STRef s (Maybe [a]))+  , ruleConts    :: {-# UNPACK #-} !(STRef s (Conts s r e t a r))+  }++type ProdR s r e t a = Prod (Rule s r) e t a++nullable :: Rule s r e t a -> ST s [a]+nullable r = do+  mn <- readSTRef $ ruleNullable r+  case mn of+    Just xs -> return xs+    Nothing -> do+      writeSTRef (ruleNullable r) $ Just mempty+      res <- nullableProd $ ruleProd r+      writeSTRef (ruleNullable r) $ Just res+      return res++nullableProd :: ProdR s r e t a -> ST s [a]+nullableProd (Terminal _ _)    = return mempty+nullableProd (NonTerminal r p) = do+  as <- nullable r+  concat <$> mapM (\a -> nullableProd $ fmap ($ a) p) as+nullableProd (Pure a)          = return [a]+nullableProd (Plus a b)        = mappend <$> nullableProd a <*> nullableProd b+nullableProd (Many p q)        = do+  as <- nullableProd $ (:[]) <$> p <|> pure []+  concat <$> mapM (\a -> nullableProd $ fmap ($ a) q) as+nullableProd Empty             = return mempty+nullableProd (Named p _)       = nullableProd p++-------------------------------------------------------------------------------+-- * States and continuations+-------------------------------------------------------------------------------+type Pos = Int++-- | An Earley state with result type @a@.+data State s r e t a where+  State :: {-# UNPACK #-} !Pos+        -> !(ProdR s r e t b)+        -> {-# UNPACK #-} !(Conts s r e t b a)+        -> State s r e t a+  Final :: a -> State s r e t a++-- | A continuation accepting an @a@ and producing a @b@.+data Cont s r e t a b where+  Cont      :: {-# UNPACK #-} !Pos+            -> !(ProdR s r e t (a -> b))+            -> {-# UNPACK #-} !(Conts s r e t b c)+            -> Cont s r e t a c+  FinalCont :: (a -> c) -> Cont s r e t a c++type Conts s r e t a c = STRef s [Cont s r e t a c]++contraMapCont :: (b -> a) -> Cont s r e t a c -> Cont s r e t b c+contraMapCont f (Cont pos p cs) = (Cont pos $! ((. f) <$> p)) cs+contraMapCont f (FinalCont g)   = FinalCont (g . f)++contToState :: a -> Cont s r e t a c -> State s r e t c+contToState a (Cont pos p cs) = State pos (($ a) <$> p) cs+contToState a (FinalCont f)   = Final (f a)++-- | Strings of non-ambiguous continuations can be optimised by removing+--   indirections.+simplifyCont :: Conts s r e t b a -> ST s [Cont s r e t b a]+simplifyCont cont = readSTRef cont >>= go False+  where+    go !_ [Cont _ (Pure f) cont'] = do+      ks' <- simplifyCont cont'+      go True $ map (contraMapCont f) ks'+    go True ks = do+      writeSTRef cont ks+      return ks+    go False ks = return ks++-------------------------------------------------------------------------------+-- * Grammars+-------------------------------------------------------------------------------+-- | Interpret an abstract 'Grammar'.+grammar :: Grammar (Rule s r) e a -> ST s a+grammar g = case g of+  RuleBind p k -> do+    c  <- newSTRef =<< newSTRef mempty+    nr <- newSTRef Nothing+    grammar $ k $ NonTerminal (Rule p nr c) $ Pure id+  FixBind f k   -> do+    a <- mfix $ fmap grammar f+    grammar $ k a+  Return x      -> return x++-- | Given a grammar, construct an initial state.+initialState :: ProdR s a e t a -> ST s (State s a e t a)+initialState r = do+  rs <- newSTRef [FinalCont id]+  return $ State (-1) r rs++-------------------------------------------------------------------------------+-- * Parsing+-------------------------------------------------------------------------------+-- | A parsing report, which contains fields that are useful for presenting+-- errors to the user if a parse is deemed a failure.  Note however that we get+-- a report even when we successfully parse something.+data Report e i = Report+  { position   :: Int -- ^ The final position in the input (0-based) that the+                      -- parser reached.+  , expected   :: [e] -- ^ The named productions processed at the final+                      -- position.+  , unconsumed :: i   -- ^ The part of the input string that was not consumed,+                      -- which may be empty.+  } deriving Show++-- | The result of a parse.+data Result s e i a+  = Ended (Report e i)+    -- ^ The parser ended.+  | Parsed a Int i (i -> ST s (Result s e i a))+    -- ^ The parser parsed something, namely an 'a'. The 'Int' is the position+    -- in the input where it did so, the 'i' is the rest of the input, and the+    -- function is the parser continuation. This allows incrementally feeding+    -- the parser more input (e.g. when the 'i' is empty).+  deriving (Functor)++{-# INLINE uncons #-}+uncons :: ListLike i t => i -> Maybe (t, i)+uncons i+  | ListLike.null i = Nothing+  | otherwise       = Just (ListLike.head i, ListLike.tail i)++{-# INLINE safeTail #-}+safeTail :: ListLike i t => i -> i+safeTail ts'+  | ListLike.null ts' = ts'+  | otherwise         = ListLike.tail ts'++{-# SPECIALISE parse :: [State s a e t a] -> [State s a e t a] -> ST s () -> [e] -> Pos -> [t] -> ST s (Result s e [t] a) #-}+-- | The internal parsing routine+parse :: ListLike i t+      => [State s a e t a] -- ^ States to process at this position+      -> [State s a e t a] -- ^ States to process at the next position+      -> ST s ()           -- ^ Computation that resets the continuation refs of productions+      -> [e]               -- ^ Named productions encountered at this position+      -> Pos               -- ^ The current position in the input string+      -> i                 -- ^ The input string+      -> ST s (Result s e i a)+parse []      []    !reset names !pos   !ts = do+  reset+  return $ Ended Report {position = pos, expected = names, unconsumed = ts}+parse []      !next !reset _names !pos !ts = do+  reset+  parse next [] (return ()) [] (pos + 1) (safeTail ts)+parse (st:ss) !next !reset names !pos !ts = case st of+  Final a -> return $ Parsed a pos ts $ parse ss next reset names pos+  State spos pr scont -> case pr of+    Terminal f p -> case uncons ts of+      Just (t, _) | f t -> parse ss (State spos (($ t) <$> p) scont : next) reset names pos ts+      _                 -> parse ss next reset names pos ts+    NonTerminal r p -> do+      rkref <- readSTRef $ ruleConts r+      ks    <- readSTRef rkref+      writeSTRef rkref (Cont spos p scont : ks)+      nulls' <- nullable r+      let notExpanded = null ks+          p'          = liftYoneda p+          nulls       = fmap (\a -> State spos (lowerYoneda $ ($ a) <$> p') scont) nulls'+      if notExpanded then do+        let st' = State pos (ruleProd r) rkref+        parse (st' : nulls ++ ss)+              next+              ((writeSTRef (ruleConts r) =<< newSTRef mempty) >> reset)+              names+              pos+              ts+      else+        parse (nulls ++ ss) next reset names pos ts+    Pure a | spos /= pos -> do+      conts <- simplifyCont scont+      parse (map (contToState a) conts ++ ss) next reset names pos ts+           | otherwise -> parse ss next reset names pos ts++    Plus p q    -> parse (State spos p scont : State spos q scont : ss) next reset names pos ts+    Many p q    -> do+      rkref <- newSTRef [Cont spos (Many p ((\f as a -> f (a : as)) <$> q)) scont]+      let st' = State pos p rkref+          nst = State spos (($ []) <$> q) scont+      parse (st' : nst : ss) next reset names pos ts+    Empty       -> parse ss next reset names pos ts++    Named pr' n -> parse (State spos pr' scont : ss) next reset (n : names) pos ts++{-# INLINE parser #-}+-- | Create a parser from the given grammar.+parser :: ListLike i t+       => (forall r. Grammar r e (Prod r e t a))+       -> i+       -> ST s (Result s e i a)+parser g xs = do+  s <- initialState =<< grammar g+  parse [s] [] (return ()) [] 0 xs++-- | Return all parses from the result of a given parser. The result may+-- contain partial parses. The 'Int's are the position at which a result was+-- produced.+allParses :: (forall s. ST s (Result s e i a)) -> ([(a, Int)], Report e i)+allParses p = runST $ p >>= go+  where+    go :: Result s e i a -> ST s ([(a, Int)], Report e i)+    go r = case r of+      Ended report     -> return ([], report)+      Parsed a pos i k -> fmap (first ((a, pos) :)) $ go =<< k i++{-# INLINE fullParses #-}+-- | Return all parses that reached the end of the input from the result of a+--   given parser.+fullParses :: ListLike i t => (forall s. ST s (Result s e i a)) -> ([a], Report e i)+fullParses p = runST $ p >>= go+  where+    go :: ListLike i t => Result s e i a -> ST s ([a], Report e i)+    go r = case r of+      Ended report -> return ([], report)+      Parsed a _ i k+        | ListLike.null i -> fmap (first (a :)) $ go =<< k i+        | otherwise       -> go =<< k i