{"id":17968335,"url":"https://github.com/travisbrown/expressier","last_synced_at":"2025-03-25T09:30:46.678Z","repository":{"id":15759071,"uuid":"18497856","full_name":"travisbrown/expressier","owner":"travisbrown","description":"A regular expression type provider demo","archived":false,"fork":false,"pushed_at":"2015-09-16T04:15:02.000Z","size":284,"stargazers_count":40,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-20T00:38:43.804Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"KomInn/kominn-poc","license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/travisbrown.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":"2014-04-06T19:52:51.000Z","updated_at":"2021-05-31T10:00:20.000Z","dependencies_parsed_at":"2022-09-24T03:51:49.126Z","dependency_job_id":null,"html_url":"https://github.com/travisbrown/expressier","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fexpressier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fexpressier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fexpressier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbrown%2Fexpressier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/travisbrown","download_url":"https://codeload.github.com/travisbrown/expressier/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245435039,"owners_count":20614817,"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-29T14:21:01.385Z","updated_at":"2025-03-25T09:30:46.671Z","avatar_url":"https://github.com/travisbrown.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# expressier\n\n[![Build status](https://travis-ci.org/travisbrown/expressier.svg?branch=master)](https://travis-ci.org/travisbrown/expressier)\n[![Coverage status](http://codecov.io/github/travisbrown/expressier/coverage.svg?branch=master)](http://codecov.io/github/travisbrown/expressier?branch=master)\n\n## tl;dr\n\nGet more useful stuff out of your regular expressions:\n\n```scala\nscala\u003e import io.expressier._\nimport io.expressier._\n\nscala\u003e case class Item(name: String, x: Int, c: Char, s: String)\ndefined class Item\n\nscala\u003e val parse = \"\"\"(?\u003cname\u003e\\w+): (?\u003cx\u003e\\d+), (?\u003cc\u003e.), (?\u003cs\u003e[A-Z]*)\"\"\".express[Item]\nparse: io.expressier.Express.To[Item] = ...\n\nscala\u003e val result: Option[Item] = parse(\"Foo: 1001, ?, BAR\")\nresult: Option[Item] = Some(Item(Foo,1001,?,BAR))\n```\n\nOr don't even bother with the case class:\n\n```scala\nscala\u003e val Qux = x\"\"\"(?\u003cfirst\u003e.+) (?\u003csecond\u003e\\d+) (?\u003cthird\u003e.) (?\u003clast\u003e\\1)\"\"\"\n\nscala\u003e Qux(\"foo 12345 - foo\").map(_.first)\nres0: Option[String] = Some(foo)\n\nscala\u003e Qux(\"foo 12345 - foo\").map(_.second)\nres1: Option[Int] = Some(12345)\n```\n\nA group matching `\\d+` will end up as an `Int`, a `.` will be a `Char`, etc.\nIf the types don't line up or you try to refer to a named group that doesn't\nexist, the compiler will let you know.\n\n## Overview\n\nThis is a quick proof-of-concept demonstration of what a type provider for\nregular expressions might look like. There are currently lots of limitations\nand problems with the implementation, and there's essentially no documentation\nbeyond this README. For a more detailed introduction to type providers in Scala,\nplease see [this project](https://github.com/travisbrown/type-provider-examples)\nand the [associated slide deck][type-provider-slides].\n\nexpressier is cross-built for Scala 2.10 and 2.11 (from a single codebase,\nthanks to Miles Sabin's new [macro-compat][macro-compat] project), but it\nprovides only partial support for [Scala.js][scala-js], since named groups in\nregular expressions currently do not work on that platform.\n\n## Motivation\n\nSuppose I've got a file with some kind of simple data format that I've read\ninto a list of strings.\n\n``` scala\nval rows = List(\n  \"en: 456, 123, 1203\",\n  \"de:  12, 567,  200\",\n  \"cz:   1,  32,   10\"\n)\n```\n\nNow suppose I only care about totals, and want a map from the language identifier\nto the sum of the three values. Assuming the format is fairly simple, it's\nreasonable to try to solve this problem with a regular expression:\n\n``` scala\nval Row = \"\"\"(\\w\\w)\\s*:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\"\"\".r\n```\n\nNow we can write something like the following:\n\n``` scala\nrows.map {\n  case Row(id, first, second, third) =\u003e (id, first + second + third)\n  case _ =\u003e sys.error(\"Invalid input\")\n}.toMap\n```\n\nExcept of course that thanks to [the magic of `any2stringadd`](https://issues.scala-lang.org/browse/SI-194)\nthis gives us something completely different from what we expected:\n\n``` scala\nMap(en -\u003e 4561231203, de -\u003e 12567200, cz -\u003e 13210)\n```\n\nWhere the values are strings, not integers. It's easy to forget that even\nthough we know that we should be able to convert `(\\d+)` into an integer\n(assuming we'll never have more than nine or ten digits), the compiler doesn't,\nand it's just handing those matches back to us as strings. So we write the\nfollowing:\n\n``` scala\nrows.map {\n  case Row(id, first, second, third) =\u003e\n    (id, first.toInt + second.toInt + third.toInt)\n  case _ =\u003e sys.error(\"Invalid input\")\n}.toMap\n```\n\nThis works, but it's more verbose, a little redundant, and it involves a\npartial function. We know `toInt` should never explode here, but the compiler\ndoesn't.\n\n## With our type provider\n\nThe implementation provided here allows the following usage:\n\n``` scala\nimport io.expressier._\n\nval Row = x\"\"\"(\\w\\w)\\s*:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\"\"\"\n\nval totals: Map[String, Int] = rows.collect {\n  case Row(id, first, second, third) =\u003e (id, first + second + third)\n}.toMap\n```\n\nOr something like this:\n\n``` scala\nscala\u003e val Row(id, first, second, third) = \"en: 456, 123, 1203\"\nid: String = en\nfirst: Int = 456\nsecond: Int = 123\nthird: Int = 1203\n```\n\nThe digit groups are now typed as integers, and as a bonus we get compile-time\nvalidation for our regular expression syntax.\n\n## With named groups\n\nNow suppose we've got a regular expression with some named groups:\n\n``` scala\nimport io.expressier._\n\nval Data = x\"\"\"(?\u003cid\u003e\\w+)-(?\u003csub\u003e.):(?\u003cvalue\u003e\\d+)\"\"\"\n```\n\nNow we can access these names as methods on the match:\n\n``` scala\nval result = Data(\"foobar-Z:12345\").getOrElse(sys.error(\"Invalid input\"))\n```\n\nAnd then:\n\n``` scala\nscala\u003e result.id\nres0: String = foobar\n\nscala\u003e result.sub\nres1: Char = Z\n\nscala\u003e result.value\nres2: Int = 12345\n```\n\nNote that the type provider has determined that the sub-identifier can be\nreturned as a single character.\n\n## Case classes\n\nIt's also now possible to collect the results of a regular expression match into\na case class (thanks to [Shapeless][shapeless]'s `LabelledGeneric`):\n\n```scala\nscala\u003e case class Foo(i: Int, s: String, c: Char)\ndefined class Foo\n\nscala\u003e val parse = \"\"\"(?\u003ci\u003e\\d{4}): (?\u003cc\u003e.)(?\u003cs\u003e[a-z]+)\"\"\".express[Foo]\nparse: io.expressier.Express.To[Foo] = ...\n\nscala\u003e val result: Option[Foo] = parse(\"1234: Foo\")\nresult: Option[Foo] = Some(Foo(1234,oo,F))\n```\n\nNote that the orders of the fields and named groups don't have to match.\n\n## Warnings\n\n\nThe implementation is a mess. There's no obvious library for parsing regular\nexpressions in Java (or at least I didn't turn one up this evening), so I've\njust cracked open `java.util.regex`, and my `internals` package does a lot\nof horrible work with reflection on private fields and non-public classes\ninside `Pattern`. This is a limitation of the ecosystem, though, not of the\napproach.\n\n## License\n\ncirce is licensed under the **[Apache License, Version 2.0][apache]** (the\n\"License\"); you may not use this software except in compliance with the License.\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n[apache]: http://www.apache.org/licenses/LICENSE-2.0\n[macro-compat]: https://github.com/milessabin/macro-compat\n[scala-js]: http://www.scala-js.org/\n[shapeless]: https://github.com/milessabin/shapeless\n[type-provider-slides]: https://github.com/travisbrown/type-provider-examples/blob/master/docs/scalar-2014-slides.pdf\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisbrown%2Fexpressier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftravisbrown%2Fexpressier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisbrown%2Fexpressier/lists"}