{"id":13721621,"url":"https://github.com/alehander92/gara","last_synced_at":"2026-01-18T14:33:04.578Z","repository":{"id":43263064,"uuid":"148918208","full_name":"alehander92/gara","owner":"alehander92","description":null,"archived":false,"fork":false,"pushed_at":"2020-06-16T10:52:39.000Z","size":49,"stargazers_count":103,"open_issues_count":13,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-19T00:10:50.297Z","etag":null,"topics":["nim","pattern"],"latest_commit_sha":null,"homepage":"","language":"Nim","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/alehander92.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-09-15T16:13:38.000Z","updated_at":"2024-08-06T23:14:20.000Z","dependencies_parsed_at":"2022-08-29T18:50:24.512Z","dependency_job_id":null,"html_url":"https://github.com/alehander92/gara","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/alehander92%2Fgara","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alehander92%2Fgara/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alehander92%2Fgara/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alehander92%2Fgara/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alehander92","download_url":"https://codeload.github.com/alehander92/gara/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251748929,"owners_count":21637413,"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":["nim","pattern"],"created_at":"2024-08-03T01:01:19.393Z","updated_at":"2026-01-18T14:33:04.563Z","avatar_url":"https://github.com/alehander92.png","language":"Nim","funding_links":[],"categories":["Macros"],"sub_categories":["Byte Size"],"readme":"[![Build Status (Travis)](https://img.shields.io/travis/alehander42/gara/master.svg \"build status\")](https://travis-ci.org/alehander42/gara)\n\n# gara\n\nA nim macro-based pattern matching library.\n\n### DSL\n\nA library that provides a `match` macro which can be used as a pattern matching construct.\n\nA `matches` macro which returns true if a value is matched.\n\nA `maybeMatches` macro which returns an `Option[tuple]` of the matched \"variables\".\n\n\n```nim\nvar rectangles = @[Rectangle(a: 0, b: 2), Rectangle(a: 4, b: 4), Rectangle(a: 4, b: 4), Rectangle(a: 4, b: 4)]\n\nmatch(rectangles):\n  @[]:\n    fail()\n  @[_, *(a: 4, b: 4) @others]: # (a: 4, b: 4) is a shorthand for `TypeName(field: value)`, see the next examples\n    check(others == a[1 .. ^1]) # we match the rest, because they all match subpattern\n  _:\n    fail()\n```\n\nIt's still experimental, there are some bugs left and the design of the DSL might still change depending on feedback of the community. For now it's a personal project, but I'd love if other people would join as contributors. It's inspired by @andreaferretti 's [patty](https://github.com/andreaferretti/patty) and @krux02 's [ast-pattern-matching](https://github.com/krux02/ast-pattern-matching) (and more stuff, there is a credits section!)\n\n\n### Features\n\nMatching\n\n* values\n* types\n* objects\n* nested subpatterns\n* capture subpatterns and values with `@name`\n* variants\n* wildcard\n* seq\n* match many elements in seq with `*`\n* two kinds of custom unpackers (thanks to @krux02 for making me aware of the scala pattern matching design and apply/unapply: they work differently here though)\n* support for recognizing other types as variant\n* if guards\n* unification\n* matches expression\n* option matching\n\n### Rationale\n\n\nGoals:\n\n* Ability to generate code with zero overhead compared to manually written if/case/for equivalents\n* Expressive and flexible syntax for patterns and extensible hooks\n* Nice error reporting and pretty understandable generated code\n\nThe goals are ordered by priority.\n\nSpeed is very important: the goal is to be able to use matching everywhere where you'd use a complicated `if`/`case`.\nIf there are cases, when we're slower:\n\nThis should be considered a bug.\n\nor\n\nThis is a limitation of the library: which is not cool.\n\nCurrently not every feature is optimized well(there are some blockers, and some features are still experimental).\n\n\nThe library is supposed to be extensible: please take a look at the unpackers section. For example we already implement the option matching with a `Some` unpacker.\n\nI have a plan about the error reporting part: basically with several hooks inside the current code we should be able to produce\ngood messages. My plan API is\n\n```nim\nmatchDebug(a): # generates an error message describing the comparisons we had and raises it if we hit unimplemented else\n\n# or\n\nlastMatchError() # if we detect this call, we generate an error message: we don't do it by default to not slow down the code\n```\n\nWith some discipline we can generate pretty readable code from our macro.\nThis would be also beneficial for the end user, as we can map his invocation with the generated code and help him\nsee the problem easily.\n\nThere are 3 cases:\n\n* The user had a logical error in his patterns: easily seen with the generated code\n* The user used a pattern in an unexpected way: this way he can see how its expansion differs from his expectation\n* Our patterns worked incorrectly: the user can issue a bug\n\n\n\n### Values\n\nJust test for equality with the value. Works also if you pass an existing variable instead of 2. For now you can't just pass various expressions tho (2 + 2), as I want to reserve syntax for the patterns\n\n```nim\nlet a = 2\n\nmatch(a):\n  2:\n    echo \"2\"\n  _:\n    echo \"no 2\"\n```\n\n### Types\n\nThe library tests with `is`\n\n```nim\nmatch(a):\n  Rectangle: # matches Rectangle(a: 0), Rectangle(a: 2, b: 4)\n    echo \"rectangle\"\n  _:\n    echo \"other\"\n```\n\n### Objects\n\nWe have a shorthand syntax for objects. You can type only the fields, and then we check only them: this is a good idea because usually\nyou know the type of the object that you are passing, so there is rarely ambiguity.\nOf course you can still add the type if you want. You can use this for tuples too.\n\nYou can pass just some of the fields!\n\n```nim\nvar rectangle = Rectangle(a: 0, b: 0)\n\nmatch(rectangle):\n  (a: 0, b: 0): # matches Rectangle(a: 0, b: 0)\n    echo \"ok\"\n  Rectangle(a: -2): # matches Rectangle(a: -2) (a: -2, e: 4)\n    echo \"weird\"\n  _:\n    echo 0\n```\n\n### Subpatterns\n\nYou can match subpatterns.\n\n```nim\nvar a = A(b: B(c: 0, e: 2))\nmatch(a):\n  A(b: B(c: 0)):\n    echo \"ok\"\n  _:\n    echo \"fail\"\n```\n\n### Capturing\n\nWe capture with `@name` for all our usecases: wsubpatterns and values.\nYou write `stuff @name` which shouldn't be ambigious in general(please read the answers and questions section)\n\n```nim\nmatch(a):\n  C(e: E(f: @f) @e): # matches C(e: E(f: 0)) and creates local variables f = 0 and e = E(..)\n    echo e\n    echo f\n  _:\n    echo \"fail\"\n```\n\n### Variants\n\nWe recognize when you do `enumLabel(..)` and we match variants then. That's very nice if you are threating them as abstract data types.\n\n```nim\nmatch(a):\n  Merge(original: @original, other: @other):\n    echo original\n  Normal(message: @message):\n    echo message\n  _:\n    echo a\n```\n\n### Wildcards\n\nYou can use `_` as a wildcard, it always succeeds. It's also used as `otherwise`.\n\n```nim\nmatch(a):\n  @[_]: # matches any value\n    echo a\n  _:\n    echo \"nope\"\n```\n\n### seq\n\n```nim\nmatch(a):\n  @[4, 5]: # matches @[4, 5]\n    echo \"ok\"\n  _:\n    echo \"no\"\n```\n\n### Many elements in seq\n\nYou can match repeated properties\n\n```nim\nvar rectangles = @[e, e, Rectangle(a: 0)]\nmatch(a):\n  @[_, _, *(a: @list)]: # creates a local variable list which collects the a fields in the matches elements : @[0]\n    echo list\n  _:\n    echo @[]\n```\n\nHere we match the elements after 1 and collect their a fields. You can also just `@name` the whole subpattern: it should be always a seq.\nWe use `allIt` for the test, but in a case like this, we optimize it out, as it is always true(we just load values).\n\n### Unpackers\n\nWe can have unpackers for types: you define `proc unpack(t: Type): T` for your type.\nThe powerful thing is, `T` can be anything that has a len and `[int]`(we will add a concept for that later, but it covers seq, tuple).\nFor example we do this for Rectangle\n\n```nim\nproc unpack(rectangle: Rectangle): seq[int] =\n  @[rectangle.a, rectangle.b]\n```\n\nOf course we are lucky here, but you can do transformations for more complicated cases.\n\nWhen you do this, you can use the unpacked values like `Type(value, value)` .\nWe even recognize the enum case, so you can do it for variants too.\n\nwe also have function unpackers: `proc name(t: Type): T`. This way you can have many unpackers for the same type. This is useful , especially if a builtin type already has a default unpacker, and you need a custom one.\nYou match passing them as calls with their expected values: `name(res)`\n\n```nim\nproc data(email: Email): tuple[name: string, domain: string] =\n  let words = email.raw.split('@', 1)\n  (name: words[0], domain: words[1])\n\nproc tokens(email: Email): seq[string] =\n  # slow\n  result = @[]\n  var token = \"\"\n  for i, c in email.raw:\n    if not c.isAlphaNumeric():\n      if token.len \u003e 0:\n        result.add(token)\n        token = \"\"\n      result.add($c)\n    else:\n      token.add(c)\n  if token.len \u003e 0:\n    result.add(token)\n\nmatch(email):\n  data(name: \"academy\"): # matches data(email) with (name: \"academy\")\n    echo email\n  tokens(@[_, _, _, _, @token]): # matches tokens(email)\n    echo token\n```\n\n(I got the idea for the email example from @andreaferretti's patty)\n\n### Support for types as variants\n\nSometimes a type acts like a variant, but isn't defined like one: you can teach the library to do it.\nIt uses internally `eKind` to get the kind field of a variant, so you just need to override it\n\n```nim\nproc eKind*(a: A): AKind =\n  a.stuff\n```\n\n### If guards\n\n```nim\nmatch(a);\n  (b: @e) and e == 4: # generates local variable e and checks e\n    echo e\n  _:\n    echo -1\n```\n\nWe can add `or` too: does it make sense?\n\n### Unification\n\ninspired by @andreaferretti's patty ideas\n\n```nim\nlet a = @[0, 0]\nmatch(a):\n  @[@x, @x]: # creates a local variable x only if both values are equal\n    echo \"equal\"\n  _:\n    echo \"not\"\n```\n\nWe check if all the subvalues are equal: that wasn't very easy to implement\n\n### Match expressions\n\nIn general, `match` can be used as an expression:\n\n```nim\nlet a = 2\n\nlet s = match(a):\n  2:\n    \"it's a 2\"\n  _:\n    \"it's not a 2\"\n```\n\nThe `matches` macro allows to match against a single expression:\nit returns a boolean value which is `true` when it matches the value.\n\n```nim\nif a.matches((b: 2, c: 4)):\n  echo 0\n\n```\n\nYou can also have captures with `maybeMatches`: it returns an `Option[tuple]`.\n\n```nim\nlet c = a.maybeMatches((a: @a, b: @b))\nif c.isSome:\n  echo c.a\n  echo c.b\n```\n\n\n### Plan\n\n* error reporting\n* fixes\n\n### Name\n\ngara means a train station in bulgarian. why a train station? I am travelling with trains these days, and I like bulgarian words.\n\n### Questions and answers\n\n**I don't like @name : it's bizarre and surprising for users**\n\nI like it, but I'd welcome ideas for a better syntax! Please, first check this list:\n\n* `expr @ name` I can't see how to make it consistent with the `(field: capture)` case, the same with other binary\n* `expr @ ``name`` ` I think this is more surprising, as it's used for 2 different puproses in quotes and in names\n\n**It's not type safe**\n\nI want to leave all the type checking to the Nim compiler. The library translates your patterns to Nim code, so the only problem should be that sometimes it might be hard to debug why something is failing: I'll try to improve that with error messages.\n\n**It is too complicated**\n\nI like to think it is relatively simple: I've tried to limit new syntax and to optimize it for variants, so e.g. in `name(..)` we first check if name is enum, then type, then we assume it might be an unpacker. In the beginning I even had `~enum` syntax for matching variants, but it seemed crazy.\nOtherwise a lot of custom or new functionality should be doable with unpackers or with a bit upgraded unpackers, please check them out\n\nFAITH\n\n### Credits\n[@krux02](https://github.com/krux02/) and [@andreaferretti](https://github.com/andreaferretti) are authors of the original nim pattern matching libs:\n\n* @krux02 's [ast-pattern-matching](https://github.com/krux02/ast-pattern-matching)\n* @andreaferretti 's [patty](https://github.com/andreaferretti/patty)\n\nI took inspiration from their libraries and discussions (An early version of this dsl was even a PR to @krux02 's lib).\n\nThanks to [@mratsim](https://github.com/mratsim) for giving me the `@name` idea with one of his [github comments on possible nim pattern matching syntax](https://github.com/nim-lang/Nim/issues/8649#issuecomment-413318800), I initially had way more inconsistent notation in mind.\n\nThanks to [@narimiran](https://github.com/narimiran/) for giving design ideas and doc fixes!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falehander92%2Fgara","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falehander92%2Fgara","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falehander92%2Fgara/lists"}