-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathModularParser.hs
More file actions
115 lines (98 loc) · 2.46 KB
/
ModularParser.hs
File metadata and controls
115 lines (98 loc) · 2.46 KB
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
112
113
114
115
--
-- Idea is to have a modular parser where the features is decided by
-- a type list.
--
-- This example is creates a parser that can parse + and one digit
-- numbers
--
-- Only tested in ghci 8.6
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE TypeApplications #-}
import Text.Read
--
-- Wrapper around the type level list as it otherwise would have the
-- wrong kind, [*] instead of *
--
data P xs where
Nil :: P '[]
Cons :: a -> P as -> P (a ': as)
--
-- Types for our different parsers/features
--
data Add
data Mul
data Val
--
-- The AST we want to produce
--
data AST
= Plus AST AST
| Times AST AST
| Value Int
deriving Show
--
-- Alias class to have less type applications later
--
class Parsing ps where
try :: String -> Maybe (AST, String)
instance Parsing' (P ps) (P ps) => Parsing (P ps) where
try = try' @(P ps) @(P ps)
--
-- The wrapping parsing class working on two lists of parsers.
-- ps is the parsers left to try on the current input
-- qs is the parsers that will be used on the child expressions
--
class Parsing' ps qs where
try' :: String -> Maybe (AST, String)
--
-- No parsers left to try
--
instance Parsing' (P '[]) qs where
try' _ = Nothing
--
-- The recursive step where we try one parser and if it fails then try the next
-- one.
--
instance (Parsing' (P ps) qs, Parser p qs) => Parsing' (P (p ': ps)) qs where
try' str = case parse @p @qs str of
Just a -> Just a
Nothing -> try' @(P ps) @qs str
--
-- The implementation of a parser
-- p is the current parser to try
-- qs is a list of parsers to use on the children
--
class Parser p qs where
parse :: String -> Maybe (AST, String)
--
-- Implemenetation for the value parser
--
instance Parser Val qs where
parse (x:xs) = do
v <- readMaybe @Int [x]
Just (Value v, xs)
parse _ = Nothing
--
-- Implementation for the plus parser
--
instance Parsing qs => Parser Add qs where
parse ('+':str) = do
(v1, str') <- try @qs str
(v2, str'') <- try @qs str'
Just (Plus v1 v2, str'')
parse _ = Nothing
--
-- Examples
--
-- A helper so that we can skip the P constructor
run :: forall xs. Parsing (P xs) => String -> Maybe (AST, String)
run = try @(P xs)
main = print $ run @'[Add, Val] "+23"