1 -- This file is part of Diohsc
2 -- Copyright (C) 2020 Martin Bays <mbays@sdf.org>
4 -- This program is free software: you can redistribute it and/or modify
5 -- it under the terms of version 3 of the GNU General Public License as
6 -- published by the Free Software Foundation, or any later version.
8 -- You should have received a copy of the GNU General Public License
9 -- along with this program. If not, see http://www.gnu.org/licenses/.
11 {-# LANGUAGE LambdaCase #-}
24 import Control
.Monad
(guard, liftM2, mzero
)
25 import Data
.Bifunctor
(first
)
26 import Data
.Char (isAlphaNum)
27 import Data
.List
(foldl')
28 import Data
.Maybe (maybeToList)
29 import Safe
(atMay
, headMay
)
31 import Text
.Parsec
.String (Parser
)
37 deriving (Eq
,Ord
,Show)
40 = EsSElemSpec ElemSpec
41 | EsSRange
(Maybe ElemSpec
) (Maybe ElemSpec
)
43 deriving (Eq
,Ord
,Show)
45 type ElemsSpecs
= [ElemsSpec
]
47 elemsSpecsNum
:: Int -> ElemsSpecs
48 elemsSpecsNum
= (:[]) . EsSElemSpec
. ESNum
50 resolveElemsSpecs
:: String -> (String -> a
-> Bool) -> [a
] -> ElemsSpecs
-> Either String [a
]
51 resolveElemsSpecs typeStr match
as ess
=
52 mapM resolveElemsSpec ess
>>= (\case
53 [] -> Left
$ "No such " <> typeStr
54 as' -> return as') . concat
56 resolveElemsSpec
(EsSElemSpec es
) =
57 maybeToList . atMay
as <$> resolveES es
58 resolveElemsSpec
(EsSRange mes1 mes2
) = do
59 mns1
<- mapM resolveES mes1
60 mns2
<- mapM resolveES mes2
61 return $ if Just
True == liftM2 (>) mns1 mns2 || mns2
== Just
(-1)
62 then reverse . maybe id drop mns2
$ maybe id (take . (+ 1)) mns1
as
63 else maybe id drop mns1
$ maybe id (take . (+ 1)) mns2
as
64 resolveElemsSpec
(EsSSearch s
) = return $ filter (s `match`
) as
66 resolveES
:: ElemSpec
-> Either String Int
67 resolveES
(ESNum n
) = return $ n
- 1
68 resolveES
(ESNumFromEnd n
) = return $ length as - n
69 resolveES
(ESSearch s
) =
70 maybe (Left
$ "No " <> typeStr
<> " matches pattern: " ++ s
) Right
$
71 headMay
. map fst . filter snd . zip [0..] $ (s `match`
) <$> as
78 | PTargetLog ElemsSpecs
79 | PTargetQueue
String ElemsSpecs
81 | PTargetAncestors PTarget ElemsSpecs
82 | PTargetDescendants PTarget ElemsSpecs
84 { ptcIncreasing
:: Bool, ptcNoVisited
:: Bool
85 , ptcTarget
:: PTarget
, ptcSpecs
:: ElemsSpecs
}
87 { ptlNoVisited
:: Bool
88 , ptlTarget
:: PTarget
, ptlSpecs
:: ElemsSpecs
}
89 | PTargetRef PTarget
String
90 deriving (Eq
,Ord
,Show)
92 data CommandArg
= CommandArg
93 { commandArgArg
:: String
94 , commandArgLiteralTail
:: String
95 } deriving (Eq
,Ord
,Show)
98 = CommandLine
(Maybe PTarget
) (Maybe (String,[CommandArg
]))
99 deriving (Eq
,Ord
,Show)
101 parseCommandLine
:: String -> Either String CommandLine
102 parseCommandLine
= first
show . parse
(spaces
>> commandLine
<* eof
) ""
104 parseCommand
:: String -> Either String String
105 parseCommand
= first
show . parse command
""
107 commandLine
:: Parser CommandLine
109 [ char
'#' >> many anyChar
>> return (CommandLine Nothing Nothing
)
110 , liftM2 CommandLine
(optionMaybe target
) $ spaces
>> optionMaybe commandAndArgs
113 commandAndArgs
:: Parser
(String, [CommandArg
])
115 liftM2 (,) command
$ spaces
>> commandArgs
117 commandArgs
:: Parser
[CommandArg
]
118 commandArgs
= liftM2 (flip CommandArg
)
119 (lookAhead
$ many1 anyChar
) commandArg `sepEndBy` spaces
121 command
:: Parser
String
123 [ liftM2 (:) (oneOf
"|!") (many
$ oneOf
"|!-")
126 commandArg
:: Parser
String
127 commandArg
= escapedArg
129 escapedArg
:: Parser
String
130 escapedArg
= escapedWhile
$ noneOf
" "
132 escapedWhile
:: Parser
Char -> Parser
String
133 escapedWhile c
= many1
$
134 -- Allow escaping chars with \\, but preserve terminal \\
135 (char
'\\' >> (anyChar
<|
> (eof
>> pure
'\\'))) <|
> c
138 nat
= read <$> many1 digit
140 countMany
:: Parser
String -> Parser
Int
141 countMany s
= (s
>> (+ 1) <$> countMany s
) <|
> return 0
143 patt
:: Parser
String
144 patt
= escapedWhile
(noneOf
"^ ") <* optional
(char
'^
')
146 elemSpec
:: Parser ElemSpec
149 , char
'$' >> ESNumFromEnd
<$> choice
151 , (+ 1) <$> countMany
(string "$")
153 , char
'^
' >> ESSearch
<$> patt
]
155 elemsSpec
:: Parser ElemsSpec
157 [ try $ string "^^" >> EsSSearch
<$> patt
159 mess1
<- optionMaybe elemSpec
161 [ char
'-' >> EsSRange mess1
<$> optionMaybe elemSpec
162 , maybe mzero
(return . EsSElemSpec
) mess1
]
165 elemsSpecs
:: Parser ElemsSpecs
166 elemsSpecs
= sepBy1 elemsSpec
(char
',')
168 elemsSpecsBy
:: Parser
String -> Parser ElemsSpecs
169 elemsSpecsBy s
= s
>> choice
171 , elemsSpecsNum
. (+ 1) <$> countMany s
]
173 escapedArgStartingWith
:: Parser
Char -> Parser
String
174 escapedArgStartingWith p
= liftM2 (:) p
(escapedArg
<|
> return "")
176 ref
:: String -> Parser
String
177 ref
= escapedArgStartingWith
. oneOf
179 baseTarget
:: Parser PTarget
181 [ PTargetLog
<$> elemsSpecsBy
(string "$")
182 , PTargetLinks
False PTargetCurr
<$> elemsSpecs
183 , PTargetRef PTargetCurr
<$> ref
"./?"
186 PTargetQueue s
<$> elemsSpecsBy
(string "~")
187 , char
'\'' >> choice
188 [ char
'\'' >> return PTargetJumpBack
189 , PTargetMark
<$> many1 alphaNum
]
191 s
<- escapedArgStartingWith alphaNum
-- scheme required for other uris
192 guard . not $ all (\c
-> c `
elem`
"!|-" ||
isAlphaNum c
) s
193 return $ PTargetAbs s
194 , return PTargetCurr
]
196 targetMod
:: Parser
(PTarget
-> PTarget
)
198 [ char
'@' >> return PTargetRoot
199 , flip PTargetAncestors
<$> elemsSpecsBy
(string "<")
200 , flip PTargetDescendants
<$> elemsSpecsBy
(string ">")
201 , flip (PTargetChild
True False) <$> elemsSpecsBy
(string "]")
202 , flip (PTargetChild
True True) <$> elemsSpecsBy
(string "}")
203 , flip (PTargetChild
False False) <$> elemsSpecsBy
(string "[")
204 , flip (PTargetChild
False True) <$> elemsSpecsBy
(string "{")
205 , optional
(char
'_
') >> flip (PTargetLinks
False) <$> elemsSpecs
206 , char
'*' >> flip (PTargetLinks
True) <$> elemsSpecs
207 , flip PTargetRef
<$> ref
"/?"
210 target
:: Parser PTarget
213 mods
<- many targetMod
214 guard . not $ base
== PTargetCurr
&& null mods
215 return $ foldl' (flip ($)) base mods