{"id":15293543,"url":"https://github.com/prosumma/parsimonious","last_synced_at":"2026-01-06T02:03:31.429Z","repository":{"id":34578121,"uuid":"38524602","full_name":"Prosumma/Parsimonious","owner":"Prosumma","description":"A parsimonious little parser combinator framework for Swift","archived":false,"fork":false,"pushed_at":"2024-01-24T16:12:10.000Z","size":229,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-04-23T13:09:37.220Z","etag":null,"topics":["parser-combinators","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"duo-labs/cloudmapper","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Prosumma.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-07-04T06:40:18.000Z","updated_at":"2022-01-02T06:16:00.000Z","dependencies_parsed_at":"2024-01-22T18:30:00.319Z","dependency_job_id":"57b0c3c6-2a5e-4f77-bbd2-8d1947c96950","html_url":"https://github.com/Prosumma/Parsimonious","commit_stats":{"total_commits":61,"total_committers":3,"mean_commits":"20.333333333333332","dds":"0.032786885245901676","last_synced_commit":"90d97b364450c25df9879d0af47692dd5611448f"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Prosumma%2FParsimonious","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Prosumma%2FParsimonious/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Prosumma%2FParsimonious/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Prosumma%2FParsimonious/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Prosumma","download_url":"https://codeload.github.com/Prosumma/Parsimonious/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245294768,"owners_count":20591909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["parser-combinators","swift"],"created_at":"2024-09-30T16:49:54.745Z","updated_at":"2026-01-06T02:03:26.391Z","avatar_url":"https://github.com/Prosumma.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Parsimonious\n\nParsimonious is a parser combinator library written in Swift. While there are several others now, Parsimonious is one of the oldest, started in early 2019.\n\nParsimonious used functional programming concepts from the start, but the latest version is the most functional yet, emphasizing composability and immutability.\n\nA Parsimonious parser is a type of the form `Parser\u003cSource: Collection, Output\u003e`, where `Source` is the collection we're parsing, and `Output` is the type of the value the parser returns.\n\nIn most cases, `Source` is a `String`, but it can be any `Collection` type. It's entirely possible to parse arrays of integers, for example. (See the unit tests.)\n\nParser combinators are large topic which I cannot cover here. If you're already familiar with parser combinators, Parsimonious shouldn't be too difficult. The unit tests contain almost all you need to know, including the implementation of a complete JSON parser.\n\n## Collection Parser vs Stream Parser\n\nMost parser combinators, such as those found in Haskell, are _stream parsers_. Stream parsers are a great way to parse large amounts of data efficiently. For example, if you have a very large file and want to parse it without loading the entire thing into memory at once, a stream parser is a great way to go.\n\nParsimonious, however, is a `Collection` parser. The primary disadvantage of a `Collection` parser is that the entire collection must be loaded into memory. (Theoretically, a Swift `Collection` does not have this requirement, but in practice it always does.) The advantage of a `Collection` parser is that it is easy to write and easy to use: It supports backtracking out of the box.\n\nBecause of this, Parsimonious is great for tokenizing, writing DSLs, and even doing further processing on the tokens to produce an AST. For example, the first collection parsed could be a `String` (a `Collection` of `Character` instances.) These could be parsed into an array of `Token` instances (i.e., a `Collection` of tokens). But because Parsimonious can parse a `Collection` of any type, the tokens can then be parsed to create an AST.\n\n## Basic Combinators \u0026amp; Operators\n\nThe fundamental combinator is `match`, which matches a single element in the underlying collection using a predicate:\n\n```swift\npublic func match\u003cC: Collection\u003e(_ test: @escaping (C.Element) -\u003e Bool) -\u003e Parser\u003cC, C.Element\u003e\n```\n\nAll other combinators ultimately build atop this one.\n\n### Strings\n\nWhen working with strings, `match` can be a bit inconvenient, because it returns a `Character`, and combinators built on it will often return `[Character]`. Most of the time, that's not what we want, so there are specialized combinators for working with `Character` and `String`. These always return a `String`.\n\n```swift\npublic func char\u003cC: Collection\u003e(\n  _ test: @escaping (Character) -\u003e Bool\n) -\u003e Parser\u003cC, String\u003e where C.Element == Character {\n  match(test).joined()\n}\n```\n\nThis is essentially the same as `match`, but it consumes a `Character` and gives a `String`.\n\n### Operators\n\nWhat parser combinator library would be worth its salt without operators?\n\nParsimonious has only a few. \n\n`postfix operator *`\n\nThis operator is exactly the same as the `many` combinator, which matches 0 or more of the underlying parser. For example, to match 0 or more of the character \"a\":\n\n```swift\nmatch(\"a\")*\n```\n\nThis will return `[Character]`. Postfix `*` is overloaded to work with combinators which return `Character` or `String`, in which case it always returns `String`:\n\n```swift\nchar(\"a\")*\n```\n\nThis will return a `String` containing the matched characters.\n\n`postfix operator +`\n\nThis operator is the same as the `many1` combinator, which matches 1 or more of the underlying parser.\n\n```swift\nchar(\"a\")+\n```\n\nThe above matches 1 or more \"a\" characters.\n\nEverything I said about `*` above applies to `+`.\n\n`prefix operator *`\n\nThis is the same as the `optional` combinator:\n\n```swift\n*char(\"a\")\n```\n\nThe above returns 0 or 1 \"a\". The returned value must implement the `Defaultable` protocol. In the event of a failed match, the default value is returned. In this case, that's an empty `String`.\n\n`infix operator \u003c|\u003e`\n\nThis is the choice operator:\n\n```swift\nstring(\"foo\") \u003c|\u003e string(\"bar\")\n```\n\nThis first attempts to match the string \"foo\". If that fails, it attempts to match \"bar\". If that fails, then parsing fails with the error of the last attempted match. The `fail` combinator can be used to customize errors:\n\n```swift\nstring(\"foo\") \u003c|\u003e string(\"bar\") \u003c|\u003e fail(MyError.oops)\n```\n\n`infix operator +`\n\nThe built-in `+` operator has been overloaded to join together arrays and strings respectively.\n\n```swift\nstring(\"foo\")* + string(\"bar\")\n```\n\nThis matches 0 or more of the string \"foo\" followed by 1 instance of the string \"bar\", so it would match \"bar\", \"foobar\", \"foofoobar\", etc.\n\n### Element Predicates\n\nThe `match` and `char` combinators have overloads which take _element predicates_. An element predicate matches an element and, if the match is successful, the parser returns that element.\n\nThe most fundamental element predicate is `(C.Element) -\u003e Bool`. There are two others. First, a `KeyPath` of type `KeyPath\u003cC.Element, Bool\u003e` and second, a model element of type `C.Element` where `C.Element` is `Equatable`.\n\nHere are some simple examples:\n\n```swift\nchar { $0 == \"e\" } // Matches and returns the character \"e\"\nchar(\\Character.isWhitespace) // Matches a whitespace character and returns it\nchar(\"e\") // Matches and returns the character \"e\"\n```\n\nThis can be expanded using a small DSL:\n\n```swift\n// Match any character which is not whitespace or a newline.\nchar(!any(\\.isWhitespace, \\.isNewline))\n```\n\nWhen model elements, keypaths and predicates are mixed, the prefix `^` operator can convert the elements and keypaths to a predicate, e.g.,\n\n```swift\nchar(!any(^\"e\", ^\\.isWhitespace))\n```\n\nWhen negating, `^` is not needed:\n\n```swift\nchar(all(!\"e\", !\\.isWhitespace))\n```\n\n## Laziness\n\nWherever possible, combinators which take other parsers as arguments use `@escaping @autoclosure` for this.\n\nThis is important in a parser combinator library because combinators are often recursive. Consider the following from the unit tests:\n\n```swift\nvar jarray: JParser = bracketed(many(json, separator: \",\")) \u003e\u003e\u003e JSON.array\nlet json = whitespacedWithNewlines(jstring \u003c|\u003e jnumber \u003c|\u003e jobject \u003c|\u003e jarray \u003c|\u003e jbool \u003c|\u003e jnull)\n```\n\nNotice that `jarray` refers to `json` and _vice versa_. Without laziness, this wouldn't be possible. The Swift compiler would complain.\n\nIf you write your own combinators, I highly recommend this practice.\n\nThere are a few places where `@escaping @autoclosure` cannot be used, such as variadic parameters. Most of the time, this should not be a problem, but in the rare circumstance when it is, the `deferred` combinator can be used to make any parser lazy:\n\n```swift\nlet bar = chain(deferred(foo), baz)\nlet foo = chain(deferred(bar), boo)\n```\n\nSince `chain` uses variadic parameters, they cannot be autoclosures. Using `deferred` here allows the two definitions to reference each other. Since `baz` and `boo` presumably don't reference `bar` and `foo`, using `deferred` with them is not necessary.\n\nAn overload of `deferred` allows the definition of an `ad hoc`, lazy parser:\n\n```swift\nlet nothing: Parser\u003cString, Void\u003e = deferred { source, index in\n  return .success(State(output: (), range: index..\u003cindex\u003e))\n}\n```\n\nThis parser consumes nothing and returns `Void`, but illustrates the point.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprosumma%2Fparsimonious","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprosumma%2Fparsimonious","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprosumma%2Fparsimonious/lists"}