{-| Module : Language.Rust.Parser Description : Parsing and lexing Copyright : (c) Alec Theriault, 2017-2018 License : BSD-style Maintainer : alec.theriault@gmail.com Stability : experimental Portability : GHC Selecting the right parser may require adding an annotation or using @-XTypeApplications@ to avoid an "Ambiguous type variable" error. Using 'Control.Monad.void' (as in the examples below) exploits the fact that most AST nodes are instances of 'Functor' to discard the 'Span' annotation that is attached to most parsed AST nodes. Conversely, if you wish to extract the 'Span' annotation, the 'Language.Rust.Syntax.AST.Located' typeclass provides a 'Language.Rust.Syntax.AST.spanOf' method. The examples below assume the following GHCi flags and imports: >>> :set -XTypeApplications -XOverloadedStrings >>> import Language.Rust.Syntax.AST >>> import Control.Monad ( void ) >>> import System.IO -} {-# LANGUAGE FlexibleInstances #-} module Language.Rust.Parser ( -- * Parsing parse, parse', readSourceFile, readTokens, Parse(..), P, execParser, execParserTokens, initPos, Span, -- * Lexing lexToken, lexNonSpace, lexTokens, translateLit, -- * Input stream readInputStream, hReadInputStream, inputStreamToString, inputStreamFromString, -- * Error reporting lexicalError, parseError, ParseFail(..), ) where import Language.Rust.Syntax import Language.Rust.Data.InputStream import Language.Rust.Data.Position ( Position, Span, Spanned, initPos ) import Language.Rust.Parser.Internal import Language.Rust.Parser.Lexer ( lexToken, lexNonSpace, lexTokens, lexicalError ) import Language.Rust.Parser.Literals ( translateLit ) import Language.Rust.Parser.ParseMonad ( P, execParser, parseError, pushToken, ParseFail(..) ) import Control.Exception ( throw ) import Data.Foldable ( traverse_ ) import System.IO ( Handle ) -- | Parse something from an input stream (it is assumed the initial position is 'initPos'). -- -- >>> fmap void $ parse @(Expr Span) "x + 1" -- Right (Binary [] AddOp (PathExpr [] Nothing (Path False [PathSegment "x" Nothing ()] ()) ()) -- (Lit [] (Int Dec 1 Unsuffixed ()) ()) -- ()) -- -- >>> fmap void $ parse @(Expr Span) "x + " -- Left (parse failure at 1:4 (Syntax error: unexpected `' (expected an expression))) -- parse :: Parse a => InputStream -> Either ParseFail a parse is = execParser parser is initPos -- | Same as 'parse', but throws a 'ParseFail' exception if it cannot parse. This function is -- intended for situations in which you are already stuck catching exceptions - otherwise you should -- prefer 'parse'. -- -- >>> void $ parse' @(Expr Span) "x + 1" -- Binary [] AddOp (PathExpr [] Nothing (Path False [PathSegment "x" Nothing ()] ()) ()) -- (Lit [] (Int Dec 1 Unsuffixed ()) ()) -- () -- -- >>> void $ parse' @(Expr Span) "x + " -- *** Exception: parse failure at 1:4 (Syntax error: unexpected `' (expected an expression)) -- parse' :: Parse a => InputStream -> a parse' = either throw id . parse -- | Same as 'execParser', but working from a list of tokens instead of an 'InputStream'. execParserTokens :: P a -> [Spanned Token] -> Position -> Either ParseFail a execParserTokens p toks = execParser (pushTokens toks *> p) (inputStreamFromString "") where pushTokens = traverse_ pushToken . reverse -- | Given a handle to a Rust source file, read that file and parse it into a 'SourceFile' -- -- >>> writeFile "empty_main.rs" "fn main() { }" -- >>> fmap void $ withFile "empty_main.rs" ReadMode readSourceFile -- SourceFile Nothing [] [Fn [] InheritedV "main" -- (FnDecl [] Nothing False ()) -- Normal NotConst Rust -- (Generics [] [] (WhereClause [] ()) ()) -- (Block [] Normal ()) ()] -- readSourceFile :: Handle -> IO (SourceFile Span) readSourceFile hdl = parse' <$> hReadInputStream hdl -- | Given a path pointing to a Rust source file, read that file and lex it (ignoring whitespace) -- -- >>> writeFile "empty_main.rs" "fn main() { }" -- >>> withFile "empty_main.rs" ReadMode readTokens -- [fn,main,(,),{,}] -- readTokens :: Handle -> IO [Spanned Token] readTokens hdl = do inp <- hReadInputStream hdl case execParser (lexTokens lexNonSpace) inp initPos of Left pf -> throw pf Right x -> pure x -- | Describes things that can be parsed class Parse a where -- | Complete parser (fails if not all of the input is consumed) parser :: P a instance Parse (Lit Span) where parser = parseLit instance Parse (Attribute Span) where parser = parseAttr instance Parse (Ty Span) where parser = parseTy instance Parse (Pat Span) where parser = parsePat instance Parse (Expr Span) where parser = parseExpr instance Parse (Stmt Span) where parser = parseStmt instance Parse (Item Span) where parser = parseItem instance Parse (SourceFile Span) where parser = parseSourceFile instance Parse TokenTree where parser = parseTt instance Parse TokenStream where parser = parseTokenStream instance Parse (Block Span) where parser = parseBlock instance Parse (ImplItem Span) where parser = parseImplItem instance Parse (TraitItem Span) where parser = parseTraitItem instance Parse (TyParam Span) where parser = parseTyParam instance Parse (LifetimeDef Span) where parser = parseLifetimeDef instance Parse (Generics Span) where parser = parseGenerics instance Parse (WhereClause Span) where parser = parseWhereClause