├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── elm-package.json ├── examples ├── Calc.elm ├── Python.elm └── Scheme.elm ├── src ├── Combine.elm └── Combine │ ├── Char.elm │ └── Num.elm └── tests ├── .gitignore ├── CurrentLocationTests.elm ├── Parsers.elm └── elm-package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff 2 | elm.js 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | 5 | cache: 6 | directories: 7 | - node_modules 8 | - sysconfcpus 9 | 10 | install: 11 | - | 12 | if [ ! -d sysconfcpus/bin ]; 13 | then 14 | git clone https://github.com/obmarg/libsysconfcpus.git; 15 | cd libsysconfcpus; 16 | ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; 17 | make && make install; 18 | cd ..; 19 | fi 20 | - npm install -g elm elm-test@0.18.7 21 | 22 | script: 23 | - $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-test 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 2.0.0 (2017-12-05) 4 | 5 | No API change, but a major change. The internal implementation how locations (line/columns) are working has changed. 6 | Lines were previously 1-based, and columns sometimes had negative values. This is changed into zero-based lines and columns can never have negative values anymore. 7 | 8 | If your application/library did not rely on parse locations, the update is seamless. 9 | 10 | ## Version 1.0.0 (2017-02-09) 11 | 12 | * Transfer from `Bogdanp/elm-combine`. 13 | 14 | --- 15 | 16 | > This repository is transferred from [Bogdanp/elm-combine](github.com/Bogdanp/elm-combine). The following changelog statements originate from that repository. 17 | 18 | 19 | --- 20 | ## elm-combine 3.1.0 (2016-11-14) 21 | 22 | ### Additions 23 | 24 | * Brought back `app` 25 | 26 | ## elm-combine 3.0.0 (2016-11-14) 27 | 28 | ### Breaking changes 29 | 30 | * The `Combine.Infix` module has been merged into `Combine` 31 | * The `Parser` type has changed from `Parser res` to `Parser state res` 32 | * The signature of `andThen` has changed to `(a -> Parser s b) -> Parser s a -> Parser s b` 33 | * The signature of `andMap` has changed to `Parser s a -> Parser s (a -> b) -> Parser s b` 34 | * The signature of `chainl` has changed to `Parser s (a -> a -> a) -> Parser s a -> Parser s a` 35 | * The signature of `chainr` has changed to `Parser s (a -> a -> a) -> Parser s a -> Parser s a` 36 | * The signature of `parse` has changed to `Parser () res -> String -> Result (ParseErr ()) (ParseOk () res)` 37 | * The signature of `fail` has changed to `String -> Parser s a` 38 | * `rec` has been renamed to `lazy` 39 | * `app` has been removed, use `primitive`, `parse` or `runParser` instead 40 | * `bimap` has been removed, use `map` and `mapError` instead 41 | 42 | ### Additions 43 | 44 | * Added `InputStream`, `ParseLocation`, `ParseContext`, `ParseResult`, `ParseErr` and `ParseOk` types 45 | * Added `runParser`, `withState`, `putState`, `modifyState` 46 | * Added `withLocation`, `withLine`, `withColumn`, `currentLocation`, `currentSourceLine`, `currentLine`, `currentColumn` 47 | * Added `lookAhead` and `whitespace` parsers 48 | 49 | ### Upgrading from 2.2.1 50 | 51 | * Replace all occurrences of `Parser *` with `Parser s *` 52 | * Replace all infix occurrences of andThen with `a |> andThen b` 53 | * Replace all imports of `Combine.Infix` with `Combine` 54 | * Replace all pattern matches on `Combine.parse` like so: 55 | 56 | ``` elm 57 | case Combine.parse someParser inputData of 58 | (Ok result, context) -> 59 | Just result 60 | 61 | (Err errors, context) -> 62 | Nothing 63 | ``` 64 | 65 | becomes 66 | 67 | ``` elm 68 | case Combine.parse someParser inputData of 69 | Ok (state, stream, result) -> 70 | Just result 71 | 72 | Err (state, stream, errors) -> 73 | Nothing 74 | ``` 75 | 76 | ## elm-combine 2.2.1 (2016-05-11) 77 | 78 | ### Additions 79 | 80 | * Added support for Elm 0.17 81 | 82 | ## elm-combine 2.2.0 (2016-03-06) 83 | 84 | ### Additions 85 | 86 | * Added `sequence` 87 | 88 | ## elm-combine 2.1.0 (2016-02-28) 89 | 90 | ### Additions 91 | 92 | * Added `sepEndBy` and `sepEndBy1` (contributed by @prt2121) 93 | 94 | ## elm-combine 2.0.2 95 | 96 | ### Fixes 97 | 98 | * Fixed issue [10](https://github.com/Bogdanp/elm-combine/issues/10). 99 | * Fixed an issue with `mapError` where the incorrect context was returned. 100 | 101 | ## elm-combine 2.0.1 102 | 103 | ### Fixes 104 | 105 | * Fixed issue [8](https://github.com/Bogdanp/elm-combine/issues/8). 106 | 107 | ## elm-combine 2.0.0 108 | 109 | ### Changes 110 | 111 | * Replaced custom `Result` ADT with `Result.Result` from `core` 112 | * Renamed `brackets` to `braces` 113 | * Renamed `squareBrackets` to `brackets` 114 | * Removed `Parser` and `RecursiveParser` constructors 115 | * Added `primitive` function for construcing custom `Parser` instances 116 | * Added basic test suite 117 | * Added Travis CI 118 | * Updated documentation 119 | 120 | ### Upgrading from 1.2.0 121 | 122 | * Replace all occurrences of `Done` with `Ok` 123 | * Replace all occurrences of `Fail` with `Err` 124 | * Replace all occurrences of `Result a` with `Result (List String) a` 125 | * Replace all occurrences of `ParseFn a` with `Context -> (Result a, Context)` 126 | * Replace all calls to the `Parser` constructor with `primitive` 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Bogdan Paul Popa 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note:** This package only works with Elm 0.18 and lower. See [`elm/parser`](https://github.com/elm/parser) for related functions in 0.19 and higher. 2 | 3 | # parser-combinators 4 | 5 | [![Build Status](https://travis-ci.org/elm-community/parser-combinators.svg)](https://travis-ci.org/elm-community/parser-combinators) 6 | 7 | A parser combinator library for [Elm](http://elm-lang.org) 0.18. See the 8 | documentation of the `Combine` module for more information and the 9 | `examples` directory for complex usage examples. 10 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "summary": "A parser combinator library", 4 | "repository": "https://github.com/elm-community/parser-combinators.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "examples", 8 | "src" 9 | ], 10 | "exposed-modules": [ 11 | "Combine", 12 | "Combine.Char", 13 | "Combine.Num" 14 | ], 15 | "dependencies": { 16 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 17 | "elm-lang/lazy": "2.0.0 <= v < 3.0.0" 18 | }, 19 | "elm-version": "0.18.0 <= v < 0.19.0" 20 | } 21 | -------------------------------------------------------------------------------- /examples/Calc.elm: -------------------------------------------------------------------------------- 1 | module Calc exposing (calc) 2 | 3 | {-| An example parser that computes arithmetic expressions. 4 | 5 | @docs calc 6 | -} 7 | 8 | import Combine exposing (..) 9 | import Combine.Num exposing (int) 10 | 11 | 12 | addop : Parser s (Int -> Int -> Int) 13 | addop = 14 | choice 15 | [ (+) <$ string "+" 16 | , (-) <$ string "-" 17 | ] 18 | 19 | 20 | mulop : Parser s (Int -> Int -> Int) 21 | mulop = 22 | choice 23 | [ (*) <$ string "*" 24 | , (//) <$ string "/" 25 | ] 26 | 27 | 28 | expr : Parser s Int 29 | expr = 30 | let 31 | go () = 32 | chainl addop term 33 | in 34 | lazy go 35 | 36 | 37 | term : Parser s Int 38 | term = 39 | let 40 | go () = 41 | chainl mulop factor 42 | in 43 | lazy go 44 | 45 | 46 | factor : Parser s Int 47 | factor = 48 | whitespace *> (parens expr <|> int) <* whitespace 49 | 50 | 51 | {-| Compute the result of an expression. 52 | -} 53 | calc : String -> Result String Int 54 | calc s = 55 | case parse (expr <* end) s of 56 | Ok ( _, _, n ) -> 57 | Ok n 58 | 59 | Err ( _, stream, ms ) -> 60 | Err ("parse error: " ++ toString ms ++ ", " ++ toString stream) 61 | -------------------------------------------------------------------------------- /examples/Python.elm: -------------------------------------------------------------------------------- 1 | module Python exposing (..) 2 | 3 | import Combine exposing (..) 4 | import Combine.Char exposing (..) 5 | import Combine.Num 6 | import String 7 | 8 | 9 | type Expression 10 | = EBool Bool 11 | | EInt Int 12 | | EFloat Float 13 | | EString String 14 | | EOr Expression Expression 15 | | EAnd Expression Expression 16 | | ENot Expression 17 | | ECmp String Expression Expression 18 | | EAdd Expression Expression 19 | | ESub Expression Expression 20 | | EMul Expression Expression 21 | | EDiv Expression Expression 22 | | EList (List Expression) 23 | | ETuple (List Expression) 24 | | EDict (List ( Expression, Expression )) 25 | | ESet (List Expression) 26 | | EIdentifier String 27 | | EAttribute Expression Expression 28 | | EApp Expression (List Expression) 29 | | EAssign Expression Expression 30 | 31 | 32 | type Statement 33 | = SExpr Expression 34 | | SPrint (List Expression) 35 | | SDel (List Expression) 36 | | SPass 37 | | SBreak 38 | | SContinue 39 | | SReturn (Maybe Expression) 40 | | SRaise (Maybe (List Expression)) 41 | | SImport (List ( Expression, Maybe Expression )) 42 | | SImportFrom Expression (List ( Expression, Maybe Expression )) 43 | | SGlobal (List Expression) 44 | | SAssert Expression (Maybe Expression) 45 | | SAssign Expression 46 | 47 | 48 | type CompoundStatement 49 | = CSimple (List Statement) 50 | | CWhile Expression (List CompoundStatement) 51 | | CFor Expression Expression (List CompoundStatement) 52 | | CWith Expression (Maybe Expression) (List CompoundStatement) 53 | | CFunc Expression (List Expression) (List CompoundStatement) 54 | 55 | 56 | type alias Indentation = 57 | List Int 58 | 59 | 60 | initIndentation : Indentation 61 | initIndentation = 62 | [ 0 ] 63 | 64 | 65 | dropWhile : (a -> Bool) -> List a -> List a 66 | dropWhile p xs = 67 | case xs of 68 | [] -> 69 | [] 70 | 71 | x :: ys -> 72 | if p x then 73 | dropWhile p ys 74 | else 75 | xs 76 | 77 | 78 | comment : Parser s String 79 | comment = 80 | regex "#[^\n]*" 81 | 82 | 83 | spaces : Parser s String 84 | spaces = 85 | regex " *" 86 | 87 | 88 | whitespace : Parser s String 89 | whitespace = 90 | comment <|> spaces "whitespace" 91 | 92 | 93 | token : Parser s res -> Parser s res 94 | token = 95 | between whitespace whitespace 96 | 97 | 98 | keyword : String -> Parser s String 99 | keyword s = 100 | string s <* spaces 101 | 102 | 103 | bool : Parser s Expression 104 | bool = 105 | EBool 106 | <$> choice 107 | [ False <$ string "False" 108 | , True <$ string "True" 109 | ] 110 | "boolean" 111 | 112 | 113 | int : Parser s Expression 114 | int = 115 | EInt <$> Combine.Num.int "integer" 116 | 117 | 118 | float : Parser s Expression 119 | float = 120 | EFloat <$> Combine.Num.float "float" 121 | 122 | 123 | str : Parser s Expression 124 | str = 125 | EString 126 | <$> choice 127 | [ string "'" *> regex "(\\\\'|[^'\n])*" <* string "'" 128 | , string "\"" *> regex "(\\\\\"|[^\"\n])*" <* string "\"" 129 | ] 130 | "string" 131 | 132 | 133 | identifier : Parser s Expression 134 | identifier = 135 | EIdentifier <$> regex "[_a-zA-Z][_a-zA-Z0-9]*" "identifier" 136 | 137 | 138 | attribute : Parser s Expression 139 | attribute = 140 | lazy <| 141 | \() -> 142 | EAttribute 143 | <$> identifier 144 | <* string "." 145 | <*> choice [ attribute, identifier ] 146 | "attribute" 147 | 148 | 149 | app : Parser s Expression 150 | app = 151 | lazy <| 152 | \() -> 153 | EApp 154 | <$> choice [ attribute, identifier ] 155 | <*> parens exprList 156 | "function call" 157 | 158 | 159 | commaSep : Parser s String 160 | commaSep = 161 | regex ", *" 162 | 163 | 164 | dictSep : Parser s String 165 | dictSep = 166 | regex ":[ \t\x0D\n]*" 167 | 168 | 169 | listSep : Parser s String 170 | listSep = 171 | regex ",[ \t\x0D\n]*" 172 | 173 | 174 | list : Parser s Expression 175 | list = 176 | lazy <| 177 | \() -> 178 | EList 179 | <$> brackets (sepBy listSep expr) 180 | "list" 181 | 182 | 183 | tuple : Parser s Expression 184 | tuple = 185 | lazy <| 186 | \() -> 187 | ETuple 188 | <$> parens (sepBy listSep expr) 189 | "tuple" 190 | 191 | 192 | dict : Parser s Expression 193 | dict = 194 | lazy <| 195 | \() -> 196 | EDict 197 | <$> brackets (sepBy listSep ((,) <$> expr <* dictSep <*> expr)) 198 | "dictionary" 199 | 200 | 201 | set : Parser s Expression 202 | set = 203 | lazy <| 204 | \() -> 205 | ESet 206 | <$> brackets (sepBy listSep expr) 207 | "set" 208 | 209 | 210 | atom : Parser s Expression 211 | atom = 212 | lazy <| 213 | \() -> 214 | choice [ bool, float, int, str, attribute, identifier, list, tuple, dict, set ] 215 | 216 | 217 | expr : Parser s Expression 218 | expr = 219 | lazy (\() -> chainl orop andExpr) 220 | 221 | 222 | andExpr : Parser s Expression 223 | andExpr = 224 | lazy (\() -> chainl andop notExpr) 225 | 226 | 227 | notExpr : Parser s Expression 228 | notExpr = 229 | lazy <| 230 | \() -> 231 | (token <| ENot <$> (string "not" *> notExpr)) <|> cmpExpr 232 | 233 | 234 | cmpExpr : Parser s Expression 235 | cmpExpr = 236 | lazy (\() -> chainl cmpop arithExpr) 237 | 238 | 239 | arithExpr : Parser s Expression 240 | arithExpr = 241 | lazy (\() -> chainl addop term) 242 | 243 | 244 | term : Parser s Expression 245 | term = 246 | lazy (\() -> chainl mulop factor) 247 | 248 | 249 | factor : Parser s Expression 250 | factor = 251 | lazy (\() -> token (parens expr <|> app <|> atom)) 252 | 253 | 254 | orop : Parser s (Expression -> Expression -> Expression) 255 | orop = 256 | EOr <$ string "or" 257 | 258 | 259 | andop : Parser s (Expression -> Expression -> Expression) 260 | andop = 261 | EAnd <$ string "and" 262 | 263 | 264 | cmpop : Parser s (Expression -> Expression -> Expression) 265 | cmpop = 266 | ECmp 267 | <$> choice 268 | [ string "<" 269 | , string ">" 270 | , string "==" 271 | , string "!=" 272 | , string ">=" 273 | , string "<=" 274 | , string "in" 275 | , (++) <$> keyword "not" <*> string "in" 276 | , string "is" 277 | , (++) <$> keyword "is" <*> string "not" 278 | ] 279 | 280 | 281 | addop : Parser s (Expression -> Expression -> Expression) 282 | addop = 283 | choice 284 | [ EAdd <$ string "+" 285 | , ESub <$ string "-" 286 | ] 287 | 288 | 289 | mulop : Parser s (Expression -> Expression -> Expression) 290 | mulop = 291 | choice 292 | [ EMul <$ string "*" 293 | , EDiv <$ string "/" 294 | ] 295 | 296 | 297 | exprList : Parser s (List Expression) 298 | exprList = 299 | sepBy commaSep expr 300 | 301 | 302 | exprStmt : Parser s Statement 303 | exprStmt = 304 | SExpr <$> expr "expression" 305 | 306 | 307 | printStmt : Parser s Statement 308 | printStmt = 309 | SPrint <$> (keyword "print" *> exprList) "print statement" 310 | 311 | 312 | delStmt : Parser s Statement 313 | delStmt = 314 | SDel <$> (keyword "del" *> exprList) "del statement" 315 | 316 | 317 | passStmt : Parser s Statement 318 | passStmt = 319 | SPass <$ keyword "pass" "pass statement" 320 | 321 | 322 | breakStmt : Parser s Statement 323 | breakStmt = 324 | SBreak <$ keyword "break" "break statement" 325 | 326 | 327 | continueStmt : Parser s Statement 328 | continueStmt = 329 | SContinue <$ keyword "continue" "continue statement" 330 | 331 | 332 | returnStmt : Parser s Statement 333 | returnStmt = 334 | SReturn <$> (keyword "return" *> maybe expr) "return statement" 335 | 336 | 337 | raiseStmt : Parser s Statement 338 | raiseStmt = 339 | SRaise <$> (keyword "raise" *> maybe exprList) "raise statement" 340 | 341 | 342 | importAs : Parser s (List ( Expression, Maybe Expression )) 343 | importAs = 344 | sepBy commaSep <| 345 | (,) 346 | <$> choice [ attribute, identifier ] 347 | <*> maybe (whitespace *> keyword "as" *> identifier) 348 | 349 | 350 | importStmt : Parser s Statement 351 | importStmt = 352 | SImport 353 | <$> (keyword "import" *> importAs) 354 | "import statement" 355 | 356 | 357 | importFromStmt : Parser s Statement 358 | importFromStmt = 359 | SImportFrom 360 | <$> (keyword "from" *> choice [ attribute, identifier ] <* spaces) 361 | <*> (keyword "import" *> importAs) 362 | "from statement" 363 | 364 | 365 | globalStmt : Parser s Statement 366 | globalStmt = 367 | SGlobal <$> (keyword "global" *> sepBy commaSep identifier) 368 | 369 | 370 | assertStmt : Parser s Statement 371 | assertStmt = 372 | SAssert 373 | <$> (keyword "assert" *> expr) 374 | <*> maybe (commaSep *> expr) 375 | 376 | 377 | assignop : Parser s (Expression -> Expression -> Expression) 378 | assignop = 379 | EAssign <$ token (string "=") 380 | 381 | 382 | assignStmt : Parser s Statement 383 | assignStmt = 384 | SAssign <$> chainr assignop expr 385 | 386 | 387 | indentation : Parser Indentation res -> Parser Indentation res 388 | indentation p = 389 | let 390 | skipIndent stack = 391 | let 392 | current = 393 | List.head stack 394 | |> Maybe.withDefault 0 395 | 396 | validate s = 397 | let 398 | indent = 399 | String.length s 400 | in 401 | if indent == current then 402 | succeed () 403 | else 404 | fail ("expected " ++ toString current ++ " spaces of indentation") 405 | in 406 | spaces >>= validate 407 | in 408 | withState skipIndent *> p 409 | 410 | 411 | indent : Parser Indentation () 412 | indent = 413 | lazy <| 414 | \() -> 415 | let 416 | push s = 417 | withState <| 418 | \stack -> 419 | let 420 | indent = 421 | String.length s 422 | in 423 | case stack of 424 | [] -> 425 | fail "negative indentation" 426 | 427 | current :: _ -> 428 | if indent > current then 429 | putState (indent :: stack) 430 | else 431 | fail "expected indentation" 432 | in 433 | lookAhead <| spaces >>= push 434 | 435 | 436 | dedent : Parser Indentation () 437 | dedent = 438 | lazy <| 439 | \() -> 440 | let 441 | pop s = 442 | withState <| 443 | \stack -> 444 | let 445 | rem = 446 | dropWhile ((/=) (String.length s)) stack 447 | in 448 | case rem of 449 | _ :: _ -> 450 | putState rem 451 | 452 | _ -> 453 | fail "unindent does not match any outer indentation level" 454 | in 455 | spaces >>= pop 456 | 457 | 458 | block : Parser Indentation (List CompoundStatement) 459 | block = 460 | lazy <| 461 | \() -> 462 | string ":" 463 | *> whitespace 464 | *> eol 465 | *> indent 466 | *> many1 stmt 467 | <* dedent 468 | 469 | 470 | blockStmt : Parser Indentation (List CompoundStatement -> CompoundStatement) -> Parser Indentation CompoundStatement 471 | blockStmt p = 472 | indentation p 473 | |> andThen 474 | (\f -> 475 | block 476 | |> andThen (\ss -> succeed (f ss)) 477 | ) 478 | 479 | 480 | simpleStmt : Parser Indentation CompoundStatement 481 | simpleStmt = 482 | lazy <| 483 | \() -> 484 | let 485 | stmt = 486 | choice 487 | [ assertStmt 488 | , globalStmt 489 | , importFromStmt 490 | , importStmt 491 | , raiseStmt 492 | , returnStmt 493 | , continueStmt 494 | , breakStmt 495 | , passStmt 496 | , delStmt 497 | , printStmt 498 | , assignStmt 499 | , exprStmt 500 | ] 501 | in 502 | indentation (CSimple <$> sepBy (string ";" <* whitespace) stmt <* (() <$ eol <|> end)) 503 | 504 | 505 | whileStmt : Parser s (List CompoundStatement -> CompoundStatement) 506 | whileStmt = 507 | CWhile <$> (keyword "while" *> expr) 508 | 509 | 510 | forStmt : Parser s (List CompoundStatement -> CompoundStatement) 511 | forStmt = 512 | CFor 513 | <$> (keyword "for" *> identifier) 514 | <*> (spaces *> keyword "in" *> expr) 515 | 516 | 517 | withStmt : Parser s (List CompoundStatement -> CompoundStatement) 518 | withStmt = 519 | CWith 520 | <$> (keyword "with" *> expr) 521 | <*> maybe (keyword "as" *> identifier) 522 | 523 | 524 | funcStmt : Parser s (List CompoundStatement -> CompoundStatement) 525 | funcStmt = 526 | CFunc 527 | <$> (keyword "def" *> identifier) 528 | <*> parens (sepBy commaSep identifier) 529 | 530 | 531 | compoundStmt : Parser Indentation CompoundStatement 532 | compoundStmt = 533 | lazy <| 534 | \() -> 535 | let 536 | parsers = 537 | List.map blockStmt 538 | [ whileStmt 539 | , forStmt 540 | , withStmt 541 | , funcStmt 542 | ] 543 | in 544 | choice parsers 545 | 546 | 547 | stmt : Parser Indentation CompoundStatement 548 | stmt = 549 | lazy <| 550 | \() -> 551 | compoundStmt <|> simpleStmt 552 | 553 | 554 | program : Parser Indentation (List CompoundStatement) 555 | program = 556 | manyTill stmt end 557 | 558 | 559 | formatError : List String -> InputStream -> String 560 | formatError ms stream = 561 | let 562 | location = 563 | currentLocation stream 564 | 565 | separator = 566 | "| " 567 | 568 | expectationSeparator = 569 | "\n * " 570 | 571 | lineNumberOffset = 572 | floor (logBase 10 (toFloat location.line)) + 1 573 | 574 | separatorOffset = 575 | String.length separator 576 | 577 | padding = 578 | location.column + separatorOffset + 2 579 | in 580 | "Parse error around line:\n\n" 581 | ++ toString location.line 582 | ++ separator 583 | ++ location.source 584 | ++ "\n" 585 | ++ String.padLeft padding ' ' "^" 586 | ++ "\nI expected one of the following:\n" 587 | ++ expectationSeparator 588 | ++ String.join expectationSeparator ms 589 | 590 | 591 | parse : String -> Result String (List CompoundStatement) 592 | parse s = 593 | case Combine.runParser program initIndentation s of 594 | Ok ( _, _, es ) -> 595 | Ok es 596 | 597 | Err ( _, stream, ms ) -> 598 | Err <| formatError ms stream 599 | 600 | 601 | test : Result String (List CompoundStatement) 602 | test = 603 | parse """import os 604 | 605 | a = b = 1 606 | 607 | def rel(p): 608 | return os.path.join(os.path.dirname(__file__), p) 609 | 610 | def f(a, b): 611 | return a + b 612 | 613 | with open(rel('Python.elm')) as f: 614 | for line in f: 615 | print f 616 | """ 617 | -------------------------------------------------------------------------------- /examples/Scheme.elm: -------------------------------------------------------------------------------- 1 | module Scheme exposing (..) 2 | 3 | import Combine exposing (..) 4 | import Combine.Char exposing (anyChar) 5 | import Combine.Num 6 | import String 7 | 8 | 9 | type E 10 | = EBool Bool 11 | | EInt Int 12 | | EFloat Float 13 | | EChar Char 14 | | EString String 15 | | EIdentifier String 16 | | EList (List E) 17 | | EVector (List E) 18 | | EQuote E 19 | | EQuasiquote E 20 | | EUnquote E 21 | | EUnquoteSplice E 22 | | EComment String 23 | 24 | 25 | comment : Parser s E 26 | comment = 27 | EComment 28 | <$> regex ";[^\n]+" 29 | "comment" 30 | 31 | 32 | bool : Parser s E 33 | bool = 34 | let 35 | boolLiteral = 36 | choice 37 | [ True <$ string "#t" 38 | , False <$ string "#f" 39 | ] 40 | in 41 | EBool 42 | <$> boolLiteral 43 | "boolean literal" 44 | 45 | 46 | int : Parser s E 47 | int = 48 | EInt 49 | <$> Combine.Num.int 50 | "integer literal" 51 | 52 | 53 | float : Parser s E 54 | float = 55 | EFloat 56 | <$> Combine.Num.float 57 | "float literal" 58 | 59 | 60 | char : Parser s E 61 | char = 62 | let 63 | charLiteral = 64 | string "#\\" 65 | *> choice 66 | [ ' ' <$ string "space" 67 | , '\n' <$ string "newline" 68 | , anyChar 69 | ] 70 | in 71 | EChar 72 | <$> charLiteral 73 | "character literal" 74 | 75 | 76 | str : Parser s E 77 | str = 78 | EString 79 | <$> regex "\"(\\\"|[^\"])+\"" 80 | "string literal" 81 | 82 | 83 | identifier : Parser s E 84 | identifier = 85 | let 86 | letter = 87 | "a-zA-Z" 88 | 89 | specialInitial = 90 | "!$%&*/:<=>?^_~+\\-" 91 | 92 | initial = 93 | letter ++ specialInitial 94 | 95 | initialRe = 96 | "[" ++ initial ++ "]" 97 | 98 | digit = 99 | "0-9" 100 | 101 | specialSubsequent = 102 | ".@+\\-" 103 | 104 | subsequent = 105 | initial ++ digit ++ specialSubsequent 106 | 107 | subsequentRe = 108 | "[" ++ subsequent ++ "]*" 109 | 110 | identifierRe = 111 | initialRe ++ subsequentRe 112 | in 113 | EIdentifier <$> regex identifierRe "identifier" 114 | 115 | 116 | list : Parser s E 117 | list = 118 | EList 119 | <$> parens (many expr) 120 | "list" 121 | 122 | 123 | vector : Parser s E 124 | vector = 125 | EVector 126 | <$> (string "#(" *> many expr <* string ")") 127 | "vector" 128 | 129 | 130 | quote : Parser s E 131 | quote = 132 | EQuote 133 | <$> (string "'" *> expr) 134 | "quoted expression" 135 | 136 | 137 | quasiquote : Parser s E 138 | quasiquote = 139 | EQuasiquote 140 | <$> (string "`" *> expr) 141 | "quasiquoted expression" 142 | 143 | 144 | unquote : Parser s E 145 | unquote = 146 | EUnquote 147 | <$> (string "," *> expr) 148 | "unquoted expression" 149 | 150 | 151 | unquoteSplice : Parser s E 152 | unquoteSplice = 153 | EUnquoteSplice 154 | <$> (string ",@" *> expr) 155 | "spliced expression" 156 | 157 | 158 | expr : Parser s E 159 | expr = 160 | lazy <| 161 | \() -> 162 | let 163 | parsers = 164 | [ bool 165 | , float 166 | , int 167 | , char 168 | , str 169 | , identifier 170 | , list 171 | , vector 172 | , quote 173 | , quasiquote 174 | , unquote 175 | , unquoteSplice 176 | , comment 177 | ] 178 | in 179 | whitespace *> choice parsers <* whitespace 180 | 181 | 182 | program : Parser s (List E) 183 | program = 184 | manyTill expr end 185 | 186 | 187 | formatError : List String -> InputStream -> String 188 | formatError ms stream = 189 | let 190 | location = 191 | currentLocation stream 192 | 193 | separator = 194 | "|> " 195 | 196 | expectationSeparator = 197 | "\n * " 198 | 199 | lineNumberOffset = 200 | floor (logBase 10 (toFloat location.line)) + 1 201 | 202 | separatorOffset = 203 | String.length separator 204 | 205 | padding = 206 | location.column + separatorOffset + 2 207 | in 208 | "Parse error around line:\n\n" 209 | ++ toString location.line 210 | ++ separator 211 | ++ location.source 212 | ++ "\n" 213 | ++ String.padLeft padding ' ' "^" 214 | ++ "\nI expected one of the following:\n" 215 | ++ expectationSeparator 216 | ++ String.join expectationSeparator ms 217 | 218 | 219 | parse : String -> Result String (List E) 220 | parse s = 221 | case Combine.parse program s of 222 | Ok ( _, _, e ) -> 223 | Ok e 224 | 225 | Err ( _, stream, ms ) -> 226 | Err <| formatError ms stream 227 | -------------------------------------------------------------------------------- /src/Combine.elm: -------------------------------------------------------------------------------- 1 | module Combine 2 | exposing 3 | ( Parser 4 | , InputStream 5 | , ParseLocation 6 | , ParseContext 7 | , ParseResult 8 | , ParseError 9 | , ParseOk 10 | , primitive 11 | , app 12 | , lazy 13 | , parse 14 | , runParser 15 | , withState 16 | , putState 17 | , modifyState 18 | , withLocation 19 | , withLine 20 | , withColumn 21 | , currentLocation 22 | , currentSourceLine 23 | , currentLine 24 | , currentColumn 25 | , map 26 | , mapError 27 | , andThen 28 | , andMap 29 | , sequence 30 | , fail 31 | , succeed 32 | , string 33 | , regex 34 | , end 35 | , whitespace 36 | , whitespace1 37 | , lookAhead 38 | , while 39 | , or 40 | , choice 41 | , optional 42 | , maybe 43 | , many 44 | , many1 45 | , manyTill 46 | , sepBy 47 | , sepBy1 48 | , sepEndBy 49 | , sepEndBy1 50 | , skip 51 | , skipMany 52 | , skipMany1 53 | , chainl 54 | , chainr 55 | , count 56 | , between 57 | , parens 58 | , braces 59 | , brackets 60 | , () 61 | , (>>=) 62 | , (<$>) 63 | , (<$) 64 | , ($>) 65 | , (<*>) 66 | , (<*) 67 | , (*>) 68 | , (<|>) 69 | ) 70 | 71 | {-| This library provides facilities for parsing structured text data 72 | into concrete Elm values. 73 | 74 | 75 | ## API Reference 76 | 77 | - [Core Types](#core-types) 78 | - [Running Parsers](#running-parsers) 79 | - [Constructing Parsers](#constructing-parsers) 80 | - [Parsers](#parsers) 81 | - [Combinators](#combinators) 82 | - [Transforming Parsers](#transforming-parsers) 83 | - [Chaining Parsers](#chaining-parsers) 84 | - [Parser Combinators](#parser-combinators) 85 | - [State Combinators](#state-combinators) 86 | 87 | 88 | ## Core Types 89 | 90 | @docs Parser, InputStream, ParseLocation, ParseContext, ParseResult, ParseError, ParseOk 91 | 92 | 93 | ## Running Parsers 94 | 95 | @docs parse, runParser 96 | 97 | 98 | ## Constructing Parsers 99 | 100 | @docs primitive, app, lazy 101 | 102 | 103 | ## Parsers 104 | 105 | @docs fail, succeed, string, regex, end, whitespace, whitespace1 106 | 107 | 108 | ## Combinators 109 | 110 | 111 | ### Transforming Parsers 112 | 113 | @docs map, (<$>), (<$), ($>), mapError, () 114 | 115 | 116 | ### Chaining Parsers 117 | 118 | @docs andThen, (>>=), andMap, (<*>), (<*), (*>), sequence 119 | 120 | 121 | ### Parser Combinators 122 | 123 | @docs lookAhead, while, or, (<|>), choice, optional, maybe, many, many1, manyTill, sepBy, sepBy1, sepEndBy, sepEndBy1, skip, skipMany, skipMany1, chainl, chainr, count, between, parens, braces, brackets 124 | 125 | 126 | ### State Combinators 127 | 128 | @docs withState, putState, modifyState, withLocation, withLine, withColumn, currentLocation, currentSourceLine, currentLine, currentColumn 129 | 130 | -} 131 | 132 | import Lazy as L 133 | import Regex exposing (Regex) 134 | import String 135 | 136 | 137 | {-| The input stream over which `Parser`s operate. 138 | 139 | - `data` is the initial input provided by the user 140 | - `input` is the remainder after running a parse 141 | - `position` is the starting position of `input` in `data` after a parse 142 | 143 | -} 144 | type alias InputStream = 145 | { data : String 146 | , input : String 147 | , position : Int 148 | } 149 | 150 | 151 | initStream : String -> InputStream 152 | initStream s = 153 | InputStream s s 0 154 | 155 | 156 | {-| A record representing the current parse location in an InputStream. 157 | 158 | - `source` the current line of source code 159 | - `line` the current line number (starting at 1) 160 | - `column` the current column (starting at 1) 161 | 162 | -} 163 | type alias ParseLocation = 164 | { source : String 165 | , line : Int 166 | , column : Int 167 | } 168 | 169 | 170 | {-| A tuple representing the current parser state, the remaining input 171 | stream and the parse result. Don't worry about this type unless 172 | you're writing your own `primitive` parsers. 173 | -} 174 | type alias ParseContext state res = 175 | ( state, InputStream, ParseResult res ) 176 | 177 | 178 | {-| Running a `Parser` results in one of two states: 179 | 180 | - `Ok res` when the parser has successfully parsed the input 181 | - `Err messages` when the parser has failed with a list of error messages. 182 | 183 | -} 184 | type alias ParseResult res = 185 | Result (List String) res 186 | 187 | 188 | {-| A tuple representing a failed parse. It contains the state after 189 | running the parser, the remaining input stream and a list of 190 | error messages. 191 | -} 192 | type alias ParseError state = 193 | ( state, InputStream, List String ) 194 | 195 | 196 | {-| A tuple representing a successful parse. It contains the state 197 | after running the parser, the remaining input stream and the 198 | result. 199 | -} 200 | type alias ParseOk state res = 201 | ( state, InputStream, res ) 202 | 203 | 204 | type alias ParseFn state res = 205 | state -> InputStream -> ParseContext state res 206 | 207 | 208 | {-| The Parser type. 209 | 210 | At their core, `Parser`s wrap functions from some `state` and an 211 | `InputStream` to a tuple representing the new `state`, the 212 | remaining `InputStream` and a `ParseResult res`. 213 | 214 | -} 215 | type Parser state res 216 | = Parser (ParseFn state res) 217 | | RecursiveParser (L.Lazy (ParseFn state res)) 218 | 219 | 220 | {-| Construct a new primitive Parser. 221 | 222 | If you find yourself reaching for this function often consider opening 223 | a [Github issue][issues] with the library to have your custom Parsers 224 | included in the standard distribution. 225 | 226 | [issues]: https://github.com/elm-community/parser-combinators/issues 227 | 228 | -} 229 | primitive : (state -> InputStream -> ParseContext state res) -> Parser state res 230 | primitive = 231 | Parser 232 | 233 | 234 | {-| Unwrap a parser so it can be applied to a state and an input 235 | stream. This function is useful if you want to construct your own 236 | parsers via `primitive`. If you're using this outside of the context 237 | of `primitive` then you might be doing something wrong so try asking 238 | for help on the mailing list. 239 | 240 | Here's how you would implement a greedy version of `manyTill` using 241 | `primitive` and `app`: 242 | 243 | manyTill : Parser s a -> Parser s x -> Parser s (List a) 244 | manyTill p end = 245 | let 246 | accumulate acc state stream = 247 | case app end state stream of 248 | ( rstate, rstream, Ok _ ) -> 249 | ( rstate, rstream, Ok (List.reverse acc) ) 250 | 251 | _ -> 252 | case app p state stream of 253 | ( rstate, rstream, Ok res ) -> 254 | accumulate (res :: acc) rstate rstream 255 | 256 | ( estate, estream, Err ms ) -> 257 | ( estate, estream, Err ms ) 258 | in 259 | primitive <| accumulate [] 260 | 261 | -} 262 | app : Parser state res -> state -> InputStream -> ParseContext state res 263 | app p = 264 | case p of 265 | Parser inner -> 266 | inner 267 | 268 | RecursiveParser t -> 269 | L.force t 270 | 271 | 272 | {-| Parse a string. See `runParser` if your parser needs to manage 273 | some internal state. 274 | 275 | import Combine.Num exposing (int) 276 | import String 277 | 278 | parseAnInteger : String -> Result String Int 279 | parseAnInteger input = 280 | case parse int input of 281 | Ok (_, stream, result) -> 282 | Ok result 283 | 284 | Err (_, stream, errors) -> 285 | Err (String.join " or " errors) 286 | 287 | parseAnInteger "123" 288 | -- Ok 123 289 | 290 | parseAnInteger "abc" 291 | -- Err "expected an integer" 292 | 293 | -} 294 | parse : Parser () res -> String -> Result (ParseError ()) (ParseOk () res) 295 | parse p = 296 | runParser p () 297 | 298 | 299 | {-| Parse a string while maintaining some internal state. 300 | 301 | import Combine.Num exposing (int) 302 | import String 303 | 304 | type alias Output = 305 | { count : Int 306 | , integers : List Int 307 | } 308 | 309 | statefulInt : Parse Int Int 310 | statefulInt = 311 | -- Parse an int, then increment the state and return the parsed 312 | -- int. It's important that we try to parse the int _first_ 313 | -- since modifying the state will always succeed. 314 | int <* modifyState ((+) 1) 315 | 316 | ints : Parse Int (List Int) 317 | ints = 318 | sepBy (string " ") statefulInt 319 | 320 | parseIntegers : String -> Result String Output 321 | parseIntegers input = 322 | case runParser ints 0 input of 323 | Ok (state, stream, ints) -> 324 | Ok { count = state, integers = ints } 325 | 326 | Err (state, stream, errors) -> 327 | Err (String.join " or " errors) 328 | 329 | parseIntegers "" 330 | -- Ok { count = 0, integers = [] } 331 | 332 | parseIntegers "1 2 3 45" 333 | -- Ok { count = 4, integers = [1, 2, 3, 45] } 334 | 335 | parseIntegers "1 a 2" 336 | -- Ok { count = 1, integers = [1] } 337 | 338 | -} 339 | runParser : Parser state res -> state -> String -> Result (ParseError state) (ParseOk state res) 340 | runParser p st s = 341 | case app p st (initStream s) of 342 | ( state, stream, Ok res ) -> 343 | Ok ( state, stream, res ) 344 | 345 | ( state, stream, Err ms ) -> 346 | Err ( state, stream, ms ) 347 | 348 | 349 | {-| Defer running a parser until it's actually required. Use this 350 | function to avoid "bad-recursion" errors. 351 | 352 | type Expression 353 | = ETerm String 354 | | EList (List E) 355 | 356 | name : Parser s String 357 | name = whitespace *> regex "[a-zA-Z]+" <* whitespace 358 | 359 | term : Parser s Expression 360 | term = ETerm <$> name 361 | 362 | list : Parser s Expression 363 | list = 364 | let 365 | -- helper is itself a function so we avoid the case where the 366 | -- value `list` tries to apply itself in its definition. 367 | helper () = 368 | EList <$> between (string "(") (string ")") (many (term <|> list)) 369 | in 370 | -- lazy defers calling helper until it's actually needed. 371 | lazy helper 372 | 373 | parse list "" 374 | -- Err ["expected \"(\""] 375 | 376 | parse list "()" 377 | -- Ok (EList []) 378 | 379 | parse list "(a (b c))" 380 | -- Ok (EList [ETerm "a", EList [ETerm "b", ETerm "c"]]) 381 | 382 | -} 383 | lazy : (() -> Parser s a) -> Parser s a 384 | lazy t = 385 | RecursiveParser (L.lazy (\() -> app (t ()))) 386 | 387 | 388 | {-| Transform both the result and error message of a parser. 389 | -} 390 | bimap : 391 | (a -> b) 392 | -> (List String -> List String) 393 | -> Parser s a 394 | -> Parser s b 395 | bimap fok ferr p = 396 | Parser <| 397 | \state stream -> 398 | case app p state stream of 399 | ( rstate, rstream, Ok res ) -> 400 | ( rstate, rstream, Ok (fok res) ) 401 | 402 | ( estate, estream, Err ms ) -> 403 | ( estate, estream, Err (ferr ms) ) 404 | 405 | 406 | 407 | -- State management 408 | -- ---------------- 409 | 410 | 411 | {-| Get the parser's state and pipe it into a parser. 412 | -} 413 | withState : (s -> Parser s a) -> Parser s a 414 | withState f = 415 | Parser <| 416 | \state stream -> 417 | app (f state) state stream 418 | 419 | 420 | {-| Replace the parser's state. 421 | -} 422 | putState : s -> Parser s () 423 | putState state = 424 | Parser <| 425 | \_ stream -> 426 | app (succeed ()) state stream 427 | 428 | 429 | {-| Modify the parser's state. 430 | -} 431 | modifyState : (s -> s) -> Parser s () 432 | modifyState f = 433 | Parser <| 434 | \state stream -> 435 | app (succeed ()) (f state) stream 436 | 437 | 438 | {-| Get the current position in the input stream and pipe it into a parser. 439 | -} 440 | withLocation : (ParseLocation -> Parser s a) -> Parser s a 441 | withLocation f = 442 | Parser <| 443 | \state stream -> 444 | app (f <| currentLocation stream) state stream 445 | 446 | 447 | {-| Get the current line and pipe it into a parser. 448 | -} 449 | withLine : (Int -> Parser s a) -> Parser s a 450 | withLine f = 451 | Parser <| 452 | \state stream -> 453 | app (f <| currentLine stream) state stream 454 | 455 | 456 | {-| Get the current column and pipe it into a parser. 457 | -} 458 | withColumn : (Int -> Parser s a) -> Parser s a 459 | withColumn f = 460 | Parser <| 461 | \state stream -> 462 | app (f <| currentColumn stream) state stream 463 | 464 | 465 | {-| Get the current `(line, column)` in the input stream. 466 | -} 467 | currentLocation : InputStream -> ParseLocation 468 | currentLocation stream = 469 | let 470 | find position currentLine lines = 471 | case lines of 472 | [] -> 473 | ParseLocation "" currentLine position 474 | 475 | line :: rest -> 476 | let 477 | length = 478 | String.length line 479 | 480 | lengthPlusNL = 481 | length + 1 482 | in 483 | if position == length then 484 | ParseLocation line currentLine position 485 | else if position > length then 486 | find (position - lengthPlusNL) (currentLine + 1) rest 487 | else 488 | ParseLocation line currentLine position 489 | in 490 | find stream.position 0 (String.split "\n" stream.data) 491 | 492 | 493 | {-| Get the current source line in the input stream. 494 | -} 495 | currentSourceLine : InputStream -> String 496 | currentSourceLine = 497 | currentLocation >> .source 498 | 499 | 500 | {-| Get the current line in the input stream. 501 | -} 502 | currentLine : InputStream -> Int 503 | currentLine = 504 | currentLocation >> .line 505 | 506 | 507 | {-| Get the current column in the input stream. 508 | -} 509 | currentColumn : InputStream -> Int 510 | currentColumn = 511 | currentLocation >> .column 512 | 513 | 514 | 515 | -- Transformers 516 | -- ------------ 517 | 518 | 519 | {-| Transform the result of a parser. 520 | 521 | let 522 | parser = 523 | string "a" 524 | |> map String.toUpper 525 | in 526 | parse parser "a" 527 | -- Ok "A" 528 | 529 | -} 530 | map : (a -> b) -> Parser s a -> Parser s b 531 | map f p = 532 | bimap f identity p 533 | 534 | 535 | {-| Transform the error of a parser. 536 | 537 | let 538 | parser = 539 | string "a" 540 | |> mapError (always ["bad input"]) 541 | in 542 | parse parser b 543 | -- Err ["bad input"] 544 | 545 | -} 546 | mapError : (List String -> List String) -> Parser s a -> Parser s a 547 | mapError = 548 | bimap identity 549 | 550 | 551 | {-| Sequence two parsers, passing the result of the first parser to a 552 | function that returns the second parser. The value of the second 553 | parser is returned on success. 554 | 555 | import Combine.Num exposing (int) 556 | 557 | choosy : Parser s String 558 | choosy = 559 | let 560 | createParser n = 561 | if n % 2 == 0 then 562 | string " is even" 563 | else 564 | string " is odd" 565 | in 566 | int 567 | |> andThen createParser 568 | 569 | parse choosy "1 is odd" 570 | -- Ok " is odd" 571 | 572 | parse choosy "2 is even" 573 | -- Ok " is even" 574 | 575 | parse choosy "1 is even" 576 | -- Err ["expected \" is odd\""] 577 | 578 | -} 579 | andThen : (a -> Parser s b) -> Parser s a -> Parser s b 580 | andThen f p = 581 | Parser <| 582 | \state stream -> 583 | case app p state stream of 584 | ( rstate, rstream, Ok res ) -> 585 | app (f res) rstate rstream 586 | 587 | ( estate, estream, Err ms ) -> 588 | ( estate, estream, Err ms ) 589 | 590 | 591 | {-| Sequence two parsers. 592 | 593 | import Combine.Num exposing (int) 594 | 595 | plus : Parser s String 596 | plus = string "+" 597 | 598 | sum : Parser s Int 599 | sum = 600 | int 601 | |> map (+) 602 | |> andMap (plus *> int) 603 | 604 | parse sum "1+2" 605 | -- Ok 3 606 | 607 | -} 608 | andMap : Parser s a -> Parser s (a -> b) -> Parser s b 609 | andMap rp lp = 610 | lp >>= flip map rp 611 | 612 | 613 | {-| Run a list of parsers in sequence, accumulating the results. The 614 | main use case for this parser is when you want to combine a list of 615 | parsers into a single, top-level, parser. For most use cases, you'll 616 | want to use one of the other combinators instead. 617 | 618 | parse (sequence [string "a", string "b"]) "ab" 619 | -- Ok ["a", "b"] 620 | 621 | parse (sequence [string "a", string "b"]) "ac" 622 | -- Err ["expected \"b\""] 623 | 624 | -} 625 | sequence : List (Parser s a) -> Parser s (List a) 626 | sequence parsers = 627 | let 628 | accumulate acc ps state stream = 629 | case ps of 630 | [] -> 631 | ( state, stream, Ok (List.reverse acc) ) 632 | 633 | x :: xs -> 634 | case app x state stream of 635 | ( rstate, rstream, Ok res ) -> 636 | accumulate (res :: acc) xs rstate rstream 637 | 638 | ( estate, estream, Err ms ) -> 639 | ( estate, estream, Err ms ) 640 | in 641 | Parser <| 642 | \state stream -> 643 | accumulate [] parsers state stream 644 | 645 | 646 | 647 | -- Combinators 648 | -- ----------- 649 | 650 | 651 | {-| Fail without consuming any input. 652 | 653 | parse (fail "some error") "hello" 654 | -- Err ["some error"] 655 | 656 | -} 657 | fail : String -> Parser s a 658 | fail m = 659 | Parser <| 660 | \state stream -> 661 | ( state, stream, Err [ m ] ) 662 | 663 | 664 | emptyErr : Parser s a 665 | emptyErr = 666 | Parser <| 667 | \state stream -> 668 | ( state, stream, Err [] ) 669 | 670 | 671 | {-| Return a value without consuming any input. 672 | 673 | parse (succeed 1) "a" 674 | -- Ok 1 675 | 676 | -} 677 | succeed : a -> Parser s a 678 | succeed res = 679 | Parser <| 680 | \state stream -> 681 | ( state, stream, Ok res ) 682 | 683 | 684 | {-| Parse an exact string match. 685 | 686 | parse (string "hello") "hello world" 687 | -- Ok "hello" 688 | 689 | parse (string "hello") "goodbye" 690 | -- Err ["expected \"hello\""] 691 | 692 | -} 693 | string : String -> Parser s String 694 | string s = 695 | Parser <| 696 | \state stream -> 697 | if String.startsWith s stream.input then 698 | let 699 | len = 700 | String.length s 701 | 702 | rem = 703 | String.dropLeft len stream.input 704 | 705 | pos = 706 | stream.position + len 707 | in 708 | ( state, { stream | input = rem, position = pos }, Ok s ) 709 | else 710 | ( state, stream, Err [ "expected " ++ toString s ] ) 711 | 712 | 713 | {-| Parse a Regex match. 714 | 715 | Regular expressions must match from the beginning of the input and their 716 | subgroups are ignored. A `^` is added implicitly to the beginning of 717 | every pattern unless one already exists. 718 | 719 | parse (regex "a+") "aaaaab" 720 | -- Ok "aaaaa" 721 | 722 | -} 723 | regex : String -> Parser s String 724 | regex pat = 725 | let 726 | pattern = 727 | if String.startsWith "^" pat then 728 | pat 729 | else 730 | "^" ++ pat 731 | in 732 | Parser <| 733 | \state stream -> 734 | case Regex.find (Regex.AtMost 1) (Regex.regex pattern) stream.input of 735 | [ match ] -> 736 | let 737 | len = 738 | String.length match.match 739 | 740 | rem = 741 | String.dropLeft len stream.input 742 | 743 | pos = 744 | stream.position + len 745 | in 746 | ( state, { stream | input = rem, position = pos }, Ok match.match ) 747 | 748 | _ -> 749 | ( state, stream, Err [ "expected input matching Regexp /" ++ pattern ++ "/" ] ) 750 | 751 | 752 | {-| Consume input while the predicate matches. 753 | 754 | parse (while ((/=) ' ')) "test 123" 755 | -- Ok "test" 756 | 757 | -} 758 | while : (Char -> Bool) -> Parser s String 759 | while pred = 760 | let 761 | accumulate acc state stream = 762 | case String.uncons stream.input of 763 | Just ( h, rest ) -> 764 | if pred h then 765 | let 766 | c = 767 | String.cons h "" 768 | 769 | pos = 770 | stream.position + 1 771 | in 772 | accumulate (acc ++ c) state { stream | input = rest, position = pos } 773 | else 774 | ( state, stream, acc ) 775 | 776 | Nothing -> 777 | ( state, stream, acc ) 778 | in 779 | Parser <| 780 | \state stream -> 781 | let 782 | ( rstate, rstream, res ) = 783 | accumulate "" state stream 784 | in 785 | ( rstate, rstream, Ok res ) 786 | 787 | 788 | {-| Fail when the input is not empty. 789 | 790 | parse end "" 791 | -- Ok () 792 | 793 | parse end "a" 794 | -- Err ["expected end of input"] 795 | 796 | -} 797 | end : Parser s () 798 | end = 799 | Parser <| 800 | \state stream -> 801 | if stream.input == "" then 802 | ( state, stream, Ok () ) 803 | else 804 | ( state, stream, Err [ "expected end of input" ] ) 805 | 806 | 807 | {-| Apply a parser without consuming any input on success. 808 | -} 809 | lookAhead : Parser s a -> Parser s a 810 | lookAhead p = 811 | Parser <| 812 | \state stream -> 813 | case app p state stream of 814 | ( rstate, _, Ok res ) -> 815 | ( rstate, stream, Ok res ) 816 | 817 | err -> 818 | err 819 | 820 | 821 | {-| Choose between two parsers. 822 | 823 | parse (or (string "a") (string "b")) "a" 824 | -- Ok "a" 825 | 826 | parse (or (string "a") (string "b")) "b" 827 | -- Ok "b" 828 | 829 | parse (or (string "a") (string "b")) "c" 830 | -- Err ["expected \"a\"", "expected \"b\""] 831 | 832 | -} 833 | or : Parser s a -> Parser s a -> Parser s a 834 | or lp rp = 835 | Parser <| 836 | \state stream -> 837 | case app lp state stream of 838 | ( _, _, Ok _ ) as res -> 839 | res 840 | 841 | ( _, _, Err lms ) -> 842 | case app rp state stream of 843 | ( _, _, Ok _ ) as res -> 844 | res 845 | 846 | ( _, _, Err rms ) -> 847 | ( state, stream, Err (lms ++ rms) ) 848 | 849 | 850 | {-| Choose between a list of parsers. 851 | 852 | parse (choice [string "a", string "b"]) "a" 853 | -- Ok "a" 854 | 855 | parse (choice [string "a", string "b"]) "b" 856 | -- Ok "b" 857 | 858 | -} 859 | choice : List (Parser s a) -> Parser s a 860 | choice xs = 861 | List.foldr or emptyErr xs 862 | 863 | 864 | {-| Return a default value when the given parser fails. 865 | 866 | letterA : Parser s String 867 | letterA = optional "a" (string "a") 868 | 869 | parse letterA "a" 870 | -- Ok "a" 871 | 872 | parse letterA "b" 873 | -- Ok "a" 874 | 875 | -} 876 | optional : a -> Parser s a -> Parser s a 877 | optional res p = 878 | p <|> succeed res 879 | 880 | 881 | {-| Wrap the return value into a `Maybe`. Returns `Nothing` on failure. 882 | 883 | parse (maybe (string "a")) "a" 884 | -- Ok (Just "a") 885 | 886 | parse (maybe (string "a")) "b" 887 | -- Ok Nothing 888 | 889 | -} 890 | maybe : Parser s a -> Parser s (Maybe a) 891 | maybe p = 892 | Parser <| 893 | \state stream -> 894 | case app p state stream of 895 | ( rstate, rstream, Ok res ) -> 896 | ( rstate, rstream, Ok (Just res) ) 897 | 898 | _ -> 899 | ( state, stream, Ok Nothing ) 900 | 901 | 902 | {-| Apply a parser zero or more times and return a list of the results. 903 | 904 | parse (many (string "a")) "aaab" 905 | -- Ok ["a", "a", "a"] 906 | 907 | parse (many (string "a")) "bbbb" 908 | -- Ok [] 909 | 910 | parse (many (string "a")) "" 911 | -- Ok [] 912 | 913 | -} 914 | many : Parser s a -> Parser s (List a) 915 | many p = 916 | let 917 | accumulate acc state stream = 918 | case app p state stream of 919 | ( rstate, rstream, Ok res ) -> 920 | if stream == rstream then 921 | ( rstate, rstream, List.reverse acc ) 922 | else 923 | accumulate (res :: acc) rstate rstream 924 | 925 | _ -> 926 | ( state, stream, List.reverse acc ) 927 | in 928 | Parser <| 929 | \state stream -> 930 | let 931 | ( rstate, rstream, res ) = 932 | accumulate [] state stream 933 | in 934 | ( rstate, rstream, Ok res ) 935 | 936 | 937 | {-| Parse at least one result. 938 | 939 | parse (many1 (string "a")) "a" 940 | -- Ok ["a"] 941 | 942 | parse (many1 (string "a")) "" 943 | -- Err ["expected \"a\""] 944 | 945 | -} 946 | many1 : Parser s a -> Parser s (List a) 947 | many1 p = 948 | (::) <$> p <*> many p 949 | 950 | 951 | {-| Apply the first parser zero or more times until second parser 952 | succeeds. On success, the list of the first parser's results is returned. 953 | 954 | string "") 955 | 956 | -} 957 | manyTill : Parser s a -> Parser s end -> Parser s (List a) 958 | manyTill p end = 959 | let 960 | accumulate acc state stream = 961 | case app end state stream of 962 | ( rstate, rstream, Ok _ ) -> 963 | ( rstate, rstream, Ok (List.reverse acc) ) 964 | 965 | ( estate, estream, Err ms ) -> 966 | case app p state stream of 967 | ( rstate, rstream, Ok res ) -> 968 | accumulate (res :: acc) rstate rstream 969 | 970 | _ -> 971 | ( estate, estream, Err ms ) 972 | in 973 | Parser (accumulate []) 974 | 975 | 976 | {-| Parser zero or more occurences of one parser separated by another. 977 | 978 | parse (sepBy (string ",") (string "a")) "b" 979 | -- Ok [] 980 | 981 | parse (sepBy (string ",") (string "a")) "a,a,a" 982 | -- Ok ["a", "a", "a"] 983 | 984 | parse (sepBy (string ",") (string "a")) "a,a,b" 985 | -- Ok ["a", "a"] 986 | 987 | -} 988 | sepBy : Parser s x -> Parser s a -> Parser s (List a) 989 | sepBy sep p = 990 | sepBy1 sep p <|> succeed [] 991 | 992 | 993 | {-| Parse one or more occurences of one parser separated by another. 994 | -} 995 | sepBy1 : Parser s x -> Parser s a -> Parser s (List a) 996 | sepBy1 sep p = 997 | (::) <$> p <*> many (sep *> p) 998 | 999 | 1000 | {-| Parse zero or more occurences of one parser separated and 1001 | optionally ended by another. 1002 | 1003 | parse (sepEndBy (string ",") (string "a")) "a,a,a," 1004 | -- Ok ["a", "a", "a"] 1005 | 1006 | -} 1007 | sepEndBy : Parser s x -> Parser s a -> Parser s (List a) 1008 | sepEndBy sep p = 1009 | sepEndBy1 sep p <|> succeed [] 1010 | 1011 | 1012 | {-| Parse one or more occurences of one parser separated and 1013 | optionally ended by another. 1014 | 1015 | parse (sepEndBy1 (string ",") (string "a")) "" 1016 | -- Err ["expected \"a\""] 1017 | 1018 | parse (sepEndBy1 (string ",") (string "a")) "a" 1019 | -- Ok ["a"] 1020 | 1021 | parse (sepEndBy1 (string ",") (string "a")) "a," 1022 | -- Ok ["a"] 1023 | 1024 | -} 1025 | sepEndBy1 : Parser s x -> Parser s a -> Parser s (List a) 1026 | sepEndBy1 sep p = 1027 | sepBy1 sep p <* maybe sep 1028 | 1029 | 1030 | {-| Apply a parser and skip its result. 1031 | -} 1032 | skip : Parser s x -> Parser s () 1033 | skip p = 1034 | () <$ p 1035 | 1036 | 1037 | {-| Apply a parser and skip its result many times. 1038 | -} 1039 | skipMany : Parser s x -> Parser s () 1040 | skipMany p = 1041 | () <$ many (skip p) 1042 | 1043 | 1044 | {-| Apply a parser and skip its result at least once. 1045 | -} 1046 | skipMany1 : Parser s x -> Parser s () 1047 | skipMany1 p = 1048 | () <$ many1 (skip p) 1049 | 1050 | 1051 | {-| Parse one or more occurences of `p` separated by `op`, recursively 1052 | apply all functions returned by `op` to the values returned by `p`. See 1053 | the `examples/Calc.elm` file for an example. 1054 | -} 1055 | chainl : Parser s (a -> a -> a) -> Parser s a -> Parser s a 1056 | chainl op p = 1057 | let 1058 | accumulate x = 1059 | (op 1060 | |> andThen 1061 | (\f -> 1062 | p 1063 | |> andThen (\y -> accumulate (f x y)) 1064 | ) 1065 | ) 1066 | <|> succeed x 1067 | in 1068 | andThen accumulate p 1069 | 1070 | 1071 | {-| Similar to `chainl` but functions of `op` are applied in 1072 | right-associative order to the values of `p`. See the 1073 | `examples/Python.elm` file for a usage example. 1074 | -} 1075 | chainr : Parser s (a -> a -> a) -> Parser s a -> Parser s a 1076 | chainr op p = 1077 | let 1078 | accumulate x = 1079 | (op 1080 | |> andThen 1081 | (\f -> 1082 | p 1083 | |> andThen accumulate 1084 | |> andThen (\y -> succeed (f x y)) 1085 | ) 1086 | ) 1087 | <|> succeed x 1088 | in 1089 | andThen accumulate p 1090 | 1091 | 1092 | {-| Parse `n` occurences of `p`. 1093 | -} 1094 | count : Int -> Parser s a -> Parser s (List a) 1095 | count n p = 1096 | let 1097 | accumulate x acc = 1098 | if x <= 0 then 1099 | succeed (List.reverse acc) 1100 | else 1101 | andThen (\res -> accumulate (x - 1) (res :: acc)) p 1102 | in 1103 | accumulate n [] 1104 | 1105 | 1106 | {-| Parse something between two other parsers. 1107 | 1108 | The parser 1109 | 1110 | between (string "(") (string ")") (string "a") 1111 | 1112 | is equivalent to the parser 1113 | 1114 | string "(" *> string "a" <* string ")" 1115 | 1116 | -} 1117 | between : Parser s l -> Parser s r -> Parser s a -> Parser s a 1118 | between lp rp p = 1119 | lp *> p <* rp 1120 | 1121 | 1122 | {-| Parse something between parentheses. 1123 | -} 1124 | parens : Parser s a -> Parser s a 1125 | parens = 1126 | between (string "(") (string ")") 1127 | 1128 | 1129 | {-| Parse something between braces `{}`. 1130 | -} 1131 | braces : Parser s a -> Parser s a 1132 | braces = 1133 | between (string "{") (string "}") 1134 | 1135 | 1136 | {-| Parse something between square brackets `[]`. 1137 | -} 1138 | brackets : Parser s a -> Parser s a 1139 | brackets = 1140 | between (string "[") (string "]") 1141 | 1142 | 1143 | {-| Parse zero or more whitespace characters. 1144 | 1145 | parse (whitespace *> string "hello") "hello" 1146 | -- Ok "hello" 1147 | 1148 | parse (whitespace *> string "hello") " hello" 1149 | -- Ok "hello" 1150 | 1151 | -} 1152 | whitespace : Parser s String 1153 | whitespace = 1154 | regex "[ \t\x0D\n]*" "whitespace" 1155 | 1156 | 1157 | {-| Parse one or more whitespace characters. 1158 | 1159 | parse (whitespace1 *> string "hello") "hello" 1160 | -- Err ["whitespace"] 1161 | 1162 | parse (whitespace1 *> string "hello") " hello" 1163 | -- Ok "hello" 1164 | 1165 | -} 1166 | whitespace1 : Parser s String 1167 | whitespace1 = 1168 | regex "[ \t\x0D\n]+" "whitespace" 1169 | 1170 | 1171 | 1172 | -- Infix operators 1173 | -- --------------- 1174 | 1175 | 1176 | {-| Variant of `mapError` that replaces the Parser's error with a List 1177 | of a single string. 1178 | 1179 | parse (string "a" "gimme an 'a'") "b" 1180 | -- Err ["gimme an 'a'"] 1181 | 1182 | -} 1183 | () : Parser s a -> String -> Parser s a 1184 | () p m = 1185 | mapError (always [ m ]) p 1186 | 1187 | 1188 | {-| Infix version of `andThen`. 1189 | 1190 | import Combine.Num exposing (int) 1191 | 1192 | choosy : Parser s String 1193 | choosy = 1194 | let 1195 | createParser n = 1196 | if n % 2 == 0 then 1197 | string " is even" 1198 | else 1199 | string " is odd" 1200 | in 1201 | int >>= createParser 1202 | 1203 | parse choosy "1 is odd" 1204 | -- Ok " is odd" 1205 | 1206 | parse choosy "2 is even" 1207 | -- Ok " is even" 1208 | 1209 | parse choosy "1 is even" 1210 | -- Err ["expected \" is odd\""] 1211 | 1212 | -} 1213 | (>>=) : Parser s a -> (a -> Parser s b) -> Parser s b 1214 | (>>=) = 1215 | flip andThen 1216 | 1217 | 1218 | {-| Infix version of `map`. 1219 | 1220 | parse (toString <$> int) "42" 1221 | -- Ok "42" 1222 | 1223 | parse (toString <$> int) "abc" 1224 | -- Err ["expected an integer"] 1225 | 1226 | -} 1227 | (<$>) : (a -> b) -> Parser s a -> Parser s b 1228 | (<$>) = 1229 | map 1230 | 1231 | 1232 | {-| Run a parser and return the value on the left on success. 1233 | 1234 | parse (True <$ string "true") "true" 1235 | -- Ok True 1236 | 1237 | parse (True <$ string "true") "false" 1238 | -- Err ["expected \"true\""] 1239 | 1240 | -} 1241 | (<$) : a -> Parser s x -> Parser s a 1242 | (<$) res = 1243 | map (always res) 1244 | 1245 | 1246 | {-| Run a parser and return the value on the right on success. 1247 | 1248 | parse (string "true" $> True) "true" 1249 | -- Ok True 1250 | 1251 | parse (string "true" $> True) "false" 1252 | -- Err ["expected \"true\""] 1253 | 1254 | -} 1255 | ($>) : Parser s x -> a -> Parser s a 1256 | ($>) = 1257 | flip (<$) 1258 | 1259 | 1260 | {-| Infix version of `andMap`. 1261 | 1262 | add : Int -> Int -> Int 1263 | add = (+) 1264 | 1265 | plus : Parser s String 1266 | plus = string "+" 1267 | 1268 | parse (add <$> int <*> (plus *> int)) "1+1" 1269 | -- Ok 2 1270 | 1271 | -} 1272 | (<*>) : Parser s (a -> b) -> Parser s a -> Parser s b 1273 | (<*>) = 1274 | flip andMap 1275 | 1276 | 1277 | {-| Join two parsers, ignoring the result of the one on the right. 1278 | 1279 | unsuffix : Parser s String 1280 | unsuffix = 1281 | regex "[a-z]" 1282 | <* regex "[!?]" 1283 | 1284 | parse unsuffix "a!" 1285 | -- Ok "a" 1286 | 1287 | -} 1288 | (<*) : Parser s a -> Parser s x -> Parser s a 1289 | (<*) lp rp = 1290 | lp 1291 | |> map always 1292 | |> andMap rp 1293 | 1294 | 1295 | {-| Join two parsers, ignoring the result of the one on the left. 1296 | 1297 | unprefix : Parser s String 1298 | unprefix = 1299 | string ">" 1300 | *> while ((==) ' ') 1301 | *> while ((/=) ' ') 1302 | 1303 | parse unprefix "> a" 1304 | -- Ok "a" 1305 | 1306 | -} 1307 | (*>) : Parser s x -> Parser s a -> Parser s a 1308 | (*>) lp rp = 1309 | lp 1310 | |> map (flip always) 1311 | |> andMap rp 1312 | 1313 | 1314 | {-| Synonym for `or`. 1315 | -} 1316 | (<|>) : Parser s a -> Parser s a -> Parser s a 1317 | (<|>) = 1318 | or 1319 | 1320 | 1321 | 1322 | -- Fixities 1323 | 1324 | 1325 | infixl 1 >>= 1326 | 1327 | 1328 | infixr 1 <|> 1329 | 1330 | 1331 | infixl 4 <$> 1332 | 1333 | 1334 | infixl 4 <$ 1335 | 1336 | 1337 | infixl 4 $> 1338 | 1339 | 1340 | infixl 4 <*> 1341 | 1342 | 1343 | infixl 4 <* 1344 | 1345 | 1346 | infixl 4 *> 1347 | -------------------------------------------------------------------------------- /src/Combine/Char.elm: -------------------------------------------------------------------------------- 1 | module Combine.Char exposing (satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit) 2 | 3 | {-| This module contains `Char`-specific Parsers. 4 | 5 | Avoid using this module if performance is a concern. You can achieve 6 | everything that you can do with this module by using `Combine.regex`, 7 | `Combine.string` or `Combine.primitive` and, in general, those will be 8 | much faster. 9 | 10 | # Parsers 11 | @docs satisfy, char, anyChar, oneOf, noneOf, space, tab, newline, crlf, eol, lower, upper, digit, octDigit, hexDigit 12 | -} 13 | 14 | import Char 15 | import Combine exposing (Parser, primitive, regex, (), (<$), (<|>)) 16 | import String 17 | 18 | 19 | {-| Parse a character matching the predicate. 20 | 21 | parse (satisfy ((==) 'a')) "a" == 22 | -- Ok 'a' 23 | 24 | parse (satisfy ((==) 'a')) "b" == 25 | -- Err ["could not satisfy predicate"] 26 | 27 | -} 28 | satisfy : (Char -> Bool) -> Parser s Char 29 | satisfy pred = 30 | primitive <| 31 | \state stream -> 32 | let 33 | message = 34 | "could not satisfy predicate" 35 | in 36 | case String.uncons stream.input of 37 | Just ( h, rest ) -> 38 | if pred h then 39 | ( state, { stream | input = rest, position = stream.position + 1 }, Ok h ) 40 | else 41 | ( state, stream, Err [ message ] ) 42 | 43 | Nothing -> 44 | ( state, stream, Err [ message ] ) 45 | 46 | 47 | {-| Parse an exact character match. 48 | 49 | parse (char 'a') "a" == 50 | -- Ok 'a' 51 | 52 | parse (char 'a') "b" == 53 | -- Err ["expected 'a'"] 54 | 55 | -} 56 | char : Char -> Parser s Char 57 | char c = 58 | satisfy ((==) c) ("expected " ++ toString c) 59 | 60 | 61 | {-| Parse any character. 62 | 63 | parse anyChar "a" == 64 | -- Ok 'a' 65 | 66 | parse anyChar "" == 67 | -- Err ["expected any character"] 68 | 69 | -} 70 | anyChar : Parser s Char 71 | anyChar = 72 | satisfy (always True) "expected any character" 73 | 74 | 75 | {-| Parse a character from the given list. 76 | 77 | parse (oneOf ['a', 'b']) "a" == 78 | -- Ok 'a' 79 | 80 | parse (oneOf ['a', 'b']) "c" == 81 | -- Err ["expected one of ['a','b']"] 82 | 83 | -} 84 | oneOf : List Char -> Parser s Char 85 | oneOf cs = 86 | satisfy (flip List.member cs) ("expected one of " ++ toString cs) 87 | 88 | 89 | {-| Parse a character that is not in the given list. 90 | 91 | parse (noneOf ['a', 'b']) "c" == 92 | -- Ok 'c' 93 | 94 | parse (noneOf ['a', 'b']) "a" == 95 | -- Err ["expected none of ['a','b']"] 96 | 97 | -} 98 | noneOf : List Char -> Parser s Char 99 | noneOf cs = 100 | satisfy (not << flip List.member cs) ("expected none of " ++ toString cs) 101 | 102 | 103 | {-| Parse a space character. 104 | -} 105 | space : Parser s Char 106 | space = 107 | satisfy ((==) ' ') "expected space" 108 | 109 | 110 | {-| Parse a `\t` character. 111 | -} 112 | tab : Parser s Char 113 | tab = 114 | satisfy ((==) '\t') "expected tab" 115 | 116 | 117 | {-| Parse a `\n` character. 118 | -} 119 | newline : Parser s Char 120 | newline = 121 | satisfy ((==) '\n') "expected newline" 122 | 123 | 124 | {-| Parse a `\r\n` sequence, returning a `\n` character. 125 | -} 126 | crlf : Parser s Char 127 | crlf = 128 | '\n' <$ regex "\x0D\n" "expected crlf" 129 | 130 | 131 | {-| Parse an end of line character or sequence, returning a `\n` character. 132 | -} 133 | eol : Parser s Char 134 | eol = 135 | newline <|> crlf 136 | 137 | 138 | {-| Parse any lowercase character. 139 | -} 140 | lower : Parser s Char 141 | lower = 142 | satisfy Char.isLower "expected a lowercase character" 143 | 144 | 145 | {-| Parse any uppercase character. 146 | -} 147 | upper : Parser s Char 148 | upper = 149 | satisfy Char.isUpper "expected an uppercase character" 150 | 151 | 152 | {-| Parse any base 10 digit. 153 | -} 154 | digit : Parser s Char 155 | digit = 156 | satisfy Char.isDigit "expected a digit" 157 | 158 | 159 | {-| Parse any base 8 digit. 160 | -} 161 | octDigit : Parser s Char 162 | octDigit = 163 | satisfy Char.isOctDigit "expected an octal digit" 164 | 165 | 166 | {-| Parse any base 16 digit. 167 | -} 168 | hexDigit : Parser s Char 169 | hexDigit = 170 | satisfy Char.isHexDigit "expected a hexadecimal digit" 171 | -------------------------------------------------------------------------------- /src/Combine/Num.elm: -------------------------------------------------------------------------------- 1 | module Combine.Num 2 | exposing 3 | ( sign 4 | , digit 5 | , int 6 | , float 7 | ) 8 | 9 | {-| This module contains Parsers specific to parsing numbers. 10 | 11 | # Parsers 12 | @docs sign, digit, int, float 13 | -} 14 | 15 | import Char 16 | import Combine exposing (..) 17 | import Combine.Char 18 | import String 19 | 20 | 21 | unwrap : (String -> Result x res) -> String -> res 22 | unwrap f s = 23 | case f s of 24 | Ok res -> 25 | res 26 | 27 | Err m -> 28 | Debug.crash ("impossible state in Combine.Num.unwrap: " ++ toString m) 29 | 30 | 31 | toInt : String -> Int 32 | toInt = 33 | unwrap String.toInt 34 | 35 | 36 | toFloat : String -> Float 37 | toFloat = 38 | unwrap String.toFloat 39 | 40 | 41 | {-| Parse a numeric sign, returning `1` for positive numbers and `-1` 42 | for negative numbers. 43 | -} 44 | sign : Parser s Int 45 | sign = 46 | optional 1 47 | (choice 48 | [ 1 <$ string "+" 49 | , -1 <$ string "-" 50 | ] 51 | ) 52 | 53 | 54 | {-| Parse a digit. 55 | -} 56 | digit : Parser s Int 57 | digit = 58 | let 59 | toDigit c = 60 | Char.toCode c - Char.toCode '0' 61 | in 62 | toDigit <$> Combine.Char.digit "expected a digit" 63 | 64 | 65 | {-| Parse an integer. 66 | -} 67 | int : Parser s Int 68 | int = 69 | (*) 70 | <$> sign 71 | <*> (toInt <$> regex "(0|[1-9][0-9]*)") 72 | "expected an integer" 73 | 74 | 75 | {-| Parse a float. 76 | -} 77 | float : Parser s Float 78 | float = 79 | ((*) << Basics.toFloat) 80 | <$> sign 81 | <*> (toFloat <$> regex "(0|[1-9][0-9]*)(\\.[0-9]+)") 82 | "expected a float" 83 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | /elm-stuff 2 | elm.js 3 | index.html -------------------------------------------------------------------------------- /tests/CurrentLocationTests.elm: -------------------------------------------------------------------------------- 1 | module CurrentLocationTests exposing (..) 2 | 3 | import Combine exposing (..) 4 | import Combine.Char 5 | import Expect 6 | import Fuzz 7 | import Test exposing (..) 8 | 9 | 10 | entryPoint : Test 11 | entryPoint = 12 | describe "entry point" 13 | [ test "column should be zero with empty input" <| 14 | \() -> 15 | { data = "", input = "", position = 0 } 16 | |> Combine.currentColumn 17 | |> Expect.equal 0 18 | , test "column should be zero with two lines" <| 19 | \() -> 20 | { data = "\n", input = "\n", position = 0 } 21 | |> Combine.currentColumn 22 | |> Expect.equal 0 23 | , fuzz Fuzz.string "column should be zero" <| 24 | \s -> 25 | { data = s, input = s, position = 0 } 26 | |> Combine.currentColumn 27 | |> Expect.equal 0 28 | , fuzz Fuzz.string "line should be zero" <| 29 | \s -> 30 | { data = s, input = s, position = 0 } 31 | |> Combine.currentLine 32 | |> Expect.equal 0 33 | ] 34 | 35 | 36 | specificLocationTests : Test 37 | specificLocationTests = 38 | describe "specific locations" 39 | [ test "should not skip to next line on eol" <| 40 | \() -> 41 | { data = "x\ny", input = "x\ny", position = 1 } 42 | |> Combine.currentLocation 43 | |> Expect.equal { source = "x", line = 0, column = 1 } 44 | , test "should skip to next line on eol + 1" <| 45 | \() -> 46 | { data = "x\ny", input = "x\ny", position = 2 } 47 | |> Combine.currentLocation 48 | |> Expect.equal { source = "y", line = 1, column = 0 } 49 | ] 50 | 51 | 52 | noNegativeValuesForColumn : Test 53 | noNegativeValuesForColumn = 54 | fuzz2 Fuzz.string Fuzz.int "withColumn should never return a value below zero" <| 55 | \s i -> 56 | let 57 | c = 58 | if String.length s == 0 then 59 | 0 60 | else 61 | (i % String.length s) 62 | in 63 | case 64 | Combine.parse 65 | (Combine.count c Combine.Char.anyChar 66 | *> (Combine.withColumn Combine.succeed) 67 | ) 68 | s 69 | of 70 | Err _ -> 71 | Expect.fail "Should always parse" 72 | 73 | Ok ( _, _, v ) -> 74 | Expect.greaterThan -1 v 75 | 76 | 77 | noNegativeValuesForLine : Test 78 | noNegativeValuesForLine = 79 | fuzz2 Fuzz.string Fuzz.int "withLine should never return a value below zero" <| 80 | \s i -> 81 | let 82 | c = 83 | if String.length s == 0 then 84 | 0 85 | else 86 | (i % String.length s) 87 | in 88 | case 89 | Combine.parse 90 | (Combine.count c Combine.Char.anyChar 91 | *> (Combine.withLine Combine.succeed) 92 | ) 93 | s 94 | of 95 | Err _ -> 96 | Expect.fail "Should always parse" 97 | 98 | Ok ( _, _, v ) -> 99 | Expect.greaterThan -1 v 100 | -------------------------------------------------------------------------------- /tests/Parsers.elm: -------------------------------------------------------------------------------- 1 | module Parsers exposing (..) 2 | 3 | import Calc exposing (calc) 4 | import Combine exposing (..) 5 | import Combine.Char exposing (..) 6 | import Expect 7 | import String 8 | import Test exposing (Test, describe, test) 9 | 10 | 11 | successful : String -> Parser () a -> String -> a -> Test 12 | successful desc p s r = 13 | test desc <| 14 | \() -> 15 | case parse p s of 16 | Ok ( _, _, res ) -> 17 | Expect.equal res r 18 | 19 | Err ( _, _, ms ) -> 20 | Expect.fail <| String.join ", " ms 21 | 22 | 23 | calcSuite : Test 24 | calcSuite = 25 | let 26 | equiv s x () = 27 | Expect.equal (calc s) (Ok x) 28 | in 29 | describe "calc example tests" 30 | [ test "Atoms" (equiv "1" 1) 31 | , test "Atoms 2" (equiv "-1" -1) 32 | , test "Parenthesized atoms" (equiv "(1)" 1) 33 | , test "Addition" (equiv "1 + 1" 2) 34 | , test "Subtraction" (equiv "1 - 1" 0) 35 | , test "Multiplication" (equiv "1 * 1" 1) 36 | , test "Division" (equiv "1 / 1" 1) 37 | , test "Precedence 1" (equiv "1 + 2 * 3" 7) 38 | , test "Precedence 2" (equiv "1 + 2 * 3 * 2" 13) 39 | , test "Parenthesized precedence" (equiv "(1 + 2) * 3 * 2" 18) 40 | ] 41 | 42 | 43 | manyTillSuite : Test 44 | manyTillSuite = 45 | let 46 | comment = 47 | string "") 48 | 49 | line = 50 | manyTill anyChar (many space *> eol) 51 | in 52 | describe "manyTill tests" 53 | [ successful "Example" comment "" [ ' ', 't', 'e', 's', 't', ' ' ] 54 | , successful "Backtracking" line "a b c\n" [ 'a', ' ', 'b', ' ', 'c' ] 55 | , successful "Backtracking 2" line "a b c \n" [ 'a', ' ', 'b', ' ', 'c' ] 56 | ] 57 | 58 | 59 | sepEndBySuite : Test 60 | sepEndBySuite = 61 | let 62 | commaSep = 63 | sepEndBy (string ",") (string "a") 64 | in 65 | describe "sepEndBy tests" 66 | [ successful "sepEndBy 1" commaSep "b" [] 67 | , successful "sepEndBy 2" commaSep "a,a,a" [ "a", "a", "a" ] 68 | , successful "sepEndBy 3" commaSep "a,a,a," [ "a", "a", "a" ] 69 | , successful "sepEndBy 4" commaSep "a,a,b" [ "a", "a" ] 70 | ] 71 | 72 | 73 | sepEndBy1Suite : Test 74 | sepEndBy1Suite = 75 | let 76 | commaSep = 77 | sepEndBy1 (string ",") (string "a") 78 | in 79 | describe "sepEndBy1 tests" 80 | [ test "sepEndBy1 1" <| 81 | \() -> 82 | Expect.equal 83 | (parse commaSep "a,a,a") 84 | (Ok ( (), { data = "a,a,a", input = "", position = 5 }, [ "a", "a", "a" ] )) 85 | , test "sepEndBy1 2" <| 86 | \() -> 87 | Expect.equal 88 | (parse commaSep "b") 89 | (Err ( (), { data = "b", input = "b", position = 0 }, [ "expected \"a\"" ] )) 90 | , test "sepEndBy1 3" <| 91 | \() -> 92 | Expect.equal 93 | (parse commaSep "a,a,a,") 94 | (Ok ( (), { data = "a,a,a,", input = "", position = 6 }, [ "a", "a", "a" ] )) 95 | , test "sepEndBy1 4" <| 96 | \() -> 97 | Expect.equal 98 | (parse commaSep "a,a,b") 99 | (Ok ( (), { data = "a,a,b", input = "b", position = 4 }, [ "a", "a" ] )) 100 | ] 101 | 102 | 103 | sequenceSuite : Test 104 | sequenceSuite = 105 | describe "sequence tests" 106 | [ test "empty sequence" <| 107 | \() -> 108 | Expect.equal 109 | (parse (sequence []) "a") 110 | (Ok ( (), { data = "a", input = "a", position = 0 }, [] )) 111 | , test "one parser" <| 112 | \() -> 113 | Expect.equal 114 | (parse (sequence [ many <| string "a" ]) "aaaab") 115 | (Ok ( (), { data = "aaaab", input = "b", position = 4 }, [ [ "a", "a", "a", "a" ] ] )) 116 | , test "many parsers" <| 117 | \() -> 118 | Expect.equal 119 | (parse (sequence [ string "a", string "b", string "c" ]) "abc") 120 | (Ok ( (), { data = "abc", input = "", position = 3 }, [ "a", "b", "c" ] )) 121 | , test "many parsers failure" <| 122 | \() -> 123 | Expect.equal 124 | (parse (sequence [ string "a", string "b", string "c" ]) "abd") 125 | (Err ( (), { data = "abd", input = "d", position = 2 }, [ "expected \"c\"" ] )) 126 | ] 127 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "parser-combinators test suite", 4 | "repository": "https://github.com/elm-community/parser-combinators.git", 5 | "license": "BSD3", 6 | "source-directories": [".", "../examples", "../src"], 7 | "exposed-modules": [ 8 | "Calc", 9 | "Combine", 10 | "Combine.Char", 11 | "Combine.Infix", 12 | "Combine.Num" 13 | ], 14 | "dependencies": { 15 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0", 16 | "elm-lang/core": "5.0.0 <= v < 6.0.0", 17 | "elm-lang/lazy": "2.0.0 <= v < 3.0.0" 18 | }, 19 | "elm-version": "0.18.0 <= v < 0.19.0" 20 | } 21 | --------------------------------------------------------------------------------