Parsing
Tip
Understanding parser combinators is a prerequisite for this module. See here for information.
Parser.hs | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
|
- It's often useful to make a custom type for errors specific to the domain in question.
asum
comes from theAlternative
typeclass:asum [x,y,z]
=x <|> y <|> z
.either :: (a -> c) -> (b -> c) -> Either a b -> c
is a useful function for handling anEither X Y
.- Viewed with the Haskell Language Server, this will be underlined in blue, with a simplification suggested.
- Like anything else in Haskell, parsers are just values, so it's possible (and idiomatic) to put them in a list and then fold the list.
- Observe how the parser as a whole is built out of smaller parsers.
"place"
is itself a parser, thanks to OverloadedStrings.a <$> b = fmap a b
. That is:<$>
is an infix synonym forfmap
. Example withconst
:fmap (const 1) [1,2,3,4] = [1,1,1,1]
- We abstract some boilerplate with a function that makes a simple parser given a name and return value.
eof
only succeeds at the end of a line, so ensures there are no more characters left.return
is not a keyword. Here, it converts anInstruction
into aParser Instruction
, namely the trivial parser that does nothing and immediately returns anInstruction
. ThisParser Instruction
is the final parser in the sequence of parsers in thedo-notation
code block.- do-notation used to build a complex parser out of a series of simpler ones and their results.
- Explicit imports like this are useful both for readability and to avoid namespace clashes.
- Another way to avoid namespace clashes. Common for both
Data.Map
andData.Text
. - Void is the empty type. This says that the custom error type is
Void
(i.e. doesn't exist), and that the type of the input sequence isText
.
Analysis¶
This module exists to parse user input on the command line into the Instruction
type. Parsing directly into a custom type is idiomatic Haskell because it handles errors nicely: either parsing succeeds and you're guaranteed to get an Instruction
, or you get an interpretable parse failure.
Note the use of word
, a function which takes a parser and returns a new parser that handles white space. This abstracts white space handling to a single function, and makes for clean, idiomatic parsing.
parsePiece
¶
```haskell
parsePiece :: Ord a => Parsec a Text Piece parsePiece = do color <- word $ (const White <\(> "white") <|> (const Black <\)> "black") pieceType <- word $ try (const Bishop <\(> "bishop") <|> try (const King <\)> "king") <|> try (const Queen <\(> "queen") <|> try (const Knight <\)> "knight") <|> try (const Rook <\(> "rook") <|> try (const Pawn <\)> "pawn") return (Piece pieceType color) ```
```haskell
parsePiece :: Ord a => Parsec a Text Piece parsePiece = do color <- word $ (const White <\(> "white") <|> (const Black <\)> "black") pieceType <- word $ asum [try (const piece <$> "bishop") | piece <- [Bishop, King, Queen, Knight, Rook, Pawn]] return (Piece pieceType color) ```
```haskell
parsePiece :: Ord a => Parsec a Text Piece parsePiece = do color <- word $ ( White <$ "white") <|> ( Black <$ "black") pieceType <- word $ asum [try ( piece <$ "bishop") | piece <- [Bishop, King, Queen, Knight, Rook, Pawn]] return (Piece pieceType color) ```
parseRank
¶
```haskell
parseRank :: Rank -> Parser Rank parseRank x = const x <$> char ( case x of One -> '1' Two -> '2' Three -> '3' Four -> '4' Five -> '5' Six -> '6' Seven -> '7' Eight -> '8' ) ```
```haskell
import qualified Data.Map as M
parseRank :: Rank -> Parser Rank
parseRank x = const x <$>
case x `M.lookup` M.fromList (zip [One .. Eight] ['1'..'8']) of
Just c -> char c
Nothing -> error "unreachable state"
```
Created: August 18, 2022