• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

msakai / pseudo-boolean / 119

04 Apr 2025 02:16PM UTC coverage: 93.287% (-0.3%) from 93.632%
119

push

github

web-flow
GitHub Actions: use GHC-9.8 for coverage (cont'd)

542 of 581 relevant lines covered (93.29%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.38
/src/Data/PseudoBoolean/Parsec.hs
1
{-# LANGUAGE BangPatterns, FlexibleContexts #-}
2
{-# OPTIONS_GHC -Wall #-}
3
-----------------------------------------------------------------------------
4
-- |
5
-- Module      :  Data.PseudoBoolean.Parsec
6
-- Copyright   :  (c) Masahiro Sakai 2011-2015
7
-- License     :  BSD-style
8
-- 
9
-- Maintainer  :  masahiro.sakai@gmail.com
10
-- Portability :  non-portable (BangPatterns, FlexibleContexts)
11
--
12
-- A parser library for OPB file and WBO files used in pseudo boolean competition.
13
-- 
14
-- References:
15
--
16
-- * Input/Output Format and Solver Requirements for the Competitions of
17
--   Pseudo-Boolean Solvers
18
--   <http://www.cril.univ-artois.fr/PB11/format.pdf>
19
--
20
-----------------------------------------------------------------------------
21

22
module Data.PseudoBoolean.Parsec
23
  (
24
  -- * Parsing OPB files
25
    opbParser
26
  , parseOPBString
27
  , parseOPBByteString
28
  , parseOPBFile
29

30
  -- * Parsing WBO files
31
  , wboParser
32
  , parseWBOString
33
  , parseWBOByteString
34
  , parseWBOFile
35
  ) where
36

37
import Prelude hiding (sum)
38
import Control.Monad
39
import Data.ByteString.Lazy (ByteString)
40
import qualified Data.ByteString.Lazy as BL
41
import Data.Maybe
42
import qualified Data.Text.Lazy.Encoding as TL
43
import Text.Parsec
44
import Data.PseudoBoolean.Types
45
import Data.PseudoBoolean.Internal.TextUtil
46

47
-- | Parser for OPB files
48
opbParser :: Stream s m Char => ParsecT s u m Formula
49
opbParser = formula
×
50

51
-- | Parser for WBO files
52
wboParser :: Stream s m Char => ParsecT s u m SoftFormula
53
wboParser = softformula
×
54

55
-- <formula>::= <sequence_of_comments> [<objective>] <sequence_of_comments_or_constraints>
56
formula :: Stream s m Char => ParsecT s u m Formula
57
formula = do
1✔
58
  h <- optionMaybe hint
1✔
59
  sequence_of_comments
1✔
60
  obj <- optionMaybe objective
1✔
61
  cs <- sequence_of_comments_or_constraints
1✔
62
  return $
1✔
63
    Formula
1✔
64
    { pbObjectiveFunction = obj
1✔
65
    , pbConstraints = cs
1✔
66
    , pbNumVars = fromMaybe (pbComputeNumVars (fmap snd obj) cs) (fmap fst h)
1✔
67
    , pbNumConstraints = fromMaybe (length cs) (fmap snd h)
1✔
68
    }
69

70
hint :: Stream s m Char => ParsecT s u m (Int,Int)
71
hint = try $ do
1✔
72
  _ <- char '*'
1✔
73
  zeroOrMoreSpace
1✔
74
  _ <- string "#variable="
1✔
75
  zeroOrMoreSpace
1✔
76
  nv <- unsigned_integer
1✔
77
  oneOrMoreSpace  
1✔
78
  _ <- string "#constraint="
1✔
79
  zeroOrMoreSpace
1✔
80
  nc <- unsigned_integer
1✔
81
  _ <- manyTill anyChar eol
1✔
82
  return (fromIntegral nv, fromIntegral nc)
1✔
83

84
-- <sequence_of_comments>::= <comment> [<sequence_of_comments>]
85
sequence_of_comments :: Stream s m Char => ParsecT s u m ()
86
sequence_of_comments = skipMany comment -- XXX: we allow empty sequence
1✔
87

88
-- <comment>::= "*" <any_sequence_of_characters_other_than_EOL> <EOL>
89
comment :: Stream s m Char => ParsecT s u m ()
90
comment = do
1✔
91
  _ <- char '*' 
1✔
92
  _ <- manyTill anyChar eol
1✔
93
  spaces -- We relax the grammer and allow spaces in the beggining of next component.
1✔
94
  return ()
×
95

96
-- <sequence_of_comments_or_constraints>::= <comment_or_constraint> [<sequence_of_comments_or_constraints>]
97
sequence_of_comments_or_constraints :: Stream s m Char => ParsecT s u m [Constraint]
98
sequence_of_comments_or_constraints = do
1✔
99
  xs <- many comment_or_constraint -- We relax the grammer and allow spaces in the beginning of next component.
1✔
100
  return $ catMaybes xs
1✔
101

102
-- <comment_or_constraint>::= <comment>|<constraint>
103
comment_or_constraint :: Stream s m Char => ParsecT s u m (Maybe Constraint)
104
comment_or_constraint =
1✔
105
  (comment >> return Nothing) <|> (liftM Just constraint)
1✔
106

107
-- <objective>::= <objective_type> <zeroOrMoreSpace> <sum> ";"
108
objective :: Stream s m Char => ParsecT s u m Objective
109
objective = do
1✔
110
  dir <- objective_type
1✔
111
  zeroOrMoreSpace
1✔
112
  obj <- sum
1✔
113
  semi
1✔
114
  return (dir, obj)
1✔
115

116
-- <constraint>::= <sum> <relational_operator> <zeroOrMoreSpace> <integer> <zeroOrMoreSpace> ";"
117
constraint :: Stream s m Char => ParsecT s u m Constraint
118
constraint = do
1✔
119
  lhs <- sum
1✔
120
  op <- relational_operator
1✔
121
  zeroOrMoreSpace
1✔
122
  rhs <- integer
1✔
123
  zeroOrMoreSpace
1✔
124
  semi
1✔
125
  return (lhs, op, rhs)
1✔
126

127
-- <sum>::= <weightedterm> | <weightedterm> <sum>
128
sum :: Stream s m Char => ParsecT s u m Sum
129
sum = many weightedterm -- we relax the grammer to allow empty sum
1✔
130

131
-- <weightedterm>::= <integer> <oneOrMoreSpace> <term> <oneOrMoreSpace>
132
weightedterm :: Stream s m Char => ParsecT s u m WeightedTerm
133
weightedterm = do
1✔
134
  w <- integer
1✔
135
  oneOrMoreSpace
1✔
136
  t <- term
1✔
137
  zeroOrMoreSpace -- we relax the grammar to allow omitting spaces at the end of <sum>.
1✔
138
  return (w,t)
1✔
139

140
-- <integer>::= <unsigned_integer> | "+" <unsigned_integer> | "-" <unsigned_integer>
141
integer :: Stream s m Char => ParsecT s u m Integer
142
integer = msum
1✔
143
  [ unsigned_integer
1✔
144
  , char '+' >> unsigned_integer
1✔
145
  , char '-' >> liftM negate unsigned_integer
1✔
146
  ]
147

148
-- <unsigned_integer>::= <digit> | <digit><unsigned_integer>
149
unsigned_integer :: Stream s m Char => ParsecT s u m Integer
150
unsigned_integer = do
1✔
151
  ds <- many1 digit
1✔
152
  return $! readUnsignedInteger ds
1✔
153

154
-- <objective_type>::= "min:" | "max:"
155
objective_type :: Stream s m Char => ParsecT s u m OptDir
156
objective_type = (try (string "min:") >> return OptMin) <|> (string "max:" >> return OptMax)
1✔
157

158
-- <relational_operator>::= ">=" | "="
159
relational_operator :: Stream s m Char => ParsecT s u m Op
160
relational_operator = msum $ map try
1✔
161
  [ string "=" >> return Eq
1✔
162
  , string "!=" >> return NEq
1✔
163
  , string ">=" >> return Ge
1✔
164
  , string ">" >> return Gt
1✔
165
  , string "<=" >> return Le
1✔
166
  , string "<" >> return Lt
1✔
167
  , string "≠" >> return NEq
1✔
168
  , string "≥" >> return Ge
1✔
169
  , string "≤" >> return Le
1✔
170
  ]
171

172
-- <variablename>::= "x" <unsigned_integer>
173
variablename :: Stream s m Char => ParsecT s u m Var
174
variablename = do
1✔
175
  _ <- char 'x'
1✔
176
  i <- unsigned_integer
1✔
177
  return $! fromIntegral i
1✔
178

179
-- <oneOrMoreSpace>::= " " [<oneOrMoreSpace>]
180
oneOrMoreSpace :: Stream s m Char => ParsecT s u m ()
181
oneOrMoreSpace  = skipMany1 (char ' ')
1✔
182

183
-- <zeroOrMoreSpace>::= [" " <zeroOrMoreSpace>]
184
zeroOrMoreSpace :: Stream s m Char => ParsecT s u m ()
185
-- zeroOrMoreSpace = skipMany (char ' ')
186
zeroOrMoreSpace = spaces
1✔
187
-- We relax the grammer and allow more type of spacing
188

189
eol :: Stream s m Char => ParsecT s u m ()
190
eol = char '\n' >> return ()
×
191

192
semi :: Stream s m Char => ParsecT s u m ()
193
semi = char ';' >> spaces
1✔
194
-- We relax the grammer and allow spaces in the beginning of next component.
195

196
{-
197
For linear pseudo-Boolean instances, <term> is defined as
198
<term>::=<variablename>
199

200
For non-linear instances, <term> is defined as
201
<term>::= <oneOrMoreLiterals>
202
-}
203
term :: Stream s m Char => ParsecT s u m Term
204
term = oneOrMoreLiterals
1✔
205

206
-- <oneOrMoreLiterals>::= <literal> | <literal> <oneOrMoreSpace> <oneOrMoreLiterals>
207
oneOrMoreLiterals :: Stream s m Char => ParsecT s u m [Lit]
208
oneOrMoreLiterals = do
1✔
209
  l <- literal
1✔
210
  mplus (try $ oneOrMoreSpace >> liftM (l:) oneOrMoreLiterals) (return [l])
1✔
211
-- Note that we cannot use sepBy1.
212
-- In "p `sepBy1` q", p should success whenever q success.
213
-- But it's not the case here.
214

215
-- <literal>::= <variablename> | "~"<variablename>
216
literal :: Stream s m Char => ParsecT s u m Lit
217
literal = variablename <|> (char '~' >> liftM negate variablename)
1✔
218

219
-- | Parse a OPB format string containing pseudo boolean problem.
220
parseOPBString :: SourceName -> String -> Either ParseError Formula
221
parseOPBString = parse (formula <* eof)
1✔
222

223
-- | Parse a OPB format lazy bytestring containing pseudo boolean problem.
224
parseOPBByteString :: SourceName -> ByteString -> Either ParseError Formula
225
parseOPBByteString = parse (formula <* eof)
1✔
226

227
-- | Parse a OPB file containing pseudo boolean problem.
228
parseOPBFile :: FilePath -> IO (Either ParseError Formula)
229
parseOPBFile fname = do
1✔
230
  input <- BL.readFile fname
1✔
231
  return $ runP (formula <* eof) () fname (TL.decodeUtf8 input)
×
232

233

234
-- <softformula>::= <sequence_of_comments> <softheader> <sequence_of_comments_or_constraints>
235
softformula :: Stream s m Char => ParsecT s u m SoftFormula
236
softformula = do
1✔
237
  h <- optionMaybe hint
1✔
238
  sequence_of_comments
1✔
239
  top <- softheader
1✔
240
  cs <- wbo_sequence_of_comments_or_constraints
1✔
241
  return $
1✔
242
    SoftFormula
1✔
243
    { wboTopCost = top
1✔
244
    , wboConstraints = cs
1✔
245
    , wboNumVars = fromMaybe (wboComputeNumVars cs) (fmap fst h)
1✔
246
    , wboNumConstraints = fromMaybe (length cs) (fmap snd h)
1✔
247
    }
248

249
-- <softheader>::= "soft:" [<unsigned_integer>] ";"
250
softheader :: Stream s m Char => ParsecT s u m (Maybe Integer)
251
softheader = do
1✔
252
  _ <- string "soft:"
1✔
253
  zeroOrMoreSpace -- XXX
1✔
254
  top <- optionMaybe unsigned_integer
1✔
255
  zeroOrMoreSpace -- XXX
1✔
256
  semi
1✔
257
  return top
1✔
258

259
-- <sequence_of_comments_or_constraints>::= <comment_or_constraint> [<sequence_of_comments_or_constraints>]
260
wbo_sequence_of_comments_or_constraints :: Stream s m Char => ParsecT s u m [SoftConstraint]
261
wbo_sequence_of_comments_or_constraints = do
1✔
262
  xs <- many wbo_comment_or_constraint -- XXX: we relax the grammer to allow empty sequence
1✔
263
  return $ catMaybes xs
1✔
264

265
-- <comment_or_constraint>::= <comment>|<constraint>|<softconstraint>
266
wbo_comment_or_constraint :: Stream s m Char => ParsecT s u m (Maybe SoftConstraint)
267
wbo_comment_or_constraint = (comment >> return Nothing) <|> m
1✔
268
  where
269
    m = liftM Just $ (constraint >>= \c -> return (Nothing, c)) <|> softconstraint
1✔
270

271
-- <softconstraint>::= "[" <zeroOrMoreSpace> <unsigned_integer> <zeroOrMoreSpace> "]" <constraint>
272
softconstraint :: Stream s m Char => ParsecT s u m SoftConstraint
273
softconstraint = do
1✔
274
  _ <- char '['
1✔
275
  zeroOrMoreSpace
1✔
276
  cost <- unsigned_integer
1✔
277
  zeroOrMoreSpace
1✔
278
  _ <- char ']'
1✔
279
  zeroOrMoreSpace -- XXX
1✔
280
  c <- constraint
1✔
281
  return (Just cost, c)
1✔
282

283
-- | Parse a WBO format string containing weighted boolean optimization problem.
284
parseWBOString :: SourceName -> String -> Either ParseError SoftFormula
285
parseWBOString = parse (softformula <* eof)
1✔
286

287
-- | Parse a WBO format lazy bytestring containing pseudo boolean problem.
288
parseWBOByteString :: SourceName -> ByteString -> Either ParseError SoftFormula
289
parseWBOByteString = parse (softformula <* eof)
1✔
290

291
-- | Parse a WBO file containing weighted boolean optimization problem.
292
parseWBOFile :: FilePath -> IO (Either ParseError SoftFormula)
293
parseWBOFile fname = do
1✔
294
  input <- BL.readFile fname
1✔
295
  return $ runP (softformula <* eof) () fname (TL.decodeUtf8 input)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc