Parsel is a parser combinator library that makes it easy to write parsers. Parser combinators lets you create simple parses that can be combined together to very complex ones. Take for example a parser that parses a digit from a given String: (You can use the pre-defined lexical parser for digit: L.digit
)
let digit = Parser<String, Int> { input in
guard let first = input.first, let number = Int(String(first)) else {
return .fail(/* some pre-defined error */)
}
return .success(result: number, rest: String(input.dropFirst()))
}
We can now simply extend this to create a parser that parses an addition of two digits from a string:
let addition = (digit ~ L.plus ~ digit).map { a, _, b in a b } // `L.plus` is a predefined parser that parses the ` ` sign
let result = addition.parse("2 4")
try! result.unwrap() // Int: 6
Parsing is a very common task, it does not always mean to parse source code or JSON strings. Parsing means to transform an unstructured input to a structured output. In case of source code this means to parse a raw string to an AST (abstract syntax tree), in case of an addition it means to parse the result of adding two numbers out of a string.
Parsing can always fail, if the input does not match the needed grammer. If the input string in the above example would have been 1
, it would have been failed because the second number is missing.
The advantage of parser combinators is that you start with a very basic parser. In the above example digit
parses only one digit. But it is not hard, to add a parser that parses more than one digit. A number is a repetition of mulitple digits. For repetition, we can use rep
, which tries to apply the parser until it fails and collects the result as an array.
Parsing an integer addition is as easy as
func intFromDigits(_ digits: [Int]) -> Int {
return digits.reduce(0) { res, e in
return res * 10 e
}
}
let number = digit.rep.map(intFromDigits)
let addition = number ~ L.plus ~ number ^^ { a, _, b in // ^^ is convenience for map
return a b
}
let result = addition.parse("123 456")
try! result.unwrap() // Int: 579
(There is also a pre-defined lexical parser for numbers L.number
that is able to parse a number in different formats [binary, octal, hexadecimal, decimal])
Since parser combinators are very high level, they abstract the whole process of parsing a lot. That means it is easier to use but it also means that the performance is not as good as an optimized, handwritten parser.
Parsel is currently available via Swift Package Manager and Cocoapods. Support for Carthage will come later.
To use Parsel in your project, just add it as a dependency in your Package.swift
file and run swift package update
.
import PackageDescription
let package = Package(
name: "MyAwesomeApp",
dependencies: [
.package(url: "https://github.com/BenchR267/Parsel", from: "3.0.2")
]
)
To use Parsel with Cocoapods, just add this entry to your Podfile and run pod install
:
target 'MyAwesomeApp' do
use_frameworks!
pod 'Parsel'
end
- Swift 4.0
- Parsel is written in Swift 4.0 development snapshots and will be available when Swift 4.0 is released
Calculator is a small example I wrote with Parsel.
Check out the documentation on the Github page.
To start contributing to Parsel please clone the project and configure it initially:
$ git clone [email protected]:BenchR267/Parsel.git
$ cd Parsel
$ make initial
Please make sure that all tests are green before you submit a pull request by running make test
.
If you experience a bug or have an idea for a feature request but you don't know where to get started: feel free to open an issue with a self-explaining description text.
If you have an idea for a better (more readable and/or faster) implementation for an existing function: feel free to change the code and submit a pull request. I will be more than happy to review the changes to make Parsel the best project it can be!
Parsel is under MIT license. See the LICENSE file for more info.