{"id":15651316,"url":"https://github.com/izeigerman/parsecat","last_synced_at":"2026-03-16T01:37:26.416Z","repository":{"id":47772964,"uuid":"128852703","full_name":"izeigerman/parsecat","owner":"izeigerman","description":"Pure functional parser combinator library which supports both applicative and monadic styles of parsing.","archived":false,"fork":false,"pushed_at":"2021-09-20T17:01:11.000Z","size":86,"stargazers_count":32,"open_issues_count":1,"forks_count":0,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-30T18:51:10.492Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/izeigerman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-10T01:04:45.000Z","updated_at":"2024-03-17T22:44:10.000Z","dependencies_parsed_at":"2022-08-25T15:10:11.517Z","dependency_job_id":null,"html_url":"https://github.com/izeigerman/parsecat","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izeigerman%2Fparsecat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izeigerman%2Fparsecat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izeigerman%2Fparsecat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/izeigerman%2Fparsecat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/izeigerman","download_url":"https://codeload.github.com/izeigerman/parsecat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251764693,"owners_count":21640117,"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-10-03T12:37:55.104Z","updated_at":"2026-03-16T01:37:26.347Z","avatar_url":"https://github.com/izeigerman.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Parsecat\n\n[![Build Status](https://travis-ci.org/izeigerman/parsecat.svg?branch=master)](https://travis-ci.org/izeigerman/parsecat)\n[![Coverage Status](https://coveralls.io/repos/github/izeigerman/parsecat/badge.svg)](https://coveralls.io/github/izeigerman/parsecat)\n\n**Parsecat** is a lightweight, pure-functional parser monad transformer and combinator library, which supports both applicative and monadic styles of parsing.\n\n## Usage\nSupports Scala 2.12 and 2.13.\n```scala\nlibraryDependencies += \"com.github.izeigerman\" %% \"parsecat-core\" % \"0.3.0\"\n// to include JSON parsers\nlibraryDependencies += \"com.github.izeigerman\" %% \"parsecat-json\" % \"0.3.0\"\n```\n\n### Text parser\nThe text parser is just a type alias for a specialized `ParsetT`:\n```scala\ntype TextParser[A] = ParserT[Id, PagedStream[Char], TextParserContext, TextPosition, A]\n```\nIt supports a 2-dimensional position tracking and has a variety of string and character implementations.\n\n#### Character and String parsers\nBefore starting to use the parser the following imports are required:\n```scala\nscala\u003e import cats.implicits._\nimport cats.implicits._\n\nscala\u003e import parsecat.parsers.string._\nimport parsecat.parsers.string._\n```\nLet's parse the following string:\n```scala\nscala\u003e val str = \"Hello World\"\nstr: String = Hello World\n```\nA parser for this string can be defined in a couple of ways. I.e.:\n```scala\nscala\u003e val parserA = andThen(string(\"Hello\") \u003c* space, string(\"World\"))\nparserA: parsecat.ParserT[cats.Id,parsecat.stream.PagedStream[Char],parsecat.parsers.TextParserContext,parsecat.parsers.TextPosition,(String, String)] = parsecat.ParserT@6f92758f\n```\nThe parser above relies on applicative properties of `ParserT` and uses the `\u003c*` operator to parse both: string \"Hello\" and a whitespace, but then keeps only a result from the first parser. `andThen` - is one of many combinator functions that are included into this library. The full list can be found [here](https://github.com/izeigerman/parsecat/blob/master/core/src/main/scala/parsecat/Combinators.scala). The result of applying of this parser to the test string is the following:\n```scala\nscala\u003e parseText(parserA, str)\nres1: Either[parsecat.ParseError[parsecat.parsers.TextPosition],(String, String)] = Right((Hello,World))\n```\nThere is also a different style of combining parsers together - a monadic one. The exactly same parser can be defined differently:\n```scala\nscala\u003e val parserM = andThen(string(\"Hello\"), space \u003e\u003e string(\"World\"))\nparserM: parsecat.ParserT[cats.Id,parsecat.stream.PagedStream[Char],parsecat.parsers.TextParserContext,parsecat.parsers.TextPosition,(String, String)] = parsecat.ParserT@7daf7131\n```\nThis time we rely on monadic properties of `ParserT` and use the `\u003e\u003e` operator - a special binding operator which discards the result of the first action. The result produced by this parser is completely the same:\n```scala\nscala\u003e parseText(parserM, str)\nres2: Either[parsecat.ParseError[parsecat.parsers.TextPosition],(String, String)] = Right((Hello,World))\n```\nWhen the parsing is unsuccessful the error will contain a very detailed information about what went wrong:\n```scala\nscala\u003e import parsecat.parsers.regex._\nimport parsecat.parsers.regex._\n\nscala\u003e val str = \"\"\"\n     | Hello\n     | World\n     | \"\"\".stripMargin\nstr: String =\n\"\nHello\nWorld\n\"\n\nscala\u003e val parser = andThen(eol \u003e\u003e string(\"Hello\"), eol \u003e\u003e regex(\"o.+d\".r))\nparser: parsecat.ParserT[cats.Id,parsecat.stream.PagedStream[Char],parsecat.parsers.TextParserContext,parsecat.parsers.TextPosition,(String, String)] = parsecat.ParserT@789c90f4\n\nscala\u003e parseText(parser, str)\nres4: Either[parsecat.ParseError[parsecat.parsers.TextPosition],(String, String)] = Left(parsecat.ParseError: [Parsecat] (row 3, column 1): input doesn't match regex 'o.+d')\n```\nHere are some other parser application examples:\n```scala\nscala\u003e parseText(many(char('a')), \"aabb\")\nres5: Either[parsecat.ParseError[parsecat.parsers.TextPosition],List[Char]] = Right(List(a, a))\n```\n```scala\nscala\u003e parseText(manyTill(anyChar, char('b')), \"aaab\")\nres6: Either[parsecat.ParseError[parsecat.parsers.TextPosition],List[Char]] = Right(List(a, a, a))\n```\n**Note:** in examples above we used `many` and `manyTill` combinators. Although this approach looks appealing, it causes creation of potentially big number of monadic bindings at runtime. This may lead to a considerable performance degradation. Use these combinators carefully and consider using the string-specific alternatives (`satisfyMany`, `anyCharTill`, `oneOfMany`, etc.) instead. The same is true for combinators `many1`, `skipMany` and `skipMany1`.\n```scala\nscala\u003e parseText(char('a') \u003c+\u003e char('b'), \"baba\")\nres7: Either[parsecat.ParseError[parsecat.parsers.TextPosition],Char] = Right(b)\n```\n**Note:** in the last example we used the `\u003c+\u003e` operator. This is a monoid associative operator or a sum (contrary to a product provided by the applicative functor). It first applies a parser to its left and if the parsing is unsuccessful, the parser on the right side of the expression will be applied instead. The same can be expressed with a help of the `orElse` combinator:\n```scala\nscala\u003e parseText(orElse(char('a'), char('b')), \"baba\")\nres9: Either[parsecat.ParseError[parsecat.parsers.TextPosition],Char] = Right(b)\n```\n\n#### Numeric parsers\nNumeric parsers - are extension to character parsers, which introduce parsing support for numeric literals. The following import is required:\n```scala\nscala\u003e import parsecat.parsers.numeric._\nimport parsecat.parsers.numeric._\n```\nExamples:\n```scala\nscala\u003e parseText(integer, \"1234567\")\nres10: Either[parsecat.ParseError[parsecat.parsers.TextPosition],Int] = Right(1234567)\n\n\nscala\u003e parseText(double, \"1.2345E67\")\nres11: Either[parsecat.ParseError[parsecat.parsers.TextPosition],Double] = Right(1.2345E67)\n```\nAll common numeric types are supported: `byte`, `short`, `int`, `long`, `float`, `double`, `bigInt` and `bigDecimal`.\n\n#### JSON parser\nThe JSON parser was added as a reference implementation and a good example of expressive power of parser combinators. The entire implementation is less than 60 LOC and could've been a part of this README, but instead you may find it here: [https://github.com/izeigerman/parsecat/blob/master/json/src/main/scala/parsecat/parsers/json/JsonParsers.scala](https://github.com/izeigerman/parsecat/blob/master/json/src/main/scala/parsecat/parsers/json/JsonParsers.scala).\n```scala\nscala\u003e :paste\n// Entering paste mode (ctrl-D to finish)\n\nimport parsecat.parsers.json._\n\nval jsonStr =\n  \"\"\"{\n    |  \"field1\": \"test\",\n    |  \"field2\": [\n    |    1, 2, 3\n    |  ],\n    |  \"field3\": {\n    |    \"field4\": true,\n    |    \"field5\": null,\n    |    \"field6\": [\n    |      { \"field7\": 1.234 }, { \"field8\": false }\n    |    ]\n    |  }\n    |}\"\"\".stripMargin\n\n\nparseJson(jsonStr)\n\n\n// Exiting paste mode, now interpreting.\n\n\nres12: Either[parsecat.ParseError[parsecat.parsers.TextPosition],parsecat.parsers.json.JsValue] = Right(JsObject(Map(field1 -\u003e JsString(test), field2 -\u003e JsArray(List(JsInt(1), JsInt(2), JsInt(3))), field3 -\u003e JsObject(Map(field4 -\u003e JsBoolean(true), field5 -\u003e JsNull, field6 -\u003e JsArray(List(JsObject(Map(field7 -\u003e JsDouble(1.234))), JsObject(Map(field8 -\u003e JsBoolean(false))))))))))\n```\n**NOTE:** this parser was created as an example and a reference implementation and should never be used in a real project. Although the general parsing performance has been significantly improved in the version `0.2.0`, it still can't compete with any modern hand-written JSON parser out there. Its performance is the same as `scala-parser-combinators` version of JSON parser, which is deprecated by now.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fizeigerman%2Fparsecat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fizeigerman%2Fparsecat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fizeigerman%2Fparsecat/lists"}