{"id":20683204,"url":"https://github.com/simonsolnes/sarpe","last_synced_at":"2025-06-12T17:08:51.251Z","repository":{"id":163220296,"uuid":"638630132","full_name":"simonsolnes/Sarpe","owner":"simonsolnes","description":"Schwifty Parser Combinators","archived":false,"fork":false,"pushed_at":"2024-02-02T20:00:04.000Z","size":21,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"trunk","last_synced_at":"2025-06-10T22:04:08.665Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simonsolnes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"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":"2023-05-09T19:00:35.000Z","updated_at":"2023-05-09T19:15:14.000Z","dependencies_parsed_at":"2025-03-10T21:48:37.685Z","dependency_job_id":"be70b065-fb83-4359-a8fb-0c6df61509e4","html_url":"https://github.com/simonsolnes/Sarpe","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/simonsolnes/Sarpe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonsolnes%2FSarpe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonsolnes%2FSarpe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonsolnes%2FSarpe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonsolnes%2FSarpe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simonsolnes","download_url":"https://codeload.github.com/simonsolnes/Sarpe/tar.gz/refs/heads/trunk","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonsolnes%2FSarpe/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259509427,"owners_count":22868834,"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":[],"created_at":"2024-11-16T22:15:53.933Z","updated_at":"2025-06-12T17:08:51.233Z","avatar_url":"https://github.com/simonsolnes.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sarpe\n/sɑːrp/\n\nHaskell-like parser combinators in a Schwifty manner\n\n**Toy project**  \nIf you are looking for something more production-ready, I would reccomend: [davedufresne/SwiftParsec](https://github.com/davedufresne/SwiftParsec)\n\nMost the theory is from Scott Wlaschin's work on parser combinators with F#. I really reccomend his talk [\"Understanding parser combinators: a deep dive\"](https://www.youtube.com/watch?v=RDalzi7mhdY). It was my first intro to combinators and what peaked my interest.\n\n## What are parser combinators\nParsers combinators are parsers that be semantically combined with other parsers. Such as:\n\n- Presedence: `parser1.preceded(by: parser2)`\n- Repitition: `parser1.repeat(4...)`\n- Branching: `either(parser1, parser2)`\n\nParser combinators are also monads, which mean we can bind them to a function that returns a parser, and apply it to the result.\n\n- Map: `parseDigits.map { Int($0) }`\n- Optional: `parser.optional()` \n- Application: `parser1.apply(parser2)`\n\nThe most important parser is `satisfy`, which takes one element and returns `.success` if it matches the predicate function.\n\n*Example*: `satsify {\"a\"...\"z\" ~= $0}` (takes a character if it's a lowercase letter)\n\nThe whole parser library can be built from `bind` and `satisfy`, but with supporting backtrack prevention, limits and for optimalization reasons, there are a few custom functions.\n\nCombinators are very modular, so one can implement the parts and combine it to a bigger parser.\n\n## Features\n\n- **Swifty**: Focus on creating parsers with Swift's expressiveness rather having a big API surface\n- **No operator overloading**: a lot of monadic parser libraries use `\u003c\u0026\u003e`, '\u003e\u003e=`, `\u003c|\u003e` etc. to combine parsers. I prefer words.\n- **Few primitives**: Makes it easy to optimize\n- **Generic**: Not limited to strings\n- **Value oriented**: Parsers are values, and they are immutable.\n- **Backtrack prevention**:\n- **Buffer limit aware**:\n\n\n## Examples\n\n### Cat or dog\n\n```swift\nenum Animal {\n    case cat\n    case dog\n}\nlet parser = either(\n    literal(\"cat\").to(Animal.cat),\n    literal(\"dog\").to(Animal.dog)\n)\n\nassert(parser.parse(\"cat\") == .success(.cat, \"\"))\n```\n\n### `Bind` example\n\n```swift\nenum Number: Equatable {\n    case signed(Int)\n    case unsigned(UInt)\n}\n\nlet number = char(\"-\").optional().bind { minus in\n    let unsignedNumber = satisfy { \"0\" ... \"9\" ~= $0 }\n        .repeat(0...)\n        .map { String($0) }\n\n    if let minus {\n        return unsignedNumber\n            .map { -Int($0)! }\n            .map { Number.signed($0) }\n    } else {\n        return unsignedNumber\n            .map { UInt($0)! }\n            .map { Number.unsigned($0) }\n    }\n}\n\nassert(number.parse(\"3\") == .limit(.unsigned(3), \"\"))\nassert(number.parse(\"-0345somethingElse\") == .success(.signed(-345), \"somethingElse\"))\n```\n\n### JSON array\n\nWhere `jsonWhitspace` and `jsonValue` are already declared.\n\n```swift\nlet jsonArray = either(\n    jsonWhitespace\n        .preceded(by: char(\"[\"))\n        .terminated(by: char(\"]\"))\n        .to(JSON.array([])),\n\n    serial(\n        jsonValue(),\n        jsonValue()\n            .preceded(by: char(\",\"))\n            .repeat(0...)\n    ).map { first, rest in\n        [first] + rest\n    }\n    .preceded(by: char(\"[\"))\n    .terminated(by: char(\"]\"))\n    .map { JSON.array($0) }\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonsolnes%2Fsarpe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimonsolnes%2Fsarpe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonsolnes%2Fsarpe/lists"}