import Data.Char |
|
-- A simple parse of a list of tokens, each token a digit char, |
-- to produce an integer. |
|
-- a typical parse scans the tokens and accumulates the intermediate |
-- results of the parse. So the recursive form takes a pair |
-- ( tokens, results ) -> ( tokens', results') |
-- where the parse step consumes some tokens and computes new results. |
-- In general, the idea is to take the current state of the parse |
-- in (tokens, results), remove the first token to get tokens', and then |
-- process that token in the context of the current results and product a |
-- new set results' |
|
tokenToDigit :: Char -> Int |
tokenToDigit c |
| c == '0' = 0 |
| c == '1' = 1 |
| c == '2' = 2 |
| c == '3' = 3 |
| c == '4' = 4 |
| c == '5' = 5 |
| c == '6' = 6 |
| c == '7' = 7 |
| c == '8' = 8 |
| c == '9' = 9 |
|
parseNumber1_r :: ( [ Char ], Int ) -> ( [ Char ], Int) |
parseNumber1_r ( [], result ) = ( [], result ) |
parseNumber1_r ( (token:tokens), result ) = |
parseNumber1_r ( tokens, result * 10 + (tokenToDigit token) ) |
|
parseNumber1 :: [ Char ] -> Int |
parseNumber1 s = snd ( parseNumber1_r ( s, 0) ) |
|
-- Now let's generalize the parser to take a function combine that is |
-- used to combine the current token with the previous result to produce |
-- the new result. We handle the fetching of the next token etc. in the |
-- recursor. |
|
parseNumber2_r :: (Char -> Int -> Int) -> ( [ Char ], Int ) -> ([ Char ], Int) |
|
parseNumber2_r combine ( [], result ) = ( [], result ) |
|
parseNumber2_r combine ( (token:tokens), result ) = |
(parseNumber2_r combine ( tokens, ( combine token result) ) ) |
|
parseNumber2 :: [ Char ] -> Int |
parseNumber2 s = snd ( parseNumber2_r |
-- the combin function does the same as above |
(\ token result -> result * 10 + (tokenToDigit token)) |
( s, 0) ) |
|
-- Let's express this same code, but using type synonyms to make the |
-- function signatures clearer |
type Token = Char |
type Result = Int |
|
-- Also, instead of doing the parse immediately, return a parser function |
-- that takes a list of tokens and returns a result |
|
parseLang1 :: (Token -> Result -> Result) -> Result -> |
( [ Token ] -> Result ) |
|
parseLang1 combine init_result = \ tokens -> |
snd ( parseLang1_r combine ( tokens, init_result ) ) |
|
parseLang1_r :: (Token -> Result -> Result) -> |
( [ Token ], Result ) -> ([ Token ], Result) |
|
parseLang1_r combine ( [], result ) = ( [], result ) |
|
parseLang1_r combine ( (token:tokens), result ) = |
(parseLang1_r combine ( tokens, ( combine token result) ) ) |
|
parseNumber3 = parseLang1 |
(\ token result -> result * 10 + (tokenToDigit token)) 0 |
|
-- In general, Result is not going to be sufficient for keeping track |
-- of the intermediate parse information. So we introduce |
-- an Intermediate type which used during the parse, and a function |
-- deliver :: Intermediate -> Result |
-- that extracts the final result from the intermediate data of the parse |
-- Our parse state is now of type ( [ Tokens ] , Intermediate ) |
|
type Intermediate = Int |
|
-- again, return a parser |
parseLang2 :: (Token -> Intermediate -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> |
( [ Token ] -> Result ) |
|
parseLang2 combine init_intermediate deliver = \ tokens -> |
deliver (snd ( parseLang2_r combine ( tokens, init_intermediate ) ) ) |
|
parseLang2_r :: (Token -> Intermediate -> Intermediate) -> |
( [ Token ], Intermediate ) -> ([ Token ], Intermediate) |
|
parseLang2_r combine ( [], intermediate ) = ( [], intermediate ) |
|
parseLang2_r combine ( (token:tokens), intermediate ) = |
(parseLang2_r combine ( tokens, ( combine token intermediate ) ) ) |
|
-- and our final act is to generate a parser for numbers. Note how the |
-- deliver function is just the identitiy in this case. |
|
parseNumber4 = parseLang2 |
(\ token intermediate -> intermediate * 10 + (tokenToDigit token)) |
0 |
(\ intermediate -> intermediate ) |
|
-- Next, we want to allow negative numbers. |
import Data.Char |
|
-- A generic look-ahead-1 token parser |
|
parseGen :: (Token -> Intermediate -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> ( [ Token ] -> Result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
deliver (snd ( parseGen_r combine ( tokens, init_intermediate ) ) ) |
|
parseGen_r :: (Token -> Intermediate -> Intermediate) -> |
( [ Token ], Intermediate ) -> ([ Token ], Intermediate) |
|
parseGen_r combine ( [], intermediate ) = ( [], intermediate ) |
|
parseGen_r combine ( (token:tokens), intermediate ) = |
(parseGen_r combine ( tokens, ( combine token intermediate ) ) ) |
|
-- now build a parser that understands negative numbers |
|
type Token = Char |
type Result = Int |
type Intermediate = (Char, Int) |
|
-- The intermediate state of the parse is represented by a pair. The first |
-- element is a Char: |
-- ' ' meaning that no digit has been seen yet |
-- '-' meaning that a - sign was encountered before the first digit |
-- '+' meaning that a + sign was encountered before the first digit, or |
-- that no sign at all was encountered. |
|
-- Note, we can use Char.isDigit c to test is c is a digit |
tokenToDigit :: Token -> Int |
tokenToDigit c |
| c == '0' = 0 |
| c == '1' = 1 |
| c == '2' = 2 |
| c == '3' = 3 |
| c == '4' = 4 |
| c == '5' = 5 |
| c == '6' = 6 |
| c == '7' = 7 |
| c == '8' = 8 |
| c == '9' = 9 |
|
combine' :: (Token -> Intermediate -> Intermediate) |
combine' token (sign, value ) |
-- handle the leading sign, if any |
| sign == ' ' && token == '-' = ('-', value) |
| sign == ' ' && token == '+' = ('+', value) |
| sign == ' ' = ('+', tokenToDigit token) |
| otherwise = (sign , value * 10 + (tokenToDigit token)) |
|
deliver' :: (Intermediate -> Result ) |
deliver' (sign, value ) |
| sign == '-' = (0 - value) |
| otherwise = value |
|
parseNumber = parseGen combine' (' ', 0) deliver' |
import Data.Char |
|
-- A generic look-ahead-1 parser |
|
parseGen :: (Token -> Intermediate -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> ( [ Token ] -> Result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
deliver (snd ( parseGen_r combine ( tokens, init_intermediate ) ) ) |
|
parseGen_r :: (Token -> Intermediate -> Intermediate) -> |
( [ Token ], Intermediate ) -> ([ Token ], Intermediate) |
|
parseGen_r combine ( [], intermediate ) = ( [], intermediate ) |
|
parseGen_r combine ( (token:tokens), intermediate ) = |
(parseGen_r combine ( tokens, ( combine token intermediate ) ) ) |
|
-- now build a parser that evaluates postfix + * arithmetic expressions |
|
type Token = String |
type Result = Int |
type Intermediate = [ Int ] |
|
combine' :: (Token -> Intermediate -> Intermediate) |
combine' "+" (x:y:stack) = (y+x) : stack |
combine' "*" (x:y:stack) = (y*x) : stack |
combine' token stack = (read token::Int) : stack |
|
deliver' :: (Intermediate -> Result ) |
deliver' (top:rest) = top |
|
-- if you wanted to return the string representation, you could do this |
-- type Result = String |
-- deliver' (top:rest) = show top |
|
evalArith = parseGen combine' [] deliver' |
import Data.Char |
|
parseGen :: (Token -> Intermediate -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> ( [ Token ] -> Result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
deliver (snd ( parseGen_r combine ( tokens, init_intermediate ) ) ) |
|
parseGen_r :: (Token -> Intermediate -> Intermediate) -> |
( [ Token ], Intermediate ) -> ([ Token ], Intermediate) |
|
parseGen_r combine ( [], intermediate ) = ( [], intermediate ) |
|
parseGen_r combine ( (token:tokens), intermediate ) = |
(parseGen_r combine ( tokens, ( combine token intermediate ) ) ) |
|
-- now build a Boolean formula evaluator |
|
type Token = Char |
type Result = String |
type Intermediate = [ Bool ] |
|
combine' :: (Token -> Intermediate -> Intermediate) |
combine' ' ' stack = stack |
combine' '0' stack = False:stack |
combine' '1' stack = True:stack |
combine' '+' (x:y:stack) = (x || y):stack |
combine' '*' (x:y:stack) = (x && y):stack |
|
deliver' :: (Intermediate -> Result ) |
deliver' (top:stack) |
| top = "1" |
| otherwise = "0" |
|
evalBool = parseGen combine' [] deliver' |
|
-- evalBool ( (evalBool "1 0 +") ++ (evalBool "1 1 *") ++ "+" ) |
import Data.Char |
|
-- A generic look-ahead-1 parser |
|
parseGen :: (Token -> Intermediate -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> ( [ Token ] -> Result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
deliver (snd ( parseGen_r ( tokens, init_intermediate ) ) ) |
where |
|
parseGen_r :: ( [ Token ], Intermediate ) -> ([ Token ], Intermediate) |
|
parseGen_r ( [], intermediate ) = ( [], intermediate ) |
|
parseGen_r ( (token:tokens), intermediate ) = |
(parseGen_r ( tokens, ( combine token intermediate ) ) ) |
|
-- now build a parser that evaluates postfix + * arithmetic expressions |
|
type Token = String |
type Result = Int |
type Intermediate = [ Int ] |
|
combine' :: (Token -> Intermediate -> Intermediate) |
combine' "+" (x:y:stack) = (y+x) : stack |
combine' "*" (x:y:stack) = (y*x) : stack |
combine' token stack = (read token::Int) : stack |
|
deliver' :: (Intermediate -> Result ) |
deliver' (top:rest) = top |
|
-- if you wanted to return the string representation, you could do this |
-- type Result = String |
-- deliver' (top:rest) = show top |
|
evalArith = parseGen combine' [] deliver' |
import Data.Char |
|
-- A generic look-ahead-1 parser |
|
parseGen :: (Token -> Intermediate -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> ( [ Token ] -> Result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
(deliver (foldl (\ i t -> combine t i) init_intermediate tokens )) |
|
-- now build a parser that evaluates postfix + * arithmetic expressions |
|
type Token = String |
type Result = Int |
type Intermediate = [ Int ] |
|
combine' :: (Token -> Intermediate -> Intermediate) |
combine' "+" (x:y:stack) = (y+x) : stack |
combine' "*" (x:y:stack) = (y*x) : stack |
combine' token stack = (read token::Int) : stack |
|
deliver' :: (Intermediate -> Result ) |
deliver' (top:rest) = top |
|
-- if you wanted to return the string representation, you could do this |
-- type Result = String |
-- deliver' (top:rest) = show top |
|
evalArith = parseGen combine' [] deliver' |
import Data.Char |
|
-- Check out http://learnyouahaskell.com/making-our-own-types-and-typeclasses |
|
-- A generic look-ahead-1 parser |
|
-- Modified from previous example to swap order of arguments to |
-- combine so that its type matches that required by foldl |
|
parseGen :: (Intermediate -> Token -> Intermediate) -> Intermediate -> |
(Intermediate -> Result ) -> ( [ Token ] -> Result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
(deliver (foldl combine init_intermediate tokens )) |
|
-- now build a parser that evaluates postfix + * arithmetic expressions |
|
-- encapsulate a value of type e or an error message |
-- a Value can be a number or an error |
data Value e = OK e | Error String deriving Eq |
|
-- assuming we know how type e can be shown, i.e. Show e, then |
-- we know how to show a Value e type |
instance (Show e) => Show (Value e) where |
show (OK x) = (show x) |
show (Error s) = "ERROR: " ++ s |
|
type Token = String |
type Result = Value Int |
type Intermediate = [ (Value Int) ] |
|
-- an algebra is a things that knows about plus and times |
class Algebra a where |
plus :: a -> a -> a |
times :: a -> a -> a |
|
-- assuming that we know how to + and * things of type e, (i.e. |
-- we have Num e, then we have algrbra's over Value e |
instance (Num e) => Algebra (Value e) where |
plus (OK x) (OK y) = (OK (x+y)) |
times (OK x) (OK y) = (OK (x*y)) |
-- ? need to handle ERROR also |
|
-- Now we have a calculator that works on type Value |
combine' :: (Intermediate -> Token -> Intermediate) |
combine' (x:y:stack) "+" = (plus y x) : stack |
combine' (x:y:stack) "*" = (times y x) : stack |
combine' stack token = (OK (read token::Int)) : stack |
|
deliver' :: (Intermediate -> Result ) |
deliver' [] = Error "No result on stack" |
deliver' [top] = top |
deliver' ((Error top):rest) = Error top |
-- ? what else |
|
evalArith = parseGen combine' [] deliver' |
calc = evalArith . words |
import Data.Char |
|
-- Check out http://learnyouahaskell.com/making-our-own-types-and-typeclasses |
|
-- parsGen :: |
-- (intermediate -> token -> intermediate) -> intermediate -> |
-- (intermediate -> result ) -> ( [ token ] -> result ) |
|
parseGen combine init_intermediate deliver = \ tokens -> |
(deliver (foldl combine init_intermediate tokens )) |
|
-- now build a parser that evaluates postfix + * arithmetic expressions |
|
-- type constuctors take types and produce new types |
|
-- encapsulate a value of type e or an error message |
-- a Value can be a number or an error |
-- Value is a type constructor, OK and Error are value constructors |
data Value e = OK e | Error String deriving Eq |
|
-- assuming we know how type e can be shown, i.e. Show e, then |
-- we know how to show a Value e type, so Value e is then an instance |
-- of the Show typeclass. Think of a typeclass as an interface |
|
instance (Show e) => Show (Value e) where |
show (OK x) = (show x) |
show (Error s) = "ERROR: " ++ s |
|
|
-- the typeclass Algebra |
-- an algebra is a things that knows about plus and times |
class Algebra a where |
plus :: a -> a -> a |
times :: a -> a -> a |
divide :: a -> a -> a |
|
-- assuming that we know how to + and * things of type e, (i.e. |
-- we have Num e, then we have algebra's over Value e |
{- |
instance (Num e) => Algebra (Value e) where |
plus (OK x) (OK y) = (OK (x+y)) |
plus (Error s) _ = (Error s) |
plus _ (Error s) = (Error s) |
times (OK x) (OK y) = (OK (x*y)) |
times (Error s) _ = (Error s) |
times _ (Error s) = (Error s) |
divide _ _ = (Error "No divide operation") |
-} |
|
-- if we want to introduce division, we need the Integral typeclass |
-- which then means we get the Num properties also. |
instance (Num e, Integral e) => Algebra (Value e) where |
plus (OK x) (OK y) = (OK (x+y)) |
plus (Error s) _ = (Error s) |
plus _ (Error s) = (Error s) |
times (OK x) (OK y) = (OK (x*y)) |
times (Error s) _ = (Error s) |
times _ (Error s) = (Error s) |
divide (OK x) (OK y) |
| y == 0 = (Error "divide by zero") |
| True = (OK (quot x y)) |
divide (Error s) _ = (Error s) |
divide _ (Error s) = (Error s) |
|
-- to get the instances of Algebra in ghci try :info Algebra |
-- to get the kind of type constructor Value in ghci try :k Value |
|
-- type Token = String |
-- type Result = Value Int |
-- type Intermediate = [ (Value Int) ] |
|
parseNum t = case reads t :: [(Int, String)] of |
[(x, "")] -> (OK x) |
_ -> (Error ("not a Number " ++ t)) |
|
|
applyOp "+" y x = (plus y x) |
applyOp "*" y x = (times y x) |
applyOp "/" y x = (divide y x) |
applyOp token _ _ = (Error ("Not an operator: " ++ token) ) |
|
|
-- Now we have a calculator that works on type Value |
-- combine' :: (Intermediate -> Token -> Intermediate) |
combine' stack token = case (parseNum token) of |
(OK x) -> (OK x) : stack |
(Error _) -> case stack of |
(x:y:rest) -> (applyOp token y x) : rest |
_ -> [ (Error ("Insufficient arguments on stack for " ++ token) ) ] |
|
-- deliver' :: (Intermediate -> Result ) |
deliver' [] = Error "No result on stack" |
deliver' [top] = top |
deliver' ((Error top):rest) = Error top |
deliver' (top:stack) = Error "Too many results on stack" |
|
evalArith = parseGen combine' [] deliver' |
calc = evalArith . words |