{"id":13760298,"url":"https://github.com/lambdaisland/regal","last_synced_at":"2025-06-28T13:42:06.605Z","repository":{"id":39604496,"uuid":"239814037","full_name":"lambdaisland/regal","owner":"lambdaisland","description":"Royally reified regular expressions","archived":false,"fork":false,"pushed_at":"2025-04-18T20:18:06.000Z","size":231,"stargazers_count":332,"open_issues_count":4,"forks_count":15,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-06-16T12:24:37.180Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lambdaisland.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null}},"created_at":"2020-02-11T16:47:50.000Z","updated_at":"2025-05-26T13:22:42.000Z","dependencies_parsed_at":"2024-04-23T08:49:35.587Z","dependency_job_id":"b546aa66-304d-43c8-b92b-4d2ee94efe2e","html_url":"https://github.com/lambdaisland/regal","commit_stats":{"total_commits":158,"total_committers":14,"mean_commits":"11.285714285714286","dds":"0.45569620253164556","last_synced_commit":"d1b00769bd2bd076209b24c590fbc707a3ef01a0"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/lambdaisland/regal","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fregal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fregal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fregal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fregal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lambdaisland","download_url":"https://codeload.github.com/lambdaisland/regal/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fregal/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261029230,"owners_count":23099717,"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-08-03T13:01:07.339Z","updated_at":"2025-06-28T13:42:06.586Z","avatar_url":"https://github.com/lambdaisland.png","language":"Clojure","funding_links":["http://opencollective.com/lambda-island"],"categories":["Clojure"],"sub_categories":[],"readme":"# Regal\n\n_Royally reified regular expressions_\n\n\n\u003c!-- badges --\u003e\n[![CircleCI](https://circleci.com/gh/lambdaisland/regal.svg?style=svg)](https://circleci.com/gh/lambdaisland/regal) [![cljdoc badge](https://cljdoc.org/badge/lambdaisland/regal)](https://cljdoc.org/d/lambdaisland/regal) [![Clojars Project](https://img.shields.io/clojars/v/lambdaisland/regal.svg)](https://clojars.org/lambdaisland/regal)\n[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://book.babashka.org#badges)\n\u003c!-- /badges --\u003e\n\n## tl;dr\n\nRegal lets you manipulate regular expressions as data, by providing a\nHiccup-like regex syntax, and ways to convert between this Hiccup syntax (Regal\nsyntax), compiled regex patterns, and test.check generators. It also helps with\nwriting cross-platform code by providing consistent semantics across JS/Java\nruntimes, and it allows converting JavaScript regex to Java regex semantically\n(useful for e.g. dealing with JSON Schema in Clojure)\n\n## The slightly longer version\n\nRegal provides a syntax for writing regular expressions using plain Clojure\ndata: vectors, keywords, strings. This is known as Regal notation.\n\nOnce you have a Regal form you can either compile it to a regex object\n(`java.util.regex.Pattern` or JavaScript `RegExp`), or you can use it to create\na Generator (see [test.check](https://github.com/clojure/test.check)) for\ngenerating values that conform to the given pattern.\n\nIt is also possible to parse regular expression patterns back to Regal forms.\n\nRegal is Clojure and ClojureScript compatible, and has fixed semantics across\nplatforms. Write your forms once and run them anywhere! It also allows\nmanipulating multiple regex flavors regardless of the current platform, so you\ncan do things like converting a JavaScript regex pattern to one that is suitable\nfor Java's regex engine.\n\n\u003c!-- opencollective --\u003e\n## Support Lambda Island Open Source\n\nRegal is part of a growing collection of quality Clojure libraries and\ntools released on the Lambda Island label. If you find value in our work please\nconsider [becoming a backer on Open Collective](http://opencollective.com/lambda-island#section-contribute)\n\u003c!-- /opencollective --\u003e\n\n## Project status\n\nRegal is alpha level software, this does not mean it is of low quality or not\nfit for use, it does mean that future breakage of the API is still possible.\n\nThe following aspects of the library are generally well tested and developed,\nand we intend to retain compatibility as much as practically possible.\n\n- Regal syntax as described in this README\n- Generating regex patterns from regal forms\n- Parsing regex patterns to regal forms\n\nThe following aspects have known issues or are otherwise untested or incomplete,\nand you can expect them to change significantly as we further develop them:\n\n- Creating test.check generators from regal forms\n- clojure.spec-alpha integration\n- Malli integration\n\n## Installation\n\ndeps.edn\n\n``` clojure\nlambdaisland/regal {:mvn/version \"0.1.175\"}\n```\n\nproject.clj\n\n``` clojure\n[lambdaisland/regal \"0.1.175\"]\n```\n\n### An example\n\n``` clojure\n(require '[lambdaisland.regal :as regal]\n         '[lambdaisland.regal.generator :as regal-gen])\n\n;; Regal expression, like Hiccup but for Regex\n(def r [:cat\n        [:+ [:class [\\a \\z]]]\n        \"=\"\n        [:+ [:not \\=]]])\n\n;; Convert to host-specific regex\n(regal/regex r)\n;;=\u003e #\"[a-z]+\\Q=\\E[^=]+\"\n\n;; Match strings\n(re-matches (regal/regex r) \"foo=bar\")\n;;=\u003e \"foo=bar\"\n\n;; ... And generate them\n(regal-gen/gen r)\n;;=\u003e #clojure.test.check.generators.Generator{...}\n\n(regal-gen/sample r)\n;;=\u003e (\"t=\" \"d=5Ë\" \"zja=·\" \"uatt=ß¾\" \"lqyk=É\" \"xkj=q\\f\" \"gxupw=æ\" \"pkadbgmc=¯²\" \"f=ÃJ\" \"d=ç\")\n```\n\n### A swiss army knife\n\nRegal can convert between three different represenations for regular\nexpressions, Regal **forms**, **patterns**(i.e. strings), and **regex** objects.\nHere is an overview of how to get from one to the other.\n\n| ↓From / To→ | Form                                   | Pattern                          | Regex                      |\n|---------------|----------------------------------------|----------------------------------|----------------------------|\n| Form          | identity                               | lambdaisland.regal/pattern       | lambdaisland.regal/regex   |\n| Pattern       | lambdaisland.regal.parse/parse-pattern | identity                         | lambdaisland.regal/compile |\n| Regex         | lambdaisland.regal.parse/parse         | lambdaisland.regal/regex-pattern | identity                   |\n\n### Regal forms\n\nForms consist of vectors, keywords, strings, character literals, and in some\ncases integers. For example:\n\n``` clojure\n[:cat [:alt [:char 11] [:char 13]] \\J [:rep \"hello\" 2 3]]\n```\n\nForms have platform-independent semantics. The same regal form will match the\nsame strings both in Clojure and ClojureScript, even though Java and JavaScript\n(and even different versions of Java or JavaScript) have different regex\n\"flavors\". In other words, we generate the regex that is right for the target\nplatform.\n\n``` clojure\n;; Clojure\n(regal/regex :vertical-whitespace) ;;=\u003e #\"\\v\"\n\n;; ClojureScript\n(regal/regex :vertical-whitespace) ;;=\u003e #\"[\\n\\x0B\\f\\r\\x85\\u2028\\u2029]\"\n```\n\nRegal currently knows about three \"flavors\"\n\n- `:java8` Java 1.8 (earlier versions are not supported)\n- `:java9` Java 9 or later\n- `:ecma` ECMAScript (JavaScript)\n\nBy default it takes the flavor that is best suited for the platform, but you can override that with `lambdaisland.regal/with-flavor`\n\n``` clojure\n(regal/with-flavor :ecma\n  (regal/pattern ...))\n```\n\nNote that using `regal/regex` with a flavor that does not correspond with the\nflavor of the platform may yield unexpected results, when dealing with \"foreign\"\nregex flavors always stick to string representations (i.e. **patterns**).\n\n### Pattern\n\nThe second regex representation regal knows about is the **pattern**, i.e. the\nregex pattern in string form.\n\n``` clojure\n(regal/regex-pattern #\"\\u000B\\v\") ;; =\u003e \"\\\\u000B\\\\v\"\n```\n\nDepending on the situation there are several reasons why you might want to use\nthis pattern representation over the compiled regex object.\n\n- simple strings, so easy to (de-)serialize\n- value semantics (can be compared)\n- allow manipulating regex pattern of regex flavors other than the one supported\n  by the current runtime\n\nNote that in Clojure the syntax available in regex patterns differs from the\nsyntax available in strings, in particluar when it comes to notations starting\nwith a backslash. e.g. `#\"\\xFF\"` is a valid regex, while `\"\\xFF\"` is not a valid\nstring. We encode regex patterns in strings, which practically speaking means\nthat backslashes are escaped (doubled).\n\n``` clojure\n(regal/regex-pattern #\"\\xFF\") ;;=\u003e \"\\\\xFF\"\n(regal/compile \"\\\\xFF\")       ;;=\u003e #\"\\xFF\"\n```\n\n### Regex\n\nTo use the regex engine provided by the runtime (e.g. through `re-find` or\n`re-seq`) you need a platform-specific regex object. This is what\n`lambdaisland.regal/regex` gives you.\n\n### Grammar\n\n- Strings and characters match literally. They are escaped, so `.` matches a\n  period, not any character, `^` matches a caret, etc.\n- A few keywords have special meaning.\n  - `:any` : match any character, like `.`. Does not match newlines.\n  - `:start` match the start of the input\n  - `:end` : match the end of the input\n  - `:digit` : match any digit (`0-9`)\n  - `:non-digit` : match non-digits (not `0-9`)\n  - `:word` : match word characters (`A-Za-z0-9_`)\n  - `:non-word` : match non-word characters (not `A-Za-z0-9_`)\n  - `:newline` : Match `\\n`\n  - `:return` : Match `\\r`\n  - `:tab` : Match `\\t`\n  - `:form-feed` : Match `\\f`\n  - `:line-break` : Match `\\n`, `\\r`, `\\r\\n`, or other unicode newline characters\n  - `:alert` : match `\\a` (U+0007)\n  - `:escape` : match `\\e` (U+001B)\n  - `:whitespace` : match any whitespace character. Uses `\\s` on JavaScript, and\n    a character range of whitespace characters on Java with equivalent semantics\n    as JavaScript `\\s`, since `\\s` in Java only matches ASCII whitespace.\n  - `:non-whitespace` : match non-whitespace\n  - `:vertical-whitespace` : match vertical whitespace, including newlines and vertical tabs `#\"\\n\\x0B\\f\\r\\x85\\u2028\\u2029\"`\n  - `:vertical-tab` : match a vertical tab `\\v` (U+000B)\n  - `:null` : match a NULL byte/char\n- All other forms are vectors, with the first element being a keyword\n  - `[:cat forms...]` : concatenation, match the given Regal expressions in order\n  - `[:alt forms...]` : alternatives, match one of the given options, like `(foo|bar|baz)`\n  - `[:* form]` : match the given form zero or more times\n  - `[:+ form]` : match the given form one or more times\n  - `[:? form]` : match the given form zero or one time\n  - `[:*? form]` : lazily match the given form zero or more times\n  - `[:+? form]` : lazily match the given form one or more times\n  - `[:?? form]` : lazily match the given form zero or one time\n  - `[:class entries...]` : match any of the given characters or ranges, with ranges given as two element vectors. E.g. `[:class [\\a \\z] [\\A \\Z] \"_\" \"-\"]` is equivalent to `[a-zA-Z_-]`\n  - `[:not entries...]` : like `:class`, but negates the result, equivalent to `[^...]`\n  - `[:repeat form num]` : repeat a form fixed number of times, like `{5}`\n  - `[:repeat form min max]` : repeat a form a number of times, like `{2,5}`\n  - `[:lazy-repeat form num]` : lazily repeat a form fixed number of times, like `{5}?`\n  - `[:lazy-repeat form min max]` : lazily repeat a form a number of times, like `{2,5}?`\n  - `[:capture forms...]` : capturing group with implicit concatenation of the given forms\n  - `[:char number]` : a single character, denoted by its unicode codepoint\n  - `[:ctrl char]` : a control character, e.g. `[:ctrl \\A]` =\u003e `^A` =\u003e `#\"\\cA\"`\n  - `[:lookahead ...]` : match if followed by pattern, without consuming input\n  - `[:negative-lookahead ...]` : match if not followed by pattern\n  - `[:lookbehind ...]` : match if preceded by pattern\n  - `[:negative-lookbehind ...]` : match if not preceded by pattern\n  - `[:atomic ...]` : match without backtracking ([atomic group](https://www.regular-expressions.info/atomic.html))\n- A `clojure.spec.alpha` definition of the grammar can be made available as `:lambdaisland.regal/form` by explicitly requiring `lambdaisland.regal.spec-alpha`\n\nYou can add your own extensions (custom tokens) by providing a `:registry` option\nmapping namespaced keywords to Regal expressions.\n\n### Use with spec.alpha\n\n``` clojure\n(require '[lambdaisland.regal.spec-alpha :as regal-spec]\n         '[clojure.spec.alpha :as s]\n         '[clojure.spec.gen.alpha :as gen])\n\n(s/def ::x-then-y (regal-spec/spec [:cat [:+ \"x\"] \"-\" [:+ \"y\"]]))\n\n(s/def ::xy-with-stars (regal-spec/spec [:cat \"*\" ::x-then-y \"*\"]))\n\n(s/valid? ::xy-with-stars \"*xxx-yy*\")\n;; =\u003e true\n\n(gen/sample (s/gen ::xy-with-stars))\n;; =\u003e (\"*x-y*\"\n;;     \"*xx-y*\"\n;;     \"*x-y*\"\n;;     \"*xxxx-y*\"\n;;     \"*xxx-yyyy*\"\n;;     \"*xxxx-yyy*\"\n;;     \"*xxxxxxx-yyyyy*\"\n;;     \"*xx-yyy*\"\n;;     \"*xxxxx-y*\"\n;;     \"*xxx-yyyy*\")\n```\n\n### Use with Malli\n\nThe `::rm/regal` schema provides a wrapper for regal checks.\n\n```clojure\n(require '[malli.core :as m]\n         '[malli.error :as me]\n         '[malli.generator :as mg]\n         '[lambdaisland.regal.malli :as rm]\n         '[lambdaisland.regal.malli.generator :as rmg])\n\n(def malli-opts {:registry {::rm/regal rm/rm-regal-schema}})\n\n(def schema (m/schema [::rm/regal [:+ \"y\"]] malli-opts))\n\n(m/form schema)\n;; =\u003e [::rm/regal [:+ \"y\"]]\n\n(m/type schema)\n;; =\u003e ::rm/regal\n\n(m/validate schema \"yyy\")\n;; =\u003e true\n\n(me/humanize (m/explain schema \"xxx\"))\n;; =\u003e [\"should match regex\"]\n\n(rmg/register-regal-generator) ;; register generator for ::rm/regal schema\n(mg/sample schema)\n;; =\u003e (\"y\" \"y\" \"y\" \"y\" \"yy\" \"yy\" \"yyyyy\" \"yyyyy\" \"yyyyy\" \"yyyy\")\n```\n\n### BYO test.check / spec-alpha\n\nRegal does not declare any dependencies. This lets people who only care about\nusing Regal Expressions to replace normal regexes to require\n`lambdaisland.regal` without imposing extra dependencies upon them.\n\nIf you want to use `lambdaisland.regal.generator` you will require\n`org.clojure/test.check`. For `lambdisland.regal.spec-alpha` you will\nadditionally need `org.clojure/spec-alpha`.\n\n\u003c!-- contributing --\u003e\n### Contributing\n\nEveryone has a right to submit patches to this projects, and thus become a contributor.\n\nContributors MUST\n\n- adhere to the [LambdaIsland Clojure Style Guide](https://nextjournal.com/lambdaisland/clojure-style-guide)\n- write patches that solve a problem. Start by stating the problem, then supply a minimal solution. `*`\n- agree to license their contributions as MPLv2.\n- not break the contract with downstream consumers. `**`\n- not break the tests.\n\nContributors SHOULD\n\n- update the CHANGELOG and README.\n- add tests for new functionality.\n\nIf you submit a pull request that adheres to these rules, then it will almost\ncertainly be merged immediately. However some things may require more\nconsideration. If you add new dependencies, or significantly increase the API\nsurface, then we need to decide if these changes are in line with the project's\ngoals. In this case you can start by [writing a\npitch](https://nextjournal.com/lambdaisland/pitch-template), and collecting\nfeedback on it.\n\n`*` This goes for features too, a feature needs to solve a problem. State the problem it solves, then supply a minimal solution.\n\n`**` As long as this project has not seen a public release (i.e. is not on Clojars)\nwe may still consider making breaking changes, if there is consensus that the\nchanges are justified.\n\u003c!-- /contributing --\u003e\n\n### Prior Art\n\n- [irregex for Chicken Scheme](http://synthcode.com/scheme/irregex/)\n- [CL-PPRE create-scanner (Common Lisp)](http://edicl.github.io/cl-ppcre/#create-scanner2)\n- [test.chuck string-from-regex](https://github.com/gfredericks/test.chuck#string-from-regex)\n- [rx for Emacs Lisp](https://www.emacswiki.org/emacs/rx) (and [source](https://github.com/emacs-mirror/emacs/blob/master/lisp/emacs-lisp/rx.el))\n- [cgrand/regex](https://github.com/cgrand/regex/)\n- [ClojureVerbalExpressions](https://github.com/VerbalExpressions/ClojureVerbalExpressions)\n- [FLIP](https://dl.acm.org/doi/10.1145/800005.807968), Bobrow and Teitelman, 1966\n\n## License\n\nCopyright \u0026copy; 2020 Arne Brasseur\n\nLicensed under the term of the Mozilla Public License 2.0, see LICENSE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaisland%2Fregal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flambdaisland%2Fregal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaisland%2Fregal/lists"}